// CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://codemirror.net/LICENSE ;(function (mod) { if (typeof exports == 'object' && typeof module == 'object') // CommonJS mod(require('../../lib/codemirror')) else if (typeof define == 'function' && define.amd) // AMD define(['../../lib/codemirror'], mod) // Plain browser env else mod(CodeMirror) })(function (CodeMirror) { 'use strict' CodeMirror.defineMode('haxe', function (config, parserConfig) { var indentUnit = config.indentUnit // Tokenizer function kw(type) { return { type: type, style: 'keyword' } } var A = kw('keyword a'), B = kw('keyword b'), C = kw('keyword c') var operator = kw('operator'), atom = { type: 'atom', style: 'atom' }, attribute = { type: 'attribute', style: 'attribute' } var type = kw('typedef') var keywords = { if: A, while: A, else: B, do: B, try: B, return: C, break: C, continue: C, new: C, throw: C, var: kw('var'), inline: attribute, static: attribute, using: kw('import'), public: attribute, private: attribute, cast: kw('cast'), import: kw('import'), macro: kw('macro'), function: kw('function'), catch: kw('catch'), untyped: kw('untyped'), callback: kw('cb'), for: kw('for'), switch: kw('switch'), case: kw('case'), default: kw('default'), in: operator, never: kw('property_access'), trace: kw('trace'), class: type, abstract: type, enum: type, interface: type, typedef: type, extends: type, implements: type, dynamic: type, true: atom, false: atom, null: atom, } var isOperatorChar = /[+\-*&%=<>!?|]/ function chain(stream, state, f) { state.tokenize = f return f(stream, state) } function toUnescaped(stream, end) { var escaped = false, next while ((next = stream.next()) != null) { if (next == end && !escaped) return true escaped = !escaped && next == '\\' } } // Used as scratch variables to communicate multiple values without // consing up tons of objects. var type, content function ret(tp, style, cont) { type = tp content = cont return style } function haxeTokenBase(stream, state) { var ch = stream.next() if (ch == '"' || ch == "'") { return chain(stream, state, haxeTokenString(ch)) } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) { return ret(ch) } else if (ch == '0' && stream.eat(/x/i)) { stream.eatWhile(/[\da-f]/i) return ret('number', 'number') } else if (/\d/.test(ch) || (ch == '-' && stream.eat(/\d/))) { stream.match(/^\d*(?:\.\d*(?!\.))?(?:[eE][+\-]?\d+)?/) return ret('number', 'number') } else if (state.reAllowed && ch == '~' && stream.eat(/\//)) { toUnescaped(stream, '/') stream.eatWhile(/[gimsu]/) return ret('regexp', 'string-2') } else if (ch == '/') { if (stream.eat('*')) { return chain(stream, state, haxeTokenComment) } else if (stream.eat('/')) { stream.skipToEnd() return ret('comment', 'comment') } else { stream.eatWhile(isOperatorChar) return ret('operator', null, stream.current()) } } else if (ch == '#') { stream.skipToEnd() return ret('conditional', 'meta') } else if (ch == '@') { stream.eat(/:/) stream.eatWhile(/[\w_]/) return ret('metadata', 'meta') } else if (isOperatorChar.test(ch)) { stream.eatWhile(isOperatorChar) return ret('operator', null, stream.current()) } else { var word if (/[A-Z]/.test(ch)) { stream.eatWhile(/[\w_<>]/) word = stream.current() return ret('type', 'variable-3', word) } else { stream.eatWhile(/[\w_]/) var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word] return known && state.kwAllowed ? ret(known.type, known.style, word) : ret('variable', 'variable', word) } } } function haxeTokenString(quote) { return function (stream, state) { if (toUnescaped(stream, quote)) state.tokenize = haxeTokenBase return ret('string', 'string') } } function haxeTokenComment(stream, state) { var maybeEnd = false, ch while ((ch = stream.next())) { if (ch == '/' && maybeEnd) { state.tokenize = haxeTokenBase break } maybeEnd = ch == '*' } return ret('comment', 'comment') } // Parser var atomicTypes = { atom: true, number: true, variable: true, string: true, regexp: true } function HaxeLexical(indented, column, type, align, prev, info) { this.indented = indented this.column = column this.type = type this.prev = prev this.info = info if (align != null) this.align = align } function inScope(state, varname) { for (var v = state.localVars; v; v = v.next) if (v.name == varname) return true } function parseHaxe(state, style, type, content, stream) { var cc = state.cc // Communicate our context to the combinators. // (Less wasteful than consing up a hundred closures on every call.) cx.state = state cx.stream = stream ;(cx.marked = null), (cx.cc = cc) if (!state.lexical.hasOwnProperty('align')) state.lexical.align = true while (true) { var combinator = cc.length ? cc.pop() : statement if (combinator(type, content)) { while (cc.length && cc[cc.length - 1].lex) cc.pop()() if (cx.marked) return cx.marked if (type == 'variable' && inScope(state, content)) return 'variable-2' if (type == 'variable' && imported(state, content)) return 'variable-3' return style } } } function imported(state, typename) { if (/[a-z]/.test(typename.charAt(0))) return false var len = state.importedtypes.length for (var i = 0; i < len; i++) if (state.importedtypes[i] == typename) return true } function registerimport(importname) { var state = cx.state for (var t = state.importedtypes; t; t = t.next) if (t.name == importname) return state.importedtypes = { name: importname, next: state.importedtypes } } // Combinator utils var cx = { state: null, column: null, marked: null, cc: null } function pass() { for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]) } function cont() { pass.apply(null, arguments) return true } function inList(name, list) { for (var v = list; v; v = v.next) if (v.name == name) return true return false } function register(varname) { var state = cx.state if (state.context) { cx.marked = 'def' if (inList(varname, state.localVars)) return state.localVars = { name: varname, next: state.localVars } } else if (state.globalVars) { if (inList(varname, state.globalVars)) return state.globalVars = { name: varname, next: state.globalVars } } } // Combinators var defaultVars = { name: 'this', next: null } function pushcontext() { if (!cx.state.context) cx.state.localVars = defaultVars cx.state.context = { prev: cx.state.context, vars: cx.state.localVars } } function popcontext() { cx.state.localVars = cx.state.context.vars cx.state.context = cx.state.context.prev } popcontext.lex = true function pushlex(type, info) { var result = function () { var state = cx.state state.lexical = new HaxeLexical(state.indented, cx.stream.column(), type, null, state.lexical, info) } result.lex = true return result } function poplex() { var state = cx.state if (state.lexical.prev) { if (state.lexical.type == ')') state.indented = state.lexical.indented state.lexical = state.lexical.prev } } poplex.lex = true function expect(wanted) { function f(type) { if (type == wanted) return cont() else if (wanted == ';') return pass() else return cont(f) } return f } function statement(type) { if (type == '@') return cont(metadef) if (type == 'var') return cont(pushlex('vardef'), vardef1, expect(';'), poplex) if (type == 'keyword a') return cont(pushlex('form'), expression, statement, poplex) if (type == 'keyword b') return cont(pushlex('form'), statement, poplex) if (type == '{') return cont(pushlex('}'), pushcontext, block, poplex, popcontext) if (type == ';') return cont() if (type == 'attribute') return cont(maybeattribute) if (type == 'function') return cont(functiondef) if (type == 'for') return cont(pushlex('form'), expect('('), pushlex(')'), forspec1, expect(')'), poplex, statement, poplex) if (type == 'variable') return cont(pushlex('stat'), maybelabel) if (type == 'switch') return cont(pushlex('form'), expression, pushlex('}', 'switch'), expect('{'), block, poplex, poplex) if (type == 'case') return cont(expression, expect(':')) if (type == 'default') return cont(expect(':')) if (type == 'catch') return cont(pushlex('form'), pushcontext, expect('('), funarg, expect(')'), statement, poplex, popcontext) if (type == 'import') return cont(importdef, expect(';')) if (type == 'typedef') return cont(typedef) return pass(pushlex('stat'), expression, expect(';'), poplex) } function expression(type) { if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator) if (type == 'type') return cont(maybeoperator) if (type == 'function') return cont(functiondef) if (type == 'keyword c') return cont(maybeexpression) if (type == '(') return cont(pushlex(')'), maybeexpression, expect(')'), poplex, maybeoperator) if (type == 'operator') return cont(expression) if (type == '[') return cont(pushlex(']'), commasep(maybeexpression, ']'), poplex, maybeoperator) if (type == '{') return cont(pushlex('}'), commasep(objprop, '}'), poplex, maybeoperator) return cont() } function maybeexpression(type) { if (type.match(/[;\}\)\],]/)) return pass() return pass(expression) } function maybeoperator(type, value) { if (type == 'operator' && /\+\+|--/.test(value)) return cont(maybeoperator) if (type == 'operator' || type == ':') return cont(expression) if (type == ';') return if (type == '(') return cont(pushlex(')'), commasep(expression, ')'), poplex, maybeoperator) if (type == '.') return cont(property, maybeoperator) if (type == '[') return cont(pushlex(']'), expression, expect(']'), poplex, maybeoperator) } function maybeattribute(type) { if (type == 'attribute') return cont(maybeattribute) if (type == 'function') return cont(functiondef) if (type == 'var') return cont(vardef1) } function metadef(type) { if (type == ':') return cont(metadef) if (type == 'variable') return cont(metadef) if (type == '(') return cont(pushlex(')'), commasep(metaargs, ')'), poplex, statement) } function metaargs(type) { if (type == 'variable') return cont() } function importdef(type, value) { if (type == 'variable' && /[A-Z]/.test(value.charAt(0))) { registerimport(value) return cont() } else if (type == 'variable' || type == 'property' || type == '.' || value == '*') return cont(importdef) } function typedef(type, value) { if (type == 'variable' && /[A-Z]/.test(value.charAt(0))) { registerimport(value) return cont() } else if (type == 'type' && /[A-Z]/.test(value.charAt(0))) { return cont() } } function maybelabel(type) { if (type == ':') return cont(poplex, statement) return pass(maybeoperator, expect(';'), poplex) } function property(type) { if (type == 'variable') { cx.marked = 'property' return cont() } } function objprop(type) { if (type == 'variable') cx.marked = 'property' if (atomicTypes.hasOwnProperty(type)) return cont(expect(':'), expression) } function commasep(what, end) { function proceed(type) { if (type == ',') return cont(what, proceed) if (type == end) return cont() return cont(expect(end)) } return function (type) { if (type == end) return cont() else return pass(what, proceed) } } function block(type) { if (type == '}') return cont() return pass(statement, block) } function vardef1(type, value) { if (type == 'variable') { register(value) return cont(typeuse, vardef2) } return cont() } function vardef2(type, value) { if (value == '=') return cont(expression, vardef2) if (type == ',') return cont(vardef1) } function forspec1(type, value) { if (type == 'variable') { register(value) return cont(forin, expression) } else { return pass() } } function forin(_type, value) { if (value == 'in') return cont() } function functiondef(type, value) { //function names starting with upper-case letters are recognised as types, so cludging them together here. if (type == 'variable' || type == 'type') { register(value) return cont(functiondef) } if (value == 'new') return cont(functiondef) if (type == '(') return cont(pushlex(')'), pushcontext, commasep(funarg, ')'), poplex, typeuse, statement, popcontext) } function typeuse(type) { if (type == ':') return cont(typestring) } function typestring(type) { if (type == 'type') return cont() if (type == 'variable') return cont() if (type == '{') return cont(pushlex('}'), commasep(typeprop, '}'), poplex) } function typeprop(type) { if (type == 'variable') return cont(typeuse) } function funarg(type, value) { if (type == 'variable') { register(value) return cont(typeuse) } } // Interface return { startState: function (basecolumn) { var defaulttypes = ['Int', 'Float', 'String', 'Void', 'Std', 'Bool', 'Dynamic', 'Array'] var state = { tokenize: haxeTokenBase, reAllowed: true, kwAllowed: true, cc: [], lexical: new HaxeLexical((basecolumn || 0) - indentUnit, 0, 'block', false), localVars: parserConfig.localVars, importedtypes: defaulttypes, context: parserConfig.localVars && { vars: parserConfig.localVars }, indented: 0, } if (parserConfig.globalVars && typeof parserConfig.globalVars == 'object') state.globalVars = parserConfig.globalVars return state }, token: function (stream, state) { if (stream.sol()) { if (!state.lexical.hasOwnProperty('align')) state.lexical.align = false state.indented = stream.indentation() } if (stream.eatSpace()) return null var style = state.tokenize(stream, state) if (type == 'comment') return style state.reAllowed = !!(type == 'operator' || type == 'keyword c' || type.match(/^[\[{}\(,;:]$/)) state.kwAllowed = type != '.' return parseHaxe(state, style, type, content, stream) }, indent: function (state, textAfter) { if (state.tokenize != haxeTokenBase) return 0 var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical if (lexical.type == 'stat' && firstChar == '}') lexical = lexical.prev var type = lexical.type, closing = firstChar == type if (type == 'vardef') return lexical.indented + 4 else if (type == 'form' && firstChar == '{') return lexical.indented else if (type == 'stat' || type == 'form') return lexical.indented + indentUnit else if (lexical.info == 'switch' && !closing) return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit) else if (lexical.align) return lexical.column + (closing ? 0 : 1) else return lexical.indented + (closing ? 0 : indentUnit) }, electricChars: '{}', blockCommentStart: '/*', blockCommentEnd: '*/', lineComment: '//', } }) CodeMirror.defineMIME('text/x-haxe', 'haxe') CodeMirror.defineMode('hxml', function () { return { startState: function () { return { define: false, inString: false, } }, token: function (stream, state) { var ch = stream.peek() var sol = stream.sol() ///* comments */ if (ch == '#') { stream.skipToEnd() return 'comment' } if (sol && ch == '-') { var style = 'variable-2' stream.eat(/-/) if (stream.peek() == '-') { stream.eat(/-/) style = 'keyword a' } if (stream.peek() == 'D') { stream.eat(/[D]/) style = 'keyword c' state.define = true } stream.eatWhile(/[A-Z]/i) return style } var ch = stream.peek() if (state.inString == false && ch == "'") { state.inString = true stream.next() } if (state.inString == true) { if (stream.skipTo("'")) { } else { stream.skipToEnd() } if (stream.peek() == "'") { stream.next() state.inString = false } return 'string' } stream.next() return null }, lineComment: '#', } }) CodeMirror.defineMIME('text/x-hxml', 'hxml') })