mirror of https://github.com/sgoudham/carbon.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
583 lines
15 KiB
JavaScript
583 lines
15 KiB
JavaScript
/**
|
|
* codemirror-solidity by alincode — https://github.com/alincode/codemirror-solidity
|
|
* Distributed under MIT
|
|
*/
|
|
'use strict'
|
|
const CodeMirror = require('codemirror')
|
|
|
|
CodeMirror.defineMode('solidity', function (config) {
|
|
let indentUnit = config.indentUnit
|
|
|
|
// let functionKeyword = 'function'
|
|
// let functionNameKeyword = 'Name'
|
|
// let leftBracketSign = '('
|
|
// let rightBracketSign = ')'
|
|
// let functionVariableName = 'variable'
|
|
// let keyWordContract = 'contract'
|
|
|
|
let keywords = {
|
|
pragma: true,
|
|
solidity: true,
|
|
import: true,
|
|
as: true,
|
|
from: true,
|
|
contract: true,
|
|
constructor: true,
|
|
is: true,
|
|
function: true,
|
|
modifier: true,
|
|
// modifiers
|
|
pure: true,
|
|
view: true,
|
|
payable: true,
|
|
constant: true,
|
|
anonymous: true,
|
|
indexed: true,
|
|
returns: true,
|
|
return: true,
|
|
event: true,
|
|
struct: true,
|
|
mapping: true,
|
|
interface: true,
|
|
using: true,
|
|
library: true,
|
|
storage: true,
|
|
memory: true,
|
|
calldata: true,
|
|
public: true,
|
|
private: true,
|
|
external: true,
|
|
internal: true,
|
|
emit: true,
|
|
assembly: true,
|
|
abstract: true,
|
|
after: true,
|
|
catch: true,
|
|
final: true,
|
|
in: true,
|
|
inline: true,
|
|
let: true,
|
|
match: true,
|
|
null: true,
|
|
of: true,
|
|
relocatable: true,
|
|
static: true,
|
|
try: true,
|
|
typeof: true,
|
|
var: true,
|
|
}
|
|
|
|
let keywordsSpecial = {
|
|
pragma: true,
|
|
returns: true,
|
|
address: true,
|
|
contract: true,
|
|
function: true,
|
|
struct: true,
|
|
}
|
|
|
|
let keywordsEtherUnit = {
|
|
wei: true,
|
|
szabo: true,
|
|
finney: true,
|
|
ether: true,
|
|
}
|
|
let keywordsTimeUnit = {
|
|
seconds: true,
|
|
minutes: true,
|
|
hours: true,
|
|
days: true,
|
|
weeks: true,
|
|
}
|
|
let keywordsBlockAndTransactionProperties = {
|
|
['block']: ['coinbase', 'difficulty', 'gaslimit', 'number', 'timestamp'],
|
|
['msg']: ['data', 'sender', 'sig', 'value'],
|
|
['tx']: ['gasprice', 'origin'],
|
|
}
|
|
let keywordsMoreBlockAndTransactionProperties = {
|
|
now: true,
|
|
gasleft: true,
|
|
blockhash: true,
|
|
}
|
|
let keywordsErrorHandling = {
|
|
assert: true,
|
|
require: true,
|
|
revert: true,
|
|
throw: true,
|
|
}
|
|
let keywordsMathematicalAndCryptographicFuctions = {
|
|
addmod: true,
|
|
mulmod: true,
|
|
keccak256: true,
|
|
sha256: true,
|
|
ripemd160: true,
|
|
ecrecover: true,
|
|
}
|
|
let keywordsContractRelated = {
|
|
this: true,
|
|
selfdestruct: true,
|
|
super: true,
|
|
}
|
|
let keywordsTypeInformation = { type: true }
|
|
let keywordsContractList = {}
|
|
|
|
let keywordsControlStructures = {
|
|
if: true,
|
|
else: true,
|
|
while: true,
|
|
do: true,
|
|
for: true,
|
|
break: true,
|
|
continue: true,
|
|
switch: true,
|
|
case: true,
|
|
default: true,
|
|
}
|
|
|
|
let keywordsValueTypes = {
|
|
bool: true,
|
|
byte: true,
|
|
string: true,
|
|
enum: true,
|
|
address: true,
|
|
}
|
|
|
|
let keywordsV0505NewReserve = {
|
|
alias: true,
|
|
apply: true,
|
|
auto: true,
|
|
copyof: true,
|
|
define: true,
|
|
immutable: true,
|
|
implements: true,
|
|
macro: true,
|
|
mutable: true,
|
|
override: true,
|
|
partial: true,
|
|
promise: true,
|
|
reference: true,
|
|
sealed: true,
|
|
sizeof: true,
|
|
supports: true,
|
|
typedef: true,
|
|
unchecked: true,
|
|
}
|
|
|
|
let keywordsAbiEncodeDecodeFunctions = {
|
|
['abi']: ['decode', 'encodePacked', 'encodeWithSelector', 'encodeWithSignature', 'encode'],
|
|
}
|
|
|
|
let keywordsMembersOfAddressType = [
|
|
'transfer',
|
|
'send',
|
|
'balance',
|
|
'call',
|
|
'delegatecall',
|
|
'staticcall',
|
|
]
|
|
|
|
let natSpecTags = ['title', 'author', 'notice', 'dev', 'param', 'return']
|
|
|
|
// let functionStructureStage = [{
|
|
// function: ['function', 'returns']
|
|
// },
|
|
// leftBracketSign,
|
|
// parameterName,
|
|
// parameterValue,
|
|
// rightBracketSign,
|
|
// ];
|
|
|
|
let atoms = {
|
|
delete: true,
|
|
new: true,
|
|
true: true,
|
|
false: true,
|
|
// "iota": true, "nil": true, "append": true,
|
|
// "cap": true, "close": true, "complex": true, "copy": true, "imag": true,
|
|
// "make": true, "panic": true, "print": true,
|
|
// "println": true, "real": true, "recover": true
|
|
}
|
|
|
|
let isOperatorChar = /[+\-*&^%:=<>!|\/~]/
|
|
let isNegativeChar = /[-]/
|
|
|
|
let curPunc
|
|
|
|
function tokenBase(stream, state) {
|
|
let ch = stream.next()
|
|
|
|
if (ch == '"' || ch == "'" || ch == '`') {
|
|
state.tokenize = tokenString(ch)
|
|
return state.tokenize(stream, state)
|
|
}
|
|
|
|
if (isVersion(stream, state)) return 'version'
|
|
|
|
if (
|
|
ch == '.' &&
|
|
keywordsMembersOfAddressType.some(function (item) {
|
|
return stream.match(`${item}`)
|
|
})
|
|
)
|
|
return 'addressFunction'
|
|
|
|
if (isNumber(ch, stream)) return 'number'
|
|
|
|
if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
|
|
return updateGarmmer(ch, state)
|
|
}
|
|
|
|
if (ch == '/') {
|
|
if (stream.eat('*')) {
|
|
state.tokenize = tokenComment
|
|
return tokenComment(stream, state)
|
|
}
|
|
if (stream.match(/\/{2}/)) {
|
|
while ((ch = stream.next())) {
|
|
if (ch == '@') {
|
|
stream.backUp(1)
|
|
state.grammar = 'doc'
|
|
break
|
|
}
|
|
}
|
|
return 'doc'
|
|
}
|
|
|
|
if (stream.eat('/')) {
|
|
stream.skipToEnd()
|
|
return 'comment'
|
|
}
|
|
}
|
|
|
|
if (isNegativeChar.test(ch)) {
|
|
if (isNumber(stream.peek(), stream)) return 'number'
|
|
return 'operator'
|
|
}
|
|
|
|
if (isOperatorChar.test(ch)) {
|
|
stream.eatWhile(isOperatorChar)
|
|
return 'operator'
|
|
}
|
|
stream.eatWhile(/[\w\$_\xa1-\uffff]/)
|
|
|
|
let cur = stream.current()
|
|
|
|
if (state.grammar == 'doc') {
|
|
if (
|
|
natSpecTags.some(function (item) {
|
|
return cur == `@${item}`
|
|
})
|
|
)
|
|
return 'docReserve'
|
|
return 'doc'
|
|
}
|
|
|
|
if (cur === 'solidity' && state.lastToken == 'pragma') {
|
|
state.lastToken = state.lastToken + ' ' + cur
|
|
}
|
|
|
|
if (keywords.propertyIsEnumerable(cur)) {
|
|
if (cur == 'case' || cur == 'default') curPunc = 'case'
|
|
if (keywordsSpecial.propertyIsEnumerable(cur)) state.lastToken = cur
|
|
//if (cur == 'function' && state.para == 'parameterMode')
|
|
return 'keyword'
|
|
}
|
|
|
|
if (keywordsEtherUnit.propertyIsEnumerable(cur)) return 'etherUnit'
|
|
if (keywordsContractRelated.propertyIsEnumerable(cur)) return 'contractRelated'
|
|
if (
|
|
keywordsControlStructures.propertyIsEnumerable(cur) ||
|
|
keywordsTypeInformation.propertyIsEnumerable(cur) ||
|
|
keywordsV0505NewReserve.propertyIsEnumerable(cur)
|
|
)
|
|
return 'keyword'
|
|
if (
|
|
keywordsValueTypes.propertyIsEnumerable(cur) ||
|
|
keywordsTimeUnit.propertyIsEnumerable(cur) ||
|
|
isValidInteger(cur) ||
|
|
isValidBytes(cur) ||
|
|
isValidFixed(cur)
|
|
) {
|
|
state.lastToken = state.lastToken + 'variable'
|
|
return 'keyword'
|
|
}
|
|
|
|
if (atoms.propertyIsEnumerable(cur)) return 'atom'
|
|
if (keywordsErrorHandling.propertyIsEnumerable(cur)) return 'errorHandling'
|
|
if (keywordsMathematicalAndCryptographicFuctions.propertyIsEnumerable(cur))
|
|
return 'mathematicalAndCryptographic'
|
|
|
|
if (
|
|
keywordsMoreBlockAndTransactionProperties.propertyIsEnumerable(cur) ||
|
|
(keywordsBlockAndTransactionProperties.hasOwnProperty(cur) &&
|
|
keywordsBlockAndTransactionProperties[cur].some(function (item) {
|
|
return stream.match(`.${item}`)
|
|
}))
|
|
)
|
|
return 'variable-2'
|
|
|
|
if (
|
|
keywordsAbiEncodeDecodeFunctions.hasOwnProperty(cur) &&
|
|
keywordsAbiEncodeDecodeFunctions[cur].some(function (item) {
|
|
return stream.match(`.${item}`)
|
|
})
|
|
)
|
|
return 'abi'
|
|
|
|
let style = updateHexLiterals(cur, stream)
|
|
if (style != null) return style
|
|
|
|
if (
|
|
(state.lastToken == 'functionName(' || state.lastToken == 'returns(') &&
|
|
keywordsContractList.propertyIsEnumerable(cur)
|
|
) {
|
|
state.lastToken += 'variable'
|
|
return 'variable'
|
|
}
|
|
if (state.lastToken == 'function') {
|
|
state.lastToken = 'functionName'
|
|
if (state.para == null) {
|
|
state.grammar = 'function'
|
|
state.para = ''
|
|
}
|
|
//state.parasMode = isNaN(state.parasMode) ? 1 : state.functionLayerCount++;
|
|
state.para += 'functionName'
|
|
return 'functionName'
|
|
}
|
|
|
|
if (state.lastToken == 'functionName(variable') {
|
|
state.lastToken = 'functionName('
|
|
return 'parameterValue'
|
|
}
|
|
|
|
if (state.lastToken == 'returns(variable') {
|
|
state.lastToken = 'returns('
|
|
return 'parameterValue'
|
|
}
|
|
|
|
if (state.lastToken == 'address' && cur == 'payable') {
|
|
state.lastToken = 'address payable'
|
|
}
|
|
if (state.lastToken == 'contract' || state.lastToken == 'struct') {
|
|
keywordsContractList[cur] = true
|
|
state.lastToken = null
|
|
}
|
|
if (state.grammar == 'function') {
|
|
return 'parameterValue'
|
|
}
|
|
|
|
return 'variable'
|
|
}
|
|
|
|
function tokenString(quote) {
|
|
return function (stream, state) {
|
|
let escaped = false,
|
|
next,
|
|
end = false
|
|
while ((next = stream.next()) != null) {
|
|
if (next == quote && !escaped) {
|
|
end = true
|
|
break
|
|
}
|
|
escaped = !escaped && quote != '`' && next == '\\'
|
|
}
|
|
if (end || !(escaped || quote == '`')) state.tokenize = tokenBase
|
|
return 'string'
|
|
}
|
|
}
|
|
|
|
function tokenComment(stream, state) {
|
|
let maybeEnd = false,
|
|
ch
|
|
while ((ch = stream.next())) {
|
|
if (ch == '/' && maybeEnd) {
|
|
state.tokenize = tokenBase
|
|
break
|
|
}
|
|
maybeEnd = ch == '*'
|
|
}
|
|
return 'comment'
|
|
}
|
|
|
|
function isVersion(stream, state) {
|
|
if (state.lastToken == 'pragma solidity') {
|
|
state.lastToken = null
|
|
return (
|
|
!state.startOfLine &&
|
|
(stream.match(/[\^{0}][0-9\.]+/) ||
|
|
stream.match(/[\>\=]+?[\s]*[0-9\.]+[\s]*[\<]?[\s]*[0-9\.]+/))
|
|
)
|
|
}
|
|
}
|
|
|
|
function isNumber(ch, stream) {
|
|
if (/[\d\.]/.test(ch)) {
|
|
if (ch == '.') {
|
|
stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/)
|
|
} else if (ch == '0') {
|
|
stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/)
|
|
} else {
|
|
stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/)
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
function isValidInteger(token) {
|
|
if (token.match(/^[u]?int/)) {
|
|
if (token.indexOf('t') + 1 == token.length) return true
|
|
let numberPart = token.substr(token.indexOf('t') + 1, token.length)
|
|
return numberPart % 8 === 0 && numberPart <= 256
|
|
}
|
|
}
|
|
|
|
function isValidBytes(token) {
|
|
if (token.match(/^bytes/)) {
|
|
if (token.indexOf('s') + 1 == token.length) return true
|
|
let bytesPart = token.substr(token.indexOf('s') + 1, token.length)
|
|
return bytesPart <= 32
|
|
}
|
|
}
|
|
|
|
function isValidFixed(token) {
|
|
if (token.match(/^[u]?fixed([0-9]+x[0-9]+)?/)) {
|
|
if (token.indexOf('d') + 1 == token.length) return true
|
|
let numberPart = token.substr(token.indexOf('d') + 1, token.length).split('x')
|
|
return numberPart[0] % 8 === 0 && numberPart[0] <= 256 && numberPart[1] <= 80
|
|
}
|
|
}
|
|
|
|
function updateHexLiterals(token, stream) {
|
|
if (token.match(/^hex/) && stream.peek() == '"') {
|
|
let maybeEnd = false,
|
|
ch,
|
|
hexValue = '',
|
|
stringAfterHex = ''
|
|
while ((ch = stream.next())) {
|
|
stringAfterHex += ch
|
|
if (ch == '"' && maybeEnd) {
|
|
hexValue = stringAfterHex.substring(1, stringAfterHex.length - 1)
|
|
if (hexValue.match(/^[0-9a-fA-F]+$/)) {
|
|
return 'number'
|
|
} else {
|
|
stream.backUp(stringAfterHex.length)
|
|
}
|
|
break
|
|
}
|
|
maybeEnd = maybeEnd || ch == '"'
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateGarmmer(ch, state) {
|
|
if (ch == ',' && state.para == 'functionName(variable') {
|
|
state.para = 'functionName('
|
|
}
|
|
if (state.para != null && state.para.startsWith('functionName')) {
|
|
if (ch == ')') {
|
|
if (state.para.endsWith('(')) {
|
|
state.para = state.para.substr(0, state.para.length - 1)
|
|
if (state.para == 'functionName') state.grammar = ''
|
|
}
|
|
} else if (ch == '(') {
|
|
state.para += ch
|
|
}
|
|
}
|
|
|
|
if (ch == '(' && state.lastToken == 'functionName') {
|
|
state.lastToken += ch
|
|
} else if (ch == ')' && state.lastToken == 'functionName(') {
|
|
state.lastToken = null
|
|
} else if (ch == '(' && state.lastToken == 'returns') {
|
|
state.lastToken += ch
|
|
} else if (
|
|
ch == ')' &&
|
|
(state.lastToken == 'returns(' || state.lastToken == 'returns(variable')
|
|
) {
|
|
state.lastToken = null
|
|
}
|
|
if (ch == '(' && state.lastToken == 'address') {
|
|
state.lastToken += ch
|
|
}
|
|
curPunc = ch
|
|
return null
|
|
}
|
|
|
|
function Context(indented, column, type, align, prev) {
|
|
this.indented = indented
|
|
this.column = column
|
|
this.type = type
|
|
this.align = align
|
|
this.prev = prev
|
|
}
|
|
function pushContext(state, col, type) {
|
|
return (state.context = new Context(state.indented, col, type, null, state.context))
|
|
}
|
|
function popContext(state) {
|
|
if (!state.context.prev) return
|
|
let t = state.context.type
|
|
if (t == ')' || t == ']' || t == '}') state.indented = state.context.indented
|
|
return (state.context = state.context.prev)
|
|
}
|
|
|
|
// Interface
|
|
return {
|
|
startState: function (basecolumn) {
|
|
return {
|
|
tokenize: null,
|
|
context: new Context((basecolumn || 0) - indentUnit, 0, 'top', false),
|
|
indented: 0,
|
|
startOfLine: true,
|
|
}
|
|
},
|
|
|
|
token: function (stream, state) {
|
|
let ctx = state.context
|
|
if (stream.sol()) {
|
|
if (ctx.align == null) ctx.align = false
|
|
state.indented = stream.indentation()
|
|
state.startOfLine = true
|
|
if (ctx.type == 'case') ctx.type = '}'
|
|
if (state.grammar == 'doc') state.grammar = null
|
|
}
|
|
if (stream.eatSpace()) return null
|
|
curPunc = null
|
|
let style = (state.tokenize || tokenBase)(stream, state)
|
|
|
|
if (style == 'comment') return style
|
|
if (ctx.align == null) ctx.align = true
|
|
|
|
if (curPunc == '{') pushContext(state, stream.column(), '}')
|
|
else if (curPunc == '[') pushContext(state, stream.column(), ']')
|
|
else if (curPunc == '(') pushContext(state, stream.column(), ')')
|
|
else if (curPunc == 'case') ctx.type = 'case'
|
|
else if (curPunc == '}' && ctx.type == '}') popContext(state)
|
|
else if (curPunc == ctx.type) popContext(state)
|
|
state.startOfLine = false
|
|
return style
|
|
},
|
|
|
|
indent: function (state, textAfter) {
|
|
if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass
|
|
let ctx = state.context,
|
|
firstChar = textAfter && textAfter.charAt(0)
|
|
if (ctx.type == 'case' && /^(?:case|default)\b/.test(textAfter)) {
|
|
state.context.type = '}'
|
|
return ctx.indented
|
|
}
|
|
let closing = firstChar == ctx.type
|
|
if (ctx.align) return ctx.column + (closing ? 0 : 1)
|
|
else return ctx.indented + (closing ? 0 : indentUnit)
|
|
},
|
|
|
|
electricChars: '{}):',
|
|
closeBrackets: '()[]{}\'\'""``',
|
|
fold: 'brace',
|
|
blockCommentStart: '/*',
|
|
blockCommentEnd: '*/',
|
|
lineComment: '//',
|
|
}
|
|
})
|
|
|
|
CodeMirror.defineMIME('text/x-solidity', 'solidity')
|