261 lines
8.4 KiB
Ruby
261 lines
8.4 KiB
Ruby
# -*- coding: utf-8 -*- #
|
||
# frozen_string_literal: true
|
||
|
||
module Rouge
|
||
module Lexers
|
||
class Rust < RegexLexer
|
||
title "Rust"
|
||
desc 'The Rust programming language (rust-lang.org)'
|
||
tag 'rust'
|
||
aliases 'rs',
|
||
# So that directives from https://github.com/budziq/rust-skeptic
|
||
# do not prevent highlighting.
|
||
'rust,no_run', 'rs,no_run',
|
||
'rust,ignore', 'rs,ignore',
|
||
'rust,should_panic', 'rs,should_panic'
|
||
filenames '*.rs'
|
||
mimetypes 'text/x-rust'
|
||
|
||
def self.detect?(text)
|
||
return true if text.shebang? 'rustc'
|
||
end
|
||
|
||
def self.keywords
|
||
@keywords ||= %w(
|
||
as async await break const continue crate dyn else enum extern false
|
||
fn for if impl in let log loop match mod move mut pub ref return self
|
||
Self static struct super trait true type unsafe use where while
|
||
abstract become box do final macro
|
||
override priv typeof unsized virtual
|
||
yield try
|
||
union
|
||
)
|
||
end
|
||
|
||
def self.builtins
|
||
@builtins ||= Set.new %w(
|
||
Add BitAnd BitOr BitXor bool c_char c_double c_float char
|
||
c_int clock_t c_long c_longlong Copy c_schar c_short
|
||
c_uchar c_uint c_ulong c_ulonglong c_ushort c_void dev_t DIR
|
||
dirent Div Eq Err f32 f64 FILE float fpos_t
|
||
i16 i32 i64 i8 isize Index ino_t int intptr_t mode_t Mul
|
||
Neg None off_t Ok Option Ord Owned pid_t ptrdiff_t
|
||
Send Shl Shr size_t Some ssize_t str Sub time_t
|
||
u16 u32 u64 u8 usize uint uintptr_t
|
||
Box Vec String Rc Arc
|
||
u128 i128 Result Sync Pin Unpin Sized Drop drop Fn FnMut FnOnce
|
||
Clone PartialEq PartialOrd AsMut AsRef From Into Default
|
||
DoubleEndedIterator ExactSizeIterator Extend IntoIterator Iterator
|
||
FromIterator ToOwned ToString TryFrom TryInto
|
||
)
|
||
end
|
||
|
||
def macro_closed?
|
||
@macro_delims.values.all?(&:zero?)
|
||
end
|
||
|
||
start {
|
||
@macro_delims = { ']' => 0, ')' => 0, '}' => 0 }
|
||
push :bol
|
||
}
|
||
|
||
delim_map = { '[' => ']', '(' => ')', '{' => '}' }
|
||
|
||
id = /[\p{XID_Start}_]\p{XID_Continue}*/
|
||
hex = /[0-9a-f]/i
|
||
escapes = %r(
|
||
\\ ([nrt'"\\0] | x#{hex}{2} | u\{(#{hex}_*){1,6}\})
|
||
)x
|
||
size = /8|16|32|64|128|size/
|
||
|
||
# Although not officially part of Rust, the rustdoc tool allows code in
|
||
# comments to begin with `#`. Code like this will be evaluated but not
|
||
# included in the HTML output produced by rustdoc. So that code intended
|
||
# for these comments can be higlighted with Rouge, the Rust lexer needs
|
||
# to check if the beginning of the line begins with a `# `.
|
||
state :bol do
|
||
mixin :whitespace
|
||
rule %r/#\s[^\n]*/, Comment::Special
|
||
rule(//) { pop! }
|
||
end
|
||
|
||
state :attribute do
|
||
mixin :whitespace
|
||
mixin :has_literals
|
||
rule %r/[(,)=:]/, Name::Decorator
|
||
rule %r/\]/, Name::Decorator, :pop!
|
||
rule id, Name::Decorator
|
||
end
|
||
|
||
state :whitespace do
|
||
rule %r/\s+/, Text
|
||
mixin :comments
|
||
end
|
||
|
||
state :comments do
|
||
# Only 3 slashes are doc comments, `////` and beyond become normal
|
||
# comments again (for some reason), so match this before the
|
||
# doc line comments rather than figure out a
|
||
rule %r(////+[^\n]*), Comment::Single
|
||
# doc line comments — either inner (`//!`), or outer (`///`).
|
||
rule %r(//[/!][^\n]*), Comment::Doc
|
||
# otherwise, `//` is just a plain line comme
|
||
rule %r(//[^\n]*), Comment::Single
|
||
# /**/ and /***/ are self-closing block comments, not doc. Because this
|
||
# is self-closing, it doesn't enter the states for nested comments
|
||
rule %r(/\*\*\*?/), Comment::Multiline
|
||
# 3+ stars and it's a normal non-doc block comment.
|
||
rule %r(/\*\*\*+), Comment::Multiline, :nested_plain_block
|
||
# `/*!` and `/**` begin doc comments. These nest and can have internal
|
||
# block/doc comments, but they're still part of the documentation
|
||
# inside.
|
||
rule %r(/[*][*!]), Comment::Doc, :nested_doc_block
|
||
# any other /* is a plain multiline comment
|
||
rule %r(/[*]), Comment::Multiline, :nested_plain_block
|
||
end
|
||
|
||
# Multiline/block comments fully nest. This is true for ones that are
|
||
# marked as documentation too. The behavior here is:
|
||
#
|
||
# - Anything inside a block doc comment is still included in the
|
||
# documentation, even if it's a nested non-doc block comment. For
|
||
# example: `/** /* still docs */ */`
|
||
# - Anything inside of a block non-doc comment is still just a normal
|
||
# comment, even if it's a nested block documentation comment. For
|
||
# example: `/* /** not docs */ */`
|
||
#
|
||
# This basically means: if (on the outermost level) the comment starts as
|
||
# one kind of block comment (either doc/non-doc), then everything inside
|
||
# of it, including nested block comments of the opposite type, needs to
|
||
# stay that type.
|
||
#
|
||
# Also note that single line comments do nothing anywhere inside of block
|
||
# comments, thankfully.
|
||
#
|
||
# We just define this as two states, because this seems easier than
|
||
# tracking it with instance vars.
|
||
[
|
||
[:nested_plain_block, Comment::Multiline],
|
||
[:nested_doc_block, Comment::Doc]
|
||
].each do |state_name, comment_token|
|
||
state state_name do
|
||
rule %r(\*/), comment_token, :pop!
|
||
rule %r(/\*), comment_token, state_name
|
||
# We only want to eat at most one `[*/]` at a time,
|
||
# but we can skip past non-`[*/]` in bulk.
|
||
rule %r([^*/]+|[*/]), comment_token
|
||
end
|
||
end
|
||
|
||
state :root do
|
||
rule %r/\n/, Text, :bol
|
||
mixin :whitespace
|
||
rule %r/#!?\[/, Name::Decorator, :attribute
|
||
rule %r/\b(?:#{Rust.keywords.join('|')})\b/, Keyword
|
||
mixin :has_literals
|
||
|
||
rule %r([=-]>), Keyword
|
||
rule %r(<->), Keyword
|
||
rule %r/[()\[\]{}|,:;]/, Punctuation
|
||
rule %r/[*\/!@~&+%^<>=\?-]|\.{2,3}/, Operator
|
||
|
||
rule %r/([.]\s*)?#{id}(?=\s*[(])/m, Name::Function
|
||
rule %r/[.]\s*await\b/, Keyword
|
||
rule %r/[.]\s*#{id}/, Name::Property
|
||
rule %r/[.]\s*\d+/, Name::Attribute
|
||
rule %r/(#{id})(::)/m do
|
||
groups Name::Namespace, Punctuation
|
||
end
|
||
|
||
# macros
|
||
rule %r/\bmacro_rules!/, Name::Decorator, :macro_rules
|
||
rule %r/#{id}!/, Name::Decorator, :macro
|
||
|
||
rule %r/'static\b/, Keyword
|
||
rule %r/'#{id}/, Name::Variable
|
||
rule %r/#{id}/ do |m|
|
||
name = m[0]
|
||
if self.class.builtins.include? name
|
||
token Name::Builtin
|
||
else
|
||
token Name
|
||
end
|
||
end
|
||
end
|
||
|
||
state :macro do
|
||
mixin :has_literals
|
||
|
||
rule %r/[\[{(]/ do |m|
|
||
@macro_delims[delim_map[m[0]]] += 1
|
||
puts " macro_delims: #{@macro_delims.inspect}" if @debug
|
||
token Punctuation
|
||
end
|
||
|
||
rule %r/[\]})]/ do |m|
|
||
@macro_delims[m[0]] -= 1
|
||
puts " macro_delims: #{@macro_delims.inspect}" if @debug
|
||
pop! if macro_closed?
|
||
token Punctuation
|
||
end
|
||
|
||
# same as the rule in root, but don't push another macro state
|
||
rule %r/#{id}!/, Name::Decorator
|
||
mixin :root
|
||
|
||
# No syntax errors in macros
|
||
rule %r/./, Text
|
||
end
|
||
|
||
state :macro_rules do
|
||
rule %r/[$]#{id}(:#{id})?/, Name::Variable
|
||
rule %r/[$]/, Name::Variable
|
||
|
||
mixin :macro
|
||
end
|
||
|
||
state :has_literals do
|
||
# constants
|
||
rule %r/\b(?:true|false)\b/, Keyword::Constant
|
||
|
||
# characters/bytes
|
||
rule %r(
|
||
b?' (?: #{escapes} | [^\\] ) '
|
||
)x, Str::Char
|
||
|
||
rule %r/b?"/, Str, :string
|
||
rule %r/b?r(#*)".*?"\1/m, Str
|
||
|
||
# numbers
|
||
dot = /[.][0-9][0-9_]*/
|
||
exp = /[eE][-+]?[0-9_]+/
|
||
flt = /f32|f64/
|
||
|
||
rule %r(
|
||
[0-9][0-9_]*
|
||
(#{dot} #{exp}? #{flt}?
|
||
|#{dot}? #{exp} #{flt}?
|
||
|#{dot}? #{exp}? #{flt}
|
||
|[.](?![._\p{XID_Start}])
|
||
)
|
||
)x, Num::Float
|
||
|
||
rule %r(
|
||
( 0b[10_]+
|
||
| 0x[0-9a-fA-F_]+
|
||
| 0o[0-7_]+
|
||
| [0-9][0-9_]*
|
||
) (u#{size}?|i#{size})?
|
||
)x, Num::Integer
|
||
|
||
end
|
||
|
||
state :string do
|
||
rule %r/"/, Str, :pop!
|
||
rule escapes, Str::Escape
|
||
rule %r/[^"\\]+/m, Str
|
||
end
|
||
end
|
||
end
|
||
end
|