# -*- coding: utf-8 -*- # # frozen_string_literal: true module Rouge module Lexers class Ruby < RegexLexer title "Ruby" desc "The Ruby programming language (ruby-lang.org)" tag 'ruby' aliases 'rb' filenames '*.rb', '*.ruby', '*.rbw', '*.rake', '*.gemspec', '*.podspec', 'Rakefile', 'Guardfile', 'Gemfile', 'Capfile', 'Podfile', 'Vagrantfile', '*.ru', '*.prawn', 'Berksfile', '*.arb', 'Dangerfile', 'Fastfile', 'Deliverfile', 'Appfile' mimetypes 'text/x-ruby', 'application/x-ruby' def self.detect?(text) return true if text.shebang? 'ruby' end state :symbols do # symbols rule %r( : # initial : @{0,2} # optional ivar, for :@foo and :@@foo [a-z_]\w*[!?]? # the symbol )xi, Str::Symbol # special symbols rule %r(:(?:\*\*|[-+]@|[/\%&\|^`~]|\[\]=?|<<|>>|<=?>|<=?|===?)), Str::Symbol rule %r/:'(\\\\|\\'|[^'])*'/, Str::Symbol rule %r/:"/, Str::Symbol, :simple_sym end state :sigil_strings do # %-sigiled strings # %(abc), %[abc], %, %.abc., %r.abc., etc delimiter_map = { '{' => '}', '[' => ']', '(' => ')', '<' => '>' } rule %r/%([rqswQWxiI])?([^\w\s])/ do |m| open = Regexp.escape(m[2]) close = Regexp.escape(delimiter_map[m[2]] || m[2]) interp = /[rQWxI]/ === m[1] toktype = Str::Other puts " open: #{open.inspect}" if @debug puts " close: #{close.inspect}" if @debug # regexes if m[1] == 'r' toktype = Str::Regex push :regex_flags end token toktype push do uniq_chars = "#{open}#{close}".squeeze uniq_chars = '' if open == close && open == "\\#" rule %r/\\[##{uniq_chars}\\]/, Str::Escape # nesting rules only with asymmetric delimiters if open != close rule %r/#{open}/ do token toktype push end end rule %r/#{close}/, toktype, :pop! if interp mixin :string_intp_escaped rule %r/#/, toktype else rule %r/[\\#]/, toktype end rule %r/[^##{uniq_chars}\\]+/m, toktype end end end state :strings do mixin :symbols rule %r/\b[a-z_]\w*?[?!]?:\s+/, Str::Symbol, :expr_start rule %r/'(\\\\|\\'|[^'])*'/, Str::Single rule %r/"/, Str::Double, :simple_string rule %r/(?_*\$?:"]), Name::Variable::Global rule %r/\$-[0adFiIlpvw]/, Name::Variable::Global rule %r/::/, Operator mixin :strings rule %r/(?:#{keywords.join('|')})(?=\W|$)/, Keyword, :expr_start rule %r/(?:#{keywords_pseudo.join('|')})\b/, Keyword::Pseudo, :expr_start rule %r( (module) (\s+) ([a-zA-Z_][a-zA-Z0-9_]*(::[a-zA-Z_][a-zA-Z0-9_]*)*) )x do groups Keyword, Text, Name::Namespace end rule %r/(def\b)(\s*)/ do groups Keyword, Text push :funcname end rule %r/(class\b)(\s*)/ do groups Keyword, Text push :classname end rule %r/(?:#{builtins_q.join('|')})[?]/, Name::Builtin, :expr_start rule %r/(?:#{builtins_b.join('|')})!/, Name::Builtin, :expr_start rule %r/(?=])/ do groups Punctuation, Text, Name::Function push :method_call end rule %r/[a-zA-Z_]\w*[?!]/, Name, :expr_start rule %r/[a-zA-Z_]\w*/, Name, :method_call rule %r/\*\*|<>?|>=|<=|<=>|=~|={3}|!~|&&?|\|\||\./, Operator, :expr_start rule %r/[-+\/*%=<>&!^|~]=?/, Operator, :expr_start rule(/[?]/) { token Punctuation; push :ternary; push :expr_start } rule %r<[\[({,:\\;/]>, Punctuation, :expr_start rule %r<[\])}]>, Punctuation end state :has_heredocs do rule %r/(?>? | <=>? | >= | ===? ) )x do |m| puts "matches: #{[m[0], m[1], m[2], m[3]].inspect}" if @debug groups Name::Class, Operator, Name::Function pop! end rule(//) { pop! } end state :classname do rule %r/\s+/, Text rule %r/\(/ do token Punctuation push :defexpr push :expr_start end # class << expr rule %r/<=0?n[x]:"" rule %r( [?](\\[MC]-)* # modifiers (\\([\\abefnrstv\#"']|x[a-fA-F0-9]{1,2}|[0-7]{1,3})|\S) (?!\w) )x, Str::Char, :pop! # special case for using a single space. Ruby demands that # these be in a single line, otherwise it would make no sense. rule %r/(\s*)(%[rqswQWxiI]? \S* )/ do groups Text, Str::Other pop! end mixin :sigil_strings rule(//) { pop! } end state :slash_regex do mixin :string_intp rule %r(\\\\), Str::Regex rule %r(\\/), Str::Regex rule %r([\\#]), Str::Regex rule %r([^\\/#]+)m, Str::Regex rule %r(/) do token Str::Regex goto :regex_flags end end state :end_part do # eat up the rest of the stream as Comment::Preproc rule %r/.+/m, Comment::Preproc, :pop! end end end end