const CodeMirror = require('codemirror') CodeMirror.defineMode('nimrod', function(conf, parserConf) { var ERRORCLASS = 'error' function wordRegexp(words) { return new RegExp('^((' + words.join(')|(') + '))\\b') } var operators = new RegExp('\\=\\+\\-\\*\\/\\<\\>\\@\\$\\~\\&\\%\\|\\!\\?\\^\\:\\\\') var identifiers = new RegExp('^[_A-Za-z][_A-Za-z0-9]*') var commonkeywords = [ 'addr', 'asm', 'atomic', 'bind', 'block', 'break', 'case', 'cast', 'const', 'continue', 'converter', 'discard', 'distinct', 'do', 'elif', 'else', 'end', 'enum', 'except', 'export', 'finally', 'for', 'from', 'generic', 'if', 'import', 'include', 'interface', 'iterator', 'lambda', 'let', 'macro', 'method', 'mixin', 'nil', 'object', 'out', 'proc', 'ptr', 'raise', 'ref', 'return', 'shared', 'static', 'template', 'try', 'tuple', 'type', 'using', 'var', 'when', 'while', 'with', 'without', 'yield', // keyword operators 'shl', 'shr', 'and', 'or', 'xor', 'not', 'div', 'mod', 'is', 'isnot', 'in', 'as', 'of' ] var commonBuiltins = [ 'int', 'int8', 'int16', 'int32', 'int64', 'uint', 'uint8', 'uint16', 'uint32', 'uint64', 'float', 'float32', 'float64', 'bool', 'char', 'string', 'cstring', 'pointer', 'range', 'array', 'openarray', 'seq', 'set', 'Byte', 'Natural', 'Positive', 'TObject', 'PObject', 'Conversion', 'TResult', 'TAddress', 'BiggestInt', 'BiggestFloat', 'cchar', 'cschar', 'cshort', 'cint', 'csize', 'cuchar', 'cushort', 'clong', 'clonglong', 'cfloat', 'cdouble', 'clongdouble', 'cuint', 'culong', 'culonglong', 'cchar', 'cstringArray', 'TEndian', 'PFloat32', 'PFloat64', 'PInt64', 'PInt32', 'TGC_Strategy', 'TFile', 'TFileMode', 'TFileHandle', 'isMainModule', 'CompileDate', 'CompileTime', 'NimrodVersion', 'NimrodMajor', 'NimrodMinor', 'NimrodPatch', 'cpuEndian', 'hostOS', 'hostCPU', 'inf', 'neginf', 'nan', 'QuitSuccess', 'QuitFailure', 'dbgLineHook', 'stdin', 'stdout', 'stderr', 'defined', 'new', 'high', 'low', 'sizeof', 'succ', 'pred', 'inc', 'dec', 'newSeq', 'len', 'incl', 'excl', 'card', 'ord', 'chr', 'ze', 'ze64', 'toU8', 'toU16', 'toU32', 'abs', 'min', 'max', 'add', 'repr', 'contains', 'toFloat', 'toBiggestFloat', 'toInt', 'toBiggestInt', 'addQuitProc', 'copy', 'setLen', 'newString', 'zeroMem', 'copyMem', 'moveMem', 'equalMem', 'alloc', 'alloc0', 'realloc', 'dealloc', 'setLen', 'assert', 'swap', 'getRefcount', 'getCurrentException', 'Msg', 'getOccupiedMem', 'getFreeMem', 'getTotalMem', 'isNil', 'seqToPtr', 'find', 'pop', 'GC_disable', 'GC_enable', 'GC_fullCollect', 'GC_setStrategy', 'GC_enableMarkAnd', 'Sweep', 'GC_disableMarkAnd', 'Sweep', 'GC_getStatistics', 'GC_ref', 'GC_ref', 'GC_ref', 'GC_unref', 'GC_unref', 'GC_unref', 'quit', 'OpenFile', 'OpenFile', 'CloseFile', 'EndOfFile', 'readChar', 'FlushFile', 'readFile', 'write', 'readLine', 'writeln', 'writeln', 'getFileSize', 'ReadBytes', 'ReadChars', 'readBuffer', 'writeBytes', 'writeChars', 'writeBuffer', 'setFilePos', 'getFilePos', 'fileHandle', 'countdown', 'countup', 'items', 'lines', 'true', 'false', // exceptions 'E_Base', 'EAsynch', 'ESynch', 'ESystem', 'EIO', 'EOS', 'ERessourceExhausted', 'EArithmetic', 'EDivByZero', 'EOverflow', 'EAccessViolation', 'EAssertionFailed', 'EControlC', 'EInvalidValue', 'EOutOfMemory', 'EInvalidIndex', 'EInvalidField', 'EOutOfRange', 'EStackOverflow', 'ENoExceptionToReraise', 'EInvalidObjectAssignment', 'EInvalidObject', 'EInvalidLibrary', 'EInvalidKey', 'EInvalidObjectConversion', 'EFloatingPoint', 'EFloatInvalidOp', 'EFloatDivByZero', 'EFloatOverflow', 'EFloatInexact', 'EDeadThrea' ] if (parserConf.extra_keywords != undefined) commonkeywords = commonkeywords.concat(parserConf.extra_keywords) if (parserConf.extra_builtins != undefined) commonBuiltins = commonBuiltins.concat(parserConf.extra_builtins) var keywords = wordRegexp(commonkeywords) var builtins = wordRegexp(commonBuiltins) var indentInfo = null var stringPrefixes = new RegExp('^((\'{3}|"{3}|[\'"]))', 'i') // tokenizers function tokenBase(stream, state) { // Handle scope changes if (stream.sol()) { var scopeOffset = state.scopes[0].offset if (stream.eatSpace()) { var lineOffset = stream.indentation() if (lineOffset > scopeOffset) { indentInfo = 'indent' } else if (lineOffset < scopeOffset) { indentInfo = 'dedent' } return null } else { if (scopeOffset > 0) { dedent(stream, state) } } } if (stream.eatSpace()) return null var ch = stream.peek() // Handle Comments if (ch === '#') { stream.skipToEnd() return 'comment' } // Handle Number Literals if (stream.match(/^[0-9\.]/, false)) { var floatLiteral = false // Floats if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true } if (stream.match(/^\d+\.\d*/)) { floatLiteral = true } if (stream.match(/^\.\d+/)) { floatLiteral = true } if (floatLiteral) { // Float literals may be "imaginary" stream.eat(/J/i) return 'number' } // Integers var intLiteral = false // Hex if (stream.match(/^0x[0-9a-f]+/i)) { intLiteral = true } // Binary if (stream.match(/^0b[01]+/i)) { intLiteral = true } // Octal if (stream.match(/^0o[0-7]+/i)) { intLiteral = true } // Decimal if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) { // Decimal literals may be "imaginary" stream.eat(/J/i) // TODO - Can you have imaginary longs? intLiteral = true } // Zero by itself with no other piece of number. if (stream.match(/^0(?![\dx])/i)) { intLiteral = true } if (intLiteral) { // Integer literals may be "long" stream.eat(/L/i) return 'number' } } // Handle Strings if (stream.match(stringPrefixes)) { state.tokenize = tokenStringFactory(stream.current()) return state.tokenize(stream, state) } if (stream.match(operators)) return 'operator' if (stream.match(keywords)) return 'keyword' if (stream.match(builtins)) return 'builtin' if (stream.match(identifiers)) { if ( state.lastToken != null && state.lastToken.match(/proc|iterator|macro|template|class|converter/) ) { return 'def' } return 'variable' } // Handle non-detected items stream.next() return ERRORCLASS } function tokenStringFactory(delimiter) { var singleline = delimiter.length == 1 var OUTCLASS = 'string' function tokenString(stream, state) { while (!stream.eol()) { stream.eatWhile(/[^'"\\]/) if (stream.eat('\\')) { stream.next() if (singleline && stream.eol()) { return OUTCLASS } } else if (stream.match(delimiter)) { state.tokenize = tokenBase return OUTCLASS } else { stream.eat(/['"]/) } } if (singleline) { if (parserConf.singleLineStringErrors) { return ERRORCLASS } else { state.tokenize = tokenBase } } return OUTCLASS } tokenString.isString = true return tokenString } function indent(stream, state, type) { type = type || 'nim' var indentUnit = 0 if (type === 'nim') { if (state.scopes[0].type !== 'nim') { state.scopes[0].offset = stream.indentation() return } for (var i = 0; i < state.scopes.length; ++i) { if (state.scopes[i].type === 'nim') { indentUnit = state.scopes[i].offset + conf.indentUnit break } } } else { indentUnit = stream.column() + stream.current().length } state.scopes.unshift({ offset: indentUnit, type: type }) } function dedent(stream, state, type) { type = type || 'nim' if (state.scopes.length == 1) return if (state.scopes[0].type === 'nim') { var _indent = stream.indentation() var _indent_index = -1 for (var i = 0; i < state.scopes.length; ++i) { if (_indent === state.scopes[i].offset) { _indent_index = i break } } if (_indent_index === -1) { return true } while (state.scopes[0].offset !== _indent) { state.scopes.shift() } return false } else { if (type === 'nim') { state.scopes[0].offset = stream.indentation() return false } else { if (state.scopes[0].type != type) { return true } state.scopes.shift() return false } } } function tokenLexer(stream, state) { indentInfo = null var style = state.tokenize(stream, state) var current = stream.current() // Handle '.' connected identifiers if (current === '.') { style = stream.match(identifiers, false) ? null : ERRORCLASS if (style === null && state.lastStyle === 'meta') { // Apply 'meta' style to '.' connected identifiers when // appropriate. style = 'meta' } return style } if ((style === 'variable' || style === 'builtin') && state.lastStyle === 'meta') { style = 'meta' } // Handle scope changes. if (current.match(/return|break|continue|raise/) || (current === 'discard' && stream.eol())) state.dedent += 1 if (current === 'lambda' || current === 'proc') state.lambda = true var delimiter_index = '[({'.indexOf(current) if (delimiter_index !== -1) { indent(stream, state, '])}'.slice(delimiter_index, delimiter_index + 1)) } else if (stream.eol() && current.match(/\=|\:|import|include|type|const|var|let/)) { indent(stream, state) } if (indentInfo === 'dedent') { if (dedent(stream, state)) { return ERRORCLASS } } delimiter_index = '])}'.indexOf(current) if (delimiter_index !== -1) { if (dedent(stream, state, current)) { return ERRORCLASS } } if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'nim') { if (state.scopes.length > 1) state.scopes.shift() state.dedent -= 1 } return style } var external = { startState: function(basecolumn) { return { tokenize: tokenBase, scopes: [{ offset: basecolumn || 0, type: 'nim' }], lastStyle: null, lastToken: null, lambda: false, dedent: 0 } }, token: function(stream, state) { var style = tokenLexer(stream, state) state.lastStyle = style var current = stream.current() if (current && style) state.lastToken = current if (stream.eol() && state.lambda) state.lambda = false return style }, indent: function(state) { if (state.tokenize != tokenBase) return state.tokenize.isString ? CodeMirror.Pass : 0 return state.scopes[0].offset }, lineComment: '#', fold: 'indent' } return external }) CodeMirror.defineMIME('text/x-nimrod', 'nimrod')