Skip to content

Commit

Permalink
Add Solidity lexer (#760)
Browse files Browse the repository at this point in the history
This commit adds a lexer for Solidity.
  • Loading branch information
veox committed Apr 5, 2020
1 parent ba53ef8 commit 561a429
Show file tree
Hide file tree
Showing 4 changed files with 468 additions and 0 deletions.
13 changes: 13 additions & 0 deletions lib/rouge/demos/solidity
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
pragma solidity ~0.4.15;

interface IMirror {
function reflect() external payable returns(bool /* ain't I pretty?.. */);
}

contract Mirror is IMirror {
event logMessage(address indexed sender, uint256 value, uint256 gas, bytes data);

function () { // no funny stuff
revert();
}
}
185 changes: 185 additions & 0 deletions lib/rouge/lexers/solidity.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# -*- coding: utf-8 -*- #

module Rouge
module Lexers
class Solidity < RegexLexer
title "Solidity"
desc "Solidity, an Ethereum smart contract programming language"
tag 'solidity'
filenames '*.sol', '*.solidity'
mimetypes 'text/x-solidity'

# optional comment or whitespace
ws = %r((?:\s|//.*?\n|/[*].*?[*]/)+)
id = /[a-zA-Z$_][\w$_]*/

def self.detect?(text)
return true if text.start_with? 'pragma solidity'
end

# TODO: seperate by "type"
def self.keywords
@keywords ||= Set.new %w(
abstract anonymous as assembly break catch calldata constant
constructor continue contract do delete else emit enum event
external fallback for function hex if indexed interface
internal import is library mapping memory modifier new
override payable public pure pragma private receive return
returns storage struct throw try type using var view virtual
while
)
end

def self.builtins
return @builtins if @builtins

@builtins = Set.new %w(
now
false true
balance now selector super this
blockhash gasleft
assert require revert
selfdestruct suicide
call callcode delegatecall
send transfer
addmod ecrecover keccak256 mulmod sha256 sha3 ripemd160
)

# TODO: use (currently shadowed by catch-all in :statements)
abi = %w(decode encode encodePacked encodeWithSelector encodeWithSignature)
@builtins.merge( abi.map { |i| "abi.#{i}" } )
block = %w(coinbase difficulty gaslimit hash number timestamp)
@builtins.merge( block.map { |i| "block.#{i}" } )
msg = %w(data gas sender sig value)
@builtins.merge( msg.map { |i| "msg.#{i}" } )
tx = %w(gasprice origin)
@builtins.merge( tx.map { |i| "tx.#{i}" } )
end

def self.constants
@constants ||= Set.new %w(
wei finney szabo ether
seconds minutes hours days weeks years
)
end

def self.keywords_type
@keywords_type ||= Set.new %w(
address bool byte bytes int string uint
)
end

def self.reserved
@reserved ||= Set.new %w(
alias after apply auto case copyof default define final fixed
immutable implements in inline let macro match mutable null of
partial promise reference relocatable sealed sizeof static
supports switch typedef typeof ufixed unchecked
)
end

start { push :bol }

state :expr_bol do
mixin :inline_whitespace

rule(//) { pop! }
end

# :expr_bol is the same as :bol but without labels, since
# labels can only appear at the beginning of a statement.
state :bol do
mixin :expr_bol
end

# TODO: natspec in comments
state :inline_whitespace do
rule %r/[ \t\r]+/, Text
rule %r/\\\n/, Text # line continuation
rule %r(/\*), Comment::Multiline, :comment_multi
end

state :whitespace do
rule %r/\n+/m, Text, :bol
rule %r(//(\\.|.)*?\n), Comment::Single, :bol
mixin :inline_whitespace
end

state :expr_whitespace do
rule %r/\n+/m, Text, :expr_bol
mixin :whitespace
end

state :statements do
mixin :whitespace
rule %r/(hex)?\"/, Str, :string_double
rule %r/(hex)?\'/, Str, :string_single
rule %r('(\\.|\\[0-7]{1,3}|\\x[a-f0-9]{1,2}|[^\\'\n])')i, Str::Char
rule %r/\d\d*\.\d+([eE]\d+)?/i, Num::Float
rule %r/0x[0-9a-f]+/i, Num::Hex
rule %r/\d+([eE]\d+)?/i, Num::Integer
rule %r(\*/), Error
rule %r([~!%^&*+=\|?:<>/-]), Operator
rule %r/[()\[\],.]/, Punctuation
rule %r/u?fixed\d+x\d+/, Keyword::Reserved
rule %r/bytes\d+/, Keyword::Type
rule %r/u?int\d+/, Keyword::Type
rule id do |m|
name = m[0]

if self.class.keywords.include? name
token Keyword
elsif self.class.builtins.include? name
token Name::Builtin
elsif self.class.constants.include? name
token Keyword::Constant
elsif self.class.keywords_type.include? name
token Keyword::Type
elsif self.class.reserved.include? name
token Keyword::Reserved
else
token Name
end
end
end

state :root do
mixin :expr_whitespace
rule(//) { push :statement }
# TODO: function declarations
end

state :statement do
rule %r/;/, Punctuation, :pop!
mixin :expr_whitespace
mixin :statements
rule %r/[{}]/, Punctuation
end

state :string_common do
rule %r/\\(u[a-fA-F0-9]{4}|x..|[^x])/, Str::Escape
rule %r/[^\\\"\'\n]+/, Str
rule %r/\\\n/, Str # line continuation
rule %r/\\/, Str # stray backslash
end

state :string_double do
mixin :string_common
rule %r/\"/, Str, :pop!
rule %r/\'/, Str
end

state :string_single do
mixin :string_common
rule %r/\'/, Str, :pop!
rule %r/\"/, Str
end

state :comment_multi do
rule %r(\*/), Comment::Multiline, :pop!
rule %r([^*/]+), Comment::Multiline
rule %r([*/]), Comment::Multiline
end
end
end
end
22 changes: 22 additions & 0 deletions spec/lexers/solidity_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*- #

describe Rouge::Lexers::Solidity do
let(:subject) { Rouge::Lexers::Solidity.new }

describe 'guessing' do
include Support::Guessing

it 'guesses by filename' do
assert_guess :filename => 'foo.sol'
assert_guess :filename => 'foo.solidity'
end

it 'guesses by mimetype' do
assert_guess :mimetype => 'text/x-solidity'
end

it 'guesses by source' do
assert_guess :source => 'pragma solidity'
end
end
end
Loading

0 comments on commit 561a429

Please sign in to comment.