From 300986f4972f2973c3642e60952ac985814f9755 Mon Sep 17 00:00:00 2001 From: Peter Leitzen Date: Sun, 6 Mar 2016 14:14:58 +0100 Subject: [PATCH] Add support for crystal language Heavily based on the Ruby lexer. Add "guessing" specs for crystal lang --- lib/rouge/demos/crystal | 45 ++++ lib/rouge/lexers/crystal.rb | 416 ++++++++++++++++++++++++++++++++++++ spec/lexers/crystal_spec.rb | 22 ++ spec/visual/samples/crystal | 45 ++++ 4 files changed, 528 insertions(+) create mode 100644 lib/rouge/demos/crystal create mode 100644 lib/rouge/lexers/crystal.rb create mode 100644 spec/lexers/crystal_spec.rb create mode 100644 spec/visual/samples/crystal diff --git a/lib/rouge/demos/crystal b/lib/rouge/demos/crystal new file mode 100644 index 0000000000..988753e96d --- /dev/null +++ b/lib/rouge/demos/crystal @@ -0,0 +1,45 @@ +lib LibC + WNOHANG = 0x00000001 + + @[ReturnsTwice] + fun fork : PidT + fun getpgid(pid : PidT) : PidT + fun kill(pid : PidT, signal : Int) : Int + fun getpid : PidT + fun getppid : PidT + fun exit(status : Int) : NoReturn + + ifdef x86_64 + alias ClockT = UInt64 + else + alias ClockT = UInt32 + end + + SC_CLK_TCK = 3 + + struct Tms + utime : ClockT + stime : ClockT + cutime : ClockT + cstime : ClockT + end + + fun times(buffer : Tms*) : ClockT + fun sysconf(name : Int) : Long +end + +class Process + def self.exit(status = 0) + LibC.exit(status) + end + + def self.pid + LibC.getpid + end + + def self.getpgid(pid : Int32) + ret = LibC.getpgid(pid) + raise Errno.new(ret) if ret < 0 + ret + end +end diff --git a/lib/rouge/lexers/crystal.rb b/lib/rouge/lexers/crystal.rb new file mode 100644 index 0000000000..19029c375c --- /dev/null +++ b/lib/rouge/lexers/crystal.rb @@ -0,0 +1,416 @@ +# -*- coding: utf-8 -*- # + +module Rouge + module Lexers + class Crystal < RegexLexer + title "Crystal" + desc "Crystal The Programming Language (crystal-lang.org)" + tag 'crystal' + aliases 'cr' + filenames '*.cr' + + mimetypes 'text/x-crystal', 'application/x-crystal' + + def self.analyze_text(text) + return 1 if text.shebang? 'crystal' + 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 /:'(\\\\|\\'|[^'])*'/, Str::Symbol + rule /:"/, Str::Symbol, :simple_sym + end + + state :sigil_strings do + # %-sigiled strings + # %(abc), %[abc], %, %.abc., %r.abc., etc + delimiter_map = { '{' => '}', '[' => ']', '(' => ')', '<' => '>' } + rule /%([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 + rule /\\[##{open}#{close}\\]/, Str::Escape + # nesting rules only with asymmetric delimiters + if open != close + rule /#{open}/ do + token toktype + push + end + end + rule /#{close}/, toktype, :pop! + + if interp + mixin :string_intp_escaped + rule /#/, toktype + else + rule /[\\#]/, toktype + end + + rule /[^##{open}#{close}\\]+/m, toktype + end + end + end + + state :strings do + mixin :symbols + rule /\b[a-z_]\w*?:\s+/, Str::Symbol, :expr_start + rule /'(\\\\|\\'|[^'])*'/, Str::Single + rule /"/, Str::Double, :simple_string + rule /(?_*\$?:"]), Name::Variable::Global + rule /\$-[0adFiIlpvw]/, Name::Variable::Global + rule /::/, Operator + + mixin :strings + + rule /(?:#{keywords.join('|')})\b/, Keyword, :expr_start + rule /(?:#{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 /(def\b)(\s*)/ do + groups Keyword, Text + push :funcname + end + + rule /(class\b)(\s*)/ do + groups Keyword, Text + push :classname + end + + rule /(?:#{builtins_q.join('|')})[?]/, Name::Builtin, :expr_start + rule /(?:#{builtins_b.join('|')})!/, Name::Builtin, :expr_start + rule /(?=0?n[x]:"" + rule %r( + [?](\\[MC]-)* # modifiers + (\\([\\abefnrstv\#"']|x[a-fA-F0-9]{1,2}|[0-7]{1,3})|\S) + (?!\w) + )x, Str::Char + + mixin :has_heredocs + + rule /[A-Z][a-zA-Z0-9_]*/, Name::Constant, :method_call + rule /(\.|::)(\s*)([a-z_]\w*[!?]?|[*%&^`~+-\/\[<>=])/ do + groups Punctuation, Text, Name::Function + push :method_call + end + + rule /[a-zA-Z_]\w*[?!]/, Name, :expr_start + rule /[a-zA-Z_]\w*/, Name, :method_call + rule /\*\*|<>?|>=|<=|<=>|=~|={3}|!~|&&?|\|\||\.{1,3}/, + Operator, :expr_start + rule /[-+\/*%=<>&!^|~]=?/, Operator, :expr_start + rule %r<[\[({,?:\\;/]>, Punctuation, :expr_start + rule %r<[\])}]>, Punctuation + end + + state :has_heredocs do + rule /(?>? | <=>? | >= | ===? + ) + )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 /\s+/, Text + rule /\(/ do + token Punctuation + push :defexpr + push :expr_start + end + + # class << expr + rule /< 'foo.cr' + end + + it 'guesses by mimetype' do + assert_guess :mimetype => 'text/x-crystal' + assert_guess :mimetype => 'application/x-crystal' + end + + it 'guesses by source' do + assert_guess :source => '#!/usr/local/bin/crystal' + end + end +end diff --git a/spec/visual/samples/crystal b/spec/visual/samples/crystal new file mode 100644 index 0000000000..988753e96d --- /dev/null +++ b/spec/visual/samples/crystal @@ -0,0 +1,45 @@ +lib LibC + WNOHANG = 0x00000001 + + @[ReturnsTwice] + fun fork : PidT + fun getpgid(pid : PidT) : PidT + fun kill(pid : PidT, signal : Int) : Int + fun getpid : PidT + fun getppid : PidT + fun exit(status : Int) : NoReturn + + ifdef x86_64 + alias ClockT = UInt64 + else + alias ClockT = UInt32 + end + + SC_CLK_TCK = 3 + + struct Tms + utime : ClockT + stime : ClockT + cutime : ClockT + cstime : ClockT + end + + fun times(buffer : Tms*) : ClockT + fun sysconf(name : Int) : Long +end + +class Process + def self.exit(status = 0) + LibC.exit(status) + end + + def self.pid + LibC.getpid + end + + def self.getpgid(pid : Int32) + ret = LibC.getpgid(pid) + raise Errno.new(ret) if ret < 0 + ret + end +end