/** * 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')