lib/rouge/lexers/elixir.rb



# -*- coding: utf-8 -*- #
# frozen_string_literal: true

module Rouge
  module Lexers
    # Direct port of pygments Lexer.
    # See: https://bitbucket.org/birkenfeld/pygments-main/src/7304e4759ae65343d89a51359ca538912519cc31/pygments/lexers/functional.py?at=default#cl-2362
    class Elixir < RegexLexer
      title "Elixir"
      desc "Elixir language (elixir-lang.org)"

      tag 'elixir'
      aliases 'elixir', 'exs'

      filenames '*.ex', '*.exs'

      mimetypes 'text/x-elixir', 'application/x-elixir'

      state :root do
        rule /\s+/m, Text
        rule /#.*$/, Comment::Single
        rule %r{\b(case|cond|end|bc|lc|if|unless|try|loop|receive|fn|defmodule|
             defp?|defprotocol|defimpl|defrecord|defmacrop?|defdelegate|
             defexception|defguardp?|defstruct|exit|raise|throw|after|rescue|catch|else)\b(?![?!])|
             (?<!\.)\b(do|\-\>)\b}x, Keyword
        rule /\b(import|require|use|recur|quote|unquote|super|refer)\b(?![?!])/, Keyword::Namespace
        rule /(?<!\.)\b(and|not|or|when|xor|in)\b/, Operator::Word
        rule %r{%=|\*=|\*\*=|\+=|\-=|\^=|\|\|=|
             <=>|<(?!<|=)|>(?!<|=|>)|<=|>=|===|==|=~|!=|!~|(?=[\s\t])\?|
             (?<=[\s\t])!+|&(&&?|(?!\d))|\|\||\^|\*|\+|\-|/|
             \||\+\+|\-\-|\*\*|\/\/|\<\-|\<\>|<<|>>|=|\.|~~~}x, Operator
        rule %r{(?<!:)(:)([a-zA-Z_]\w*([?!]|=(?![>=]))?|\<\>|===?|>=?|<=?|
             <=>|&&?|%\(\)|%\[\]|%\{\}|\+\+?|\-\-?|\|\|?|\!|//|[%&`/\|]|
             \*\*?|=?~|<\-)|([a-zA-Z_]\w*([?!])?)(:)(?!:)}, Str::Symbol
        rule /:"/, Str::Symbol, :interpoling_symbol
        rule /\b(nil|true|false)\b(?![?!])|\b[A-Z]\w*\b/, Name::Constant
        rule /\b(__(FILE|LINE|MODULE|MAIN|FUNCTION)__)\b(?![?!])/, Name::Builtin::Pseudo
        rule /[a-zA-Z_!][\w_]*[!\?]?/, Name
        rule %r{::|[%(){};,/\|:\\\[\]]}, Punctuation
        rule /@[a-zA-Z_]\w*|&\d/, Name::Variable
        rule %r{\b(0[xX][0-9A-Fa-f]+|\d(_?\d)*(\.(?![^\d\s])
             (_?\d)*)?([eE][-+]?\d(_?\d)*)?|0[bB][01]+)\b}x, Num

        mixin :strings
        mixin :sigil_strings
      end

      state :strings do
        rule /(%[A-Ba-z])?"""(?:.|\n)*?"""/, Str::Doc
        rule /'''(?:.|\n)*?'''/, Str::Doc
        rule /"/, Str::Doc, :dqs
        rule /'.*?'/, Str::Single
        rule %r{(?<!\w)\?(\\(x\d{1,2}|\h{1,2}(?!\h)\b|0[0-7]{0,2}(?![0-7])\b[^x0MC])|(\\[MC]-)+\w|[^\s\\])}, Str::Other

      end

      state :dqs do
        rule /"/, Str::Double, :pop!
        mixin :enddoublestr
      end

      state :interpoling do
        rule /#\{/, Str::Interpol, :interpoling_string
      end

      state :interpoling_string do
        rule /\}/, Str::Interpol, :pop!
        mixin :root
      end

      state :interpoling_symbol do
        rule /"/, Str::Symbol, :pop!
        mixin :interpoling
        rule /[^#"]+/, Str::Symbol
      end

      state :enddoublestr do
        mixin :interpoling
        rule /[^#"]+/, Str::Double
      end

      state :sigil_strings do
        # ~-sigiled strings
        # ~(abc), ~[abc], ~<abc>, ~|abc|, ~r/abc/, etc
        # Cribbed and adjusted from Ruby lexer
        delimiter_map = { '{' => '}', '[' => ']', '(' => ')', '<' => '>' }
        # Match a-z for custom sigils too
        sigil_opens = Regexp.union(delimiter_map.keys + %w(| / ' "))
        rule /~([A-Za-z])?(#{sigil_opens})/ do |m|
          open = Regexp.escape(m[2])
          close = Regexp.escape(delimiter_map[m[2]] || m[2])
          interp = /[SRCW]/ === m[1]
          toktype = Str::Other

          puts "    open: #{open.inspect}" if @debug
          puts "    close: #{close.inspect}" if @debug

          # regexes
          if 'Rr'.include? m[1]
            toktype = Str::Regex
            push :regex_flags
          end

          if 'Ww'.include? m[1]
            push :list_flags
          end

          token toktype

          push do
            rule /#{close}/, toktype, :pop!

            if interp
              mixin :interpoling
              rule /#/, toktype
            else
              rule /[\\#]/, toktype
            end

            rule /[^##{open}#{close}\\]+/m, toktype
          end
        end
      end

      state :regex_flags do
        rule /[fgimrsux]*/, Str::Regex, :pop!
      end

      state :list_flags do
        rule /[csa]?/, Str::Other, :pop!
      end
    end
  end
end