├── test └── test.js ├── out ├── test.coffee ├── js2cs.coffee └── js2cs.recompiled.js ├── example ├── codemirror │ ├── css │ │ ├── people.jpg │ │ ├── sparqlcolors.css │ │ ├── docs.css │ │ ├── csscolors.css │ │ ├── xmlcolors.css │ │ └── jscolors.css │ └── js │ │ ├── parsedummy.js │ │ ├── tokenize.js │ │ ├── highlight.js │ │ ├── mirrorframe.js │ │ ├── parsehtmlmixed.js │ │ ├── util.js │ │ ├── stringstream.js │ │ ├── parsecss.js │ │ ├── parsesparql.js │ │ ├── tokenizejavascript.js │ │ ├── parsexml.js │ │ ├── parsejavascript.js │ │ ├── undo.js │ │ ├── codemirror.js │ │ └── select.js ├── app.js └── example.html ├── README.md └── js2cs.js /test/test.js: -------------------------------------------------------------------------------- 1 | for(_e = 0, _g = _f.length; _e < _g; _e++) 2 | { 3 | thing(); 4 | } 5 | -------------------------------------------------------------------------------- /out/test.coffee: -------------------------------------------------------------------------------- 1 | _e: 0, _g: _f.length 2 | while _e < _g 3 | _e = _e + 1 4 | thing() 5 | 6 | -------------------------------------------------------------------------------- /example/codemirror/css/people.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixflame/js2cs/HEAD/example/codemirror/css/people.jpg -------------------------------------------------------------------------------- /example/codemirror/css/sparqlcolors.css: -------------------------------------------------------------------------------- 1 | html { 2 | cursor: text; 3 | } 4 | 5 | .editbox { 6 | margin: .4em; 7 | padding: 0; 8 | font-family: monospace; 9 | font-size: 10pt; 10 | color: black; 11 | } 12 | 13 | .editbox p { 14 | margin: 0; 15 | } 16 | 17 | span.sp-keyword { 18 | color: #708; 19 | } 20 | 21 | span.sp-prefixed { 22 | color: #5d1; 23 | } 24 | 25 | span.sp-var { 26 | color: #00c; 27 | } 28 | 29 | span.sp-comment { 30 | color: #a70; 31 | } 32 | 33 | span.sp-literal { 34 | color: #a22; 35 | } 36 | 37 | span.sp-uri { 38 | color: #292; 39 | } 40 | 41 | span.sp-operator { 42 | color: #088; 43 | } 44 | -------------------------------------------------------------------------------- /example/codemirror/css/docs.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 3em 6em; 4 | color: black; 5 | max-width: 50em; 6 | } 7 | 8 | h1 { 9 | font-size: 22pt; 10 | } 11 | 12 | .underline { 13 | border-bottom: 3px solid #C44; 14 | } 15 | 16 | h2 { 17 | font-size: 14pt; 18 | } 19 | 20 | h3 { 21 | font-size: 12pt; 22 | } 23 | 24 | p.rel { 25 | padding-left: 2em; 26 | text-indent: -2em; 27 | } 28 | 29 | div.border { 30 | border: 1px solid black; 31 | } 32 | 33 | code { 34 | font-family: courier, monospace; 35 | font-size: 90%; 36 | color: #144; 37 | } 38 | 39 | pre.code { 40 | margin: 1.1em 12px; 41 | border: 1px solid #CCCCCC; 42 | color: black; 43 | padding: .4em; 44 | font-family: courier, monospace; 45 | } 46 | 47 | .warn { 48 | color: #C00; 49 | } 50 | -------------------------------------------------------------------------------- /example/codemirror/css/csscolors.css: -------------------------------------------------------------------------------- 1 | html { 2 | cursor: text; 3 | } 4 | 5 | .editbox { 6 | margin: .4em; 7 | padding: 0; 8 | font-family: monospace; 9 | font-size: 10pt; 10 | color: black; 11 | } 12 | 13 | pre.code, .editbox { 14 | color: #666; 15 | } 16 | 17 | .editbox p { 18 | margin: 0; 19 | } 20 | 21 | span.css-at { 22 | color: #708; 23 | } 24 | 25 | span.css-unit { 26 | color: #281; 27 | } 28 | 29 | span.css-value { 30 | color: #708; 31 | } 32 | 33 | span.css-identifier { 34 | color: black; 35 | } 36 | 37 | span.css-selector { 38 | color: #11B; 39 | } 40 | 41 | span.css-important { 42 | color: #00F; 43 | } 44 | 45 | span.css-colorcode { 46 | color: #299; 47 | } 48 | 49 | span.css-comment { 50 | color: #A70; 51 | } 52 | 53 | span.css-string { 54 | color: #A22; 55 | } 56 | -------------------------------------------------------------------------------- /example/codemirror/css/xmlcolors.css: -------------------------------------------------------------------------------- 1 | html { 2 | cursor: text; 3 | } 4 | 5 | .editbox { 6 | margin: .4em; 7 | padding: 0; 8 | font-family: monospace; 9 | font-size: 10pt; 10 | color: black; 11 | } 12 | 13 | .editbox p { 14 | margin: 0; 15 | } 16 | 17 | span.xml-tagname { 18 | color: #A0B; 19 | } 20 | 21 | span.xml-attribute { 22 | color: #281; 23 | } 24 | 25 | span.xml-punctuation { 26 | color: black; 27 | } 28 | 29 | span.xml-attname { 30 | color: #00F; 31 | } 32 | 33 | span.xml-comment { 34 | color: #A70; 35 | } 36 | 37 | span.xml-cdata { 38 | color: #48A; 39 | } 40 | 41 | span.xml-processing { 42 | color: #999; 43 | } 44 | 45 | span.xml-entity { 46 | color: #A22; 47 | } 48 | 49 | span.xml-error { 50 | color: #F00 !important; 51 | } 52 | 53 | span.xml-text { 54 | color: black; 55 | } 56 | -------------------------------------------------------------------------------- /example/codemirror/css/jscolors.css: -------------------------------------------------------------------------------- 1 | html { 2 | cursor: text; 3 | } 4 | 5 | .editbox { 6 | margin: .4em; 7 | padding: 0; 8 | font-family: monospace; 9 | font-size: 10pt; 10 | color: black; 11 | } 12 | 13 | pre.code, .editbox { 14 | color: #666666; 15 | } 16 | 17 | .editbox p { 18 | margin: 0; 19 | } 20 | 21 | span.js-punctuation { 22 | color: #666666; 23 | } 24 | 25 | span.js-operator { 26 | color: #666666; 27 | } 28 | 29 | span.js-keyword { 30 | color: #770088; 31 | } 32 | 33 | span.js-atom { 34 | color: #228811; 35 | } 36 | 37 | span.js-variable { 38 | color: black; 39 | } 40 | 41 | span.js-variabledef { 42 | color: #0000FF; 43 | } 44 | 45 | span.js-localvariable { 46 | color: #004499; 47 | } 48 | 49 | span.js-property { 50 | color: black; 51 | } 52 | 53 | span.js-comment { 54 | color: #AA7700; 55 | } 56 | 57 | span.js-string { 58 | color: #AA2222; 59 | } 60 | -------------------------------------------------------------------------------- /example/codemirror/js/parsedummy.js: -------------------------------------------------------------------------------- 1 | var DummyParser = Editor.Parser = (function() { 2 | function tokenizeDummy(source) { 3 | while (!source.endOfLine()) source.next(); 4 | return "text"; 5 | } 6 | function parseDummy(source) { 7 | function indentTo(n) {return function() {return n;}} 8 | source = tokenizer(source, tokenizeDummy); 9 | var space = 0; 10 | 11 | var iter = { 12 | next: function() { 13 | var tok = source.next(); 14 | if (tok.type == "whitespace") { 15 | if (tok.value == "\n") tok.indentation = indentTo(space); 16 | else space = tok.value.length; 17 | } 18 | return tok; 19 | }, 20 | copy: function() { 21 | var _space = space; 22 | return function(_source) { 23 | space = _space; 24 | source = tokenizer(_source, tokenizeDummy); 25 | return iter; 26 | }; 27 | } 28 | }; 29 | return iter; 30 | } 31 | return {make: parseDummy}; 32 | })(); 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Usage 2 | ---------- 3 | 4 | [CoffeeScript](http://www.coffeescript.com) 5 | 6 | 7 | node js2cs.js file_to_convert 8 | 9 | 10 | Options: --debug, --ilevel, --convert 11 | 12 | --debug: Print the AST tree to STDIO. 13 | 14 | --ilevel: Show node types, names and indent levels. 15 | 16 | --convert: Output converted program to stdout. 17 | 18 | Browser demo is in /example 19 | 20 | Known Limitations 21 | -------------------- 22 | 23 | * Limited by PEG.js's ability to read your syntax. You may (will) have to refactor it to use this tool. 24 | 25 | * No support for LabelledStatement. Only used with BreakStatement (break) which is not used in Coffee. 26 | 27 | * Untested. Not symbol-for-symbol, token-for-token. 28 | 29 | * For Statements are turned into While statements in Coffee because Coffee's For is one-way to JavaScript (can't be translated back to that.) 30 | 31 | * Comma operator (,) compiles to \n. Not supported in CoffeeScript. 32 | 33 | * Postfix expression like ++i is not handled yet. 34 | 35 | * Else if is NOT supported by the PEGjs grammar. You can use if { stuff; } else { if() { stuff; } }. 36 | 37 | * No one line if statements. 38 | 39 | -- 40 | Jonathan Silverman ("jsilver") 41 | -------------------------------------------------------------------------------- /example/codemirror/js/tokenize.js: -------------------------------------------------------------------------------- 1 | // A framework for simple tokenizers. Takes care of newlines and 2 | // white-space, and of getting the text from the source stream into 3 | // the token object. A state is a function of two arguments -- a 4 | // string stream and a setState function. The second can be used to 5 | // change the tokenizer's state, and can be ignored for stateless 6 | // tokenizers. This function should advance the stream over a token 7 | // and return a string or object containing information about the next 8 | // token, or null to pass and have the (new) state be called to finish 9 | // the token. When a string is given, it is wrapped in a {style, type} 10 | // object. In the resulting object, the characters consumed are stored 11 | // under the content property. Any whitespace following them is also 12 | // automatically consumed, and added to the value property. (Thus, 13 | // content is the actual meaningful part of the token, while value 14 | // contains all the text it spans.) 15 | 16 | function tokenizer(source, state) { 17 | // Newlines are always a separate token. 18 | function isWhiteSpace(ch) { 19 | // The messy regexp is because IE's regexp matcher is of the 20 | // opinion that non-breaking spaces are no whitespace. 21 | return ch != "\n" && /^[\s\u00a0]*$/.test(ch); 22 | } 23 | 24 | var tokenizer = { 25 | state: state, 26 | 27 | take: function(type) { 28 | if (typeof(type) == "string") 29 | type = {style: type, type: type}; 30 | 31 | type.content = (type.content || "") + source.get(); 32 | if (!/\n$/.test(type.content)) 33 | source.nextWhile(isWhiteSpace); 34 | type.value = type.content + source.get(); 35 | return type; 36 | }, 37 | 38 | next: function () { 39 | if (!source.more()) throw StopIteration; 40 | 41 | var type; 42 | if (source.equals("\n")) { 43 | source.next(); 44 | return this.take("whitespace"); 45 | } 46 | 47 | if (source.applies(isWhiteSpace)) 48 | type = "whitespace"; 49 | else 50 | while (!type) 51 | type = this.state(source, function(s) {tokenizer.state = s;}); 52 | 53 | return this.take(type); 54 | } 55 | }; 56 | return tokenizer; 57 | } 58 | -------------------------------------------------------------------------------- /example/codemirror/js/highlight.js: -------------------------------------------------------------------------------- 1 | // Minimal framing needed to use CodeMirror-style parsers to highlight 2 | // code. Load this along with tokenize.js, stringstream.js, and your 3 | // parser. Then call highlightText, passing a string as the first 4 | // argument, and as the second argument either a callback function 5 | // that will be called with an array of SPAN nodes for every line in 6 | // the code, or a DOM node to which to append these spans, and 7 | // optionally (not needed if you only loaded one parser) a parser 8 | // object. 9 | 10 | // Stuff from util.js that the parsers are using. 11 | var StopIteration = {toString: function() {return "StopIteration"}}; 12 | 13 | var Editor = {}; 14 | var indentUnit = 2; 15 | 16 | (function(){ 17 | function normaliseString(string) { 18 | var tab = ""; 19 | for (var i = 0; i < indentUnit; i++) tab += " "; 20 | 21 | string = string.replace(/\t/g, tab).replace(/\u00a0/g, " ").replace(/\r\n?/g, "\n"); 22 | var pos = 0, parts = [], lines = string.split("\n"); 23 | for (var line = 0; line < lines.length; line++) { 24 | if (line != 0) parts.push("\n"); 25 | parts.push(lines[line]); 26 | } 27 | 28 | return { 29 | next: function() { 30 | if (pos < parts.length) return parts[pos++]; 31 | else throw StopIteration; 32 | } 33 | }; 34 | } 35 | 36 | window.highlightText = function(string, callback, parser) { 37 | parser = (parser || Editor.Parser).make(stringStream(normaliseString(string))); 38 | var line = []; 39 | if (callback.nodeType == 1) { 40 | var node = callback; 41 | callback = function(line) { 42 | for (var i = 0; i < line.length; i++) 43 | node.appendChild(line[i]); 44 | node.appendChild(document.createElement("BR")); 45 | }; 46 | } 47 | 48 | try { 49 | while (true) { 50 | var token = parser.next(); 51 | if (token.value == "\n") { 52 | callback(line); 53 | line = []; 54 | } 55 | else { 56 | var span = document.createElement("SPAN"); 57 | span.className = token.style; 58 | span.appendChild(document.createTextNode(token.value)); 59 | line.push(span); 60 | } 61 | } 62 | } 63 | catch (e) { 64 | if (e != StopIteration) throw e; 65 | } 66 | if (line.length) callback(line); 67 | } 68 | })(); 69 | -------------------------------------------------------------------------------- /example/codemirror/js/mirrorframe.js: -------------------------------------------------------------------------------- 1 | /* Demonstration of embedding CodeMirror in a bigger application. The 2 | * interface defined here is a mess of prompts and confirms, and 3 | * should probably not be used in a real project. 4 | */ 5 | 6 | function MirrorFrame(place, options) { 7 | this.home = document.createElement("DIV"); 8 | if (place.appendChild) 9 | place.appendChild(this.home); 10 | else 11 | place(this.home); 12 | 13 | var self = this; 14 | function makeButton(name, action) { 15 | var button = document.createElement("INPUT"); 16 | button.type = "button"; 17 | button.value = name; 18 | self.home.appendChild(button); 19 | button.onclick = function(){self[action].call(self);}; 20 | } 21 | 22 | makeButton("Search", "search"); 23 | makeButton("Replace", "replace"); 24 | makeButton("Current line", "line"); 25 | makeButton("Jump to line", "jump"); 26 | makeButton("Insert constructor", "macro"); 27 | makeButton("Indent all", "reindent"); 28 | 29 | this.mirror = new CodeMirror(this.home, options); 30 | } 31 | 32 | MirrorFrame.prototype = { 33 | search: function() { 34 | var text = prompt("Enter search term:", ""); 35 | if (!text) return; 36 | 37 | var first = true; 38 | do { 39 | var cursor = this.mirror.getSearchCursor(text, first); 40 | first = false; 41 | while (cursor.findNext()) { 42 | cursor.select(); 43 | if (!confirm("Search again?")) 44 | return; 45 | } 46 | } while (confirm("End of document reached. Start over?")); 47 | }, 48 | 49 | replace: function() { 50 | // This is a replace-all, but it is possible to implement a 51 | // prompting replace. 52 | var from = prompt("Enter search string:", ""), to; 53 | if (from) to = prompt("What should it be replaced with?", ""); 54 | if (to == null) return; 55 | 56 | var cursor = this.mirror.getSearchCursor(from, false); 57 | while (cursor.findNext()) 58 | cursor.replace(to); 59 | }, 60 | 61 | jump: function() { 62 | var line = prompt("Jump to line:", ""); 63 | if (line && !isNaN(Number(line))) 64 | this.mirror.jumpToLine(Number(line)); 65 | }, 66 | 67 | line: function() { 68 | alert("The cursor is currently at line " + this.mirror.currentLine()); 69 | this.mirror.focus(); 70 | }, 71 | 72 | macro: function() { 73 | var name = prompt("Name your constructor:", ""); 74 | if (name) 75 | this.mirror.replaceSelection("function " + name + "() {\n \n}\n\n" + name + ".prototype = {\n \n};\n"); 76 | }, 77 | 78 | reindent: function() { 79 | this.mirror.reindent(); 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /example/codemirror/js/parsehtmlmixed.js: -------------------------------------------------------------------------------- 1 | var HTMLMixedParser = Editor.Parser = (function() { 2 | if (!(CSSParser && JSParser && XMLParser)) 3 | throw new Error("CSS, JS, and XML parsers must be loaded for HTML mixed mode to work."); 4 | XMLParser.configure({useHTMLKludges: true}); 5 | 6 | function parseMixed(stream) { 7 | var htmlParser = XMLParser.make(stream), localParser = null, inTag = false; 8 | var iter = {next: top, copy: copy}; 9 | 10 | function top() { 11 | var token = htmlParser.next(); 12 | if (token.content == "<") 13 | inTag = true; 14 | else if (token.style == "xml-tagname" && inTag === true) 15 | inTag = token.content.toLowerCase(); 16 | else if (token.content == ">") { 17 | if (inTag == "script") 18 | iter.next = local(JSParser, " 0) 52 | throw "End of stringstream reached without emptying buffer ('" + accum + "')."; 53 | else 54 | throw StopIteration; 55 | } 56 | return current.charAt(pos++); 57 | }, 58 | // Return the characters iterated over since the last call to 59 | // .get(). 60 | get: function() { 61 | var temp = accum; 62 | accum = ""; 63 | if (pos > 0){ 64 | temp += current.slice(0, pos); 65 | current = current.slice(pos); 66 | pos = 0; 67 | } 68 | return temp; 69 | }, 70 | // Push a string back into the stream. 71 | push: function(str) { 72 | current = current.slice(0, pos) + str + current.slice(pos); 73 | }, 74 | lookAhead: function(str, consume, skipSpaces, caseInsensitive) { 75 | function cased(str) {return caseInsensitive ? str.toLowerCase() : str;} 76 | str = cased(str); 77 | var found = false; 78 | 79 | var _accum = accum, _pos = pos; 80 | if (skipSpaces) this.nextWhileMatches(/[\s\u00a0]/); 81 | 82 | while (true) { 83 | var end = pos + str.length, left = current.length - pos; 84 | if (end <= current.length) { 85 | found = str == cased(current.slice(pos, end)); 86 | pos = end; 87 | break; 88 | } 89 | else if (str.slice(0, left) == cased(current.slice(pos))) { 90 | accum += current; current = ""; 91 | try {current = source.next();} 92 | catch (e) {break;} 93 | pos = 0; 94 | str = str.slice(left); 95 | } 96 | else { 97 | break; 98 | } 99 | } 100 | 101 | if (!(found && consume)) { 102 | current = accum.slice(_accum.length) + current; 103 | pos = _pos; 104 | accum = _accum; 105 | } 106 | 107 | return found; 108 | }, 109 | 110 | // Utils built on top of the above 111 | more: function() { 112 | return this.peek() !== null; 113 | }, 114 | applies: function(test) { 115 | var next = this.peek(); 116 | return (next !== null && test(next)); 117 | }, 118 | nextWhile: function(test) { 119 | var next; 120 | while ((next = this.peek()) !== null && test(next)) 121 | this.next(); 122 | }, 123 | matches: function(re) { 124 | var next = this.peek(); 125 | return (next !== null && re.test(next)); 126 | }, 127 | nextWhileMatches: function(re) { 128 | var next; 129 | while ((next = this.peek()) !== null && re.test(next)) 130 | this.next(); 131 | }, 132 | equals: function(ch) { 133 | return ch === this.peek(); 134 | }, 135 | endOfLine: function() { 136 | var next = this.peek(); 137 | return next == null || next == "\n"; 138 | } 139 | }; 140 | }; 141 | -------------------------------------------------------------------------------- /example/codemirror/js/parsecss.js: -------------------------------------------------------------------------------- 1 | /* Simple parser for CSS */ 2 | 3 | var CSSParser = Editor.Parser = (function() { 4 | var tokenizeCSS = (function() { 5 | function normal(source, setState) { 6 | var ch = source.next(); 7 | if (ch == "@") { 8 | source.nextWhileMatches(/\w/); 9 | return "css-at"; 10 | } 11 | else if (ch == "/" && source.equals("*")) { 12 | setState(inCComment); 13 | return null; 14 | } 15 | else if (ch == "<" && source.equals("!")) { 16 | setState(inSGMLComment); 17 | return null; 18 | } 19 | else if (ch == "=") { 20 | return "css-compare"; 21 | } 22 | else if (source.equals("=") && (ch == "~" || ch == "|")) { 23 | source.next(); 24 | return "css-compare"; 25 | } 26 | else if (ch == "\"" || ch == "'") { 27 | setState(inString(ch)); 28 | return null; 29 | } 30 | else if (ch == "#") { 31 | source.nextWhileMatches(/\w/); 32 | return "css-hash"; 33 | } 34 | else if (ch == "!") { 35 | source.nextWhileMatches(/[ \t]/); 36 | source.nextWhileMatches(/\w/); 37 | return "css-important"; 38 | } 39 | else if (/\d/.test(ch)) { 40 | source.nextWhileMatches(/[\w.%]/); 41 | return "css-unit"; 42 | } 43 | else if (/[,.+>*\/]/.test(ch)) { 44 | return "css-select-op"; 45 | } 46 | else if (/[;{}:\[\]]/.test(ch)) { 47 | return "css-punctuation"; 48 | } 49 | else { 50 | source.nextWhileMatches(/[\w\\\-_]/); 51 | return "css-identifier"; 52 | } 53 | } 54 | 55 | function inCComment(source, setState) { 56 | var maybeEnd = false; 57 | while (!source.endOfLine()) { 58 | var ch = source.next(); 59 | if (maybeEnd && ch == "/") { 60 | setState(normal); 61 | break; 62 | } 63 | maybeEnd = (ch == "*"); 64 | } 65 | return "css-comment"; 66 | } 67 | 68 | function inSGMLComment(source, setState) { 69 | var dashes = 0; 70 | while (!source.endOfLine()) { 71 | var ch = source.next(); 72 | if (dashes >= 2 && ch == ">") { 73 | setState(normal); 74 | break; 75 | } 76 | dashes = (ch == "-") ? dashes + 1 : 0; 77 | } 78 | return "css-comment"; 79 | } 80 | 81 | function inString(quote) { 82 | return function(source, setState) { 83 | var escaped = false; 84 | while (!source.endOfLine()) { 85 | var ch = source.next(); 86 | if (ch == quote && !escaped) 87 | break; 88 | escaped = !escaped && ch == "\\"; 89 | } 90 | if (!escaped) 91 | setState(normal); 92 | return "css-string"; 93 | }; 94 | } 95 | 96 | return function(source, startState) { 97 | return tokenizer(source, startState || normal); 98 | }; 99 | })(); 100 | 101 | function indentCSS(inBraces, inRule, base) { 102 | return function(nextChars) { 103 | if (!inBraces || /^\}/.test(nextChars)) return base; 104 | else if (inRule) return base + indentUnit * 2; 105 | else return base + indentUnit; 106 | }; 107 | } 108 | 109 | // This is a very simplistic parser -- since CSS does not really 110 | // nest, it works acceptably well, but some nicer colouroing could 111 | // be provided with a more complicated parser. 112 | function parseCSS(source, basecolumn) { 113 | basecolumn = basecolumn || 0; 114 | var tokens = tokenizeCSS(source); 115 | var inBraces = false, inRule = false, inDecl = false;; 116 | 117 | var iter = { 118 | next: function() { 119 | var token = tokens.next(), style = token.style, content = token.content; 120 | 121 | if (style == "css-hash") 122 | style = token.style = inRule ? "css-colorcode" : "css-identifier"; 123 | if (style == "css-identifier") { 124 | if (inRule) token.style = "css-value"; 125 | else if (!inBraces && !inDecl) token.style = "css-selector"; 126 | } 127 | 128 | if (content == "\n") 129 | token.indentation = indentCSS(inBraces, inRule, basecolumn); 130 | 131 | if (content == "{") 132 | inBraces = true; 133 | else if (content == "}") 134 | inBraces = inRule = inDecl = false; 135 | else if (content == ";") 136 | inRule = inDecl = false; 137 | else if (inBraces && style != "css-comment" && style != "whitespace") 138 | inRule = true; 139 | else if (!inBraces && style == "css-at") 140 | inDecl = true; 141 | 142 | return token; 143 | }, 144 | 145 | copy: function() { 146 | var _inBraces = inBraces, _inRule = inRule, _tokenState = tokens.state; 147 | return function(source) { 148 | tokens = tokenizeCSS(source, _tokenState); 149 | inBraces = _inBraces; 150 | inRule = _inRule; 151 | return iter; 152 | }; 153 | } 154 | }; 155 | return iter; 156 | } 157 | 158 | return {make: parseCSS, electricChars: "}"}; 159 | })(); 160 | -------------------------------------------------------------------------------- /example/codemirror/js/parsesparql.js: -------------------------------------------------------------------------------- 1 | var SparqlParser = Editor.Parser = (function() { 2 | function wordRegexp(words) { 3 | return new RegExp("^(?:" + words.join("|") + ")$", "i"); 4 | } 5 | var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri", 6 | "isblank", "isliteral", "union", "a"]); 7 | var keywords = wordRegexp(["base", "prefix", "select", "distinct", "reduced", "construct", "describe", 8 | "ask", "from", "named", "where", "order", "limit", "offset", "filter", "optional", 9 | "graph", "by", "asc", "desc"]); 10 | var operatorChars = /[*+\-<>=&|]/; 11 | 12 | var tokenizeSparql = (function() { 13 | function normal(source, setState) { 14 | var ch = source.next(); 15 | if (ch == "$" || ch == "?") { 16 | source.nextWhileMatches(/[\w\d]/); 17 | return "sp-var"; 18 | } 19 | else if (ch == "<" && !source.matches(/[\s\u00a0=]/)) { 20 | source.nextWhileMatches(/[^\s\u00a0>]/); 21 | if (source.equals(">")) source.next(); 22 | return "sp-uri"; 23 | } 24 | else if (ch == "\"" || ch == "'") { 25 | setState(inLiteral(ch)); 26 | return null; 27 | } 28 | else if (/[{}\(\),\.;\[\]]/.test(ch)) { 29 | return "sp-punc"; 30 | } 31 | else if (ch == "#") { 32 | while (!source.endOfLine()) source.next(); 33 | return "sp-comment"; 34 | } 35 | else if (operatorChars.test(ch)) { 36 | source.nextWhileMatches(operatorChars); 37 | return "sp-operator"; 38 | } 39 | else if (ch == ":") { 40 | source.nextWhileMatches(/[\w\d\._\-]/); 41 | return "sp-prefixed"; 42 | } 43 | else { 44 | source.nextWhileMatches(/[_\w\d]/); 45 | if (source.equals(":")) { 46 | source.next(); 47 | source.nextWhileMatches(/[\w\d_\-]/); 48 | return "sp-prefixed"; 49 | } 50 | var word = source.get(), type; 51 | if (ops.test(word)) 52 | type = "sp-operator"; 53 | else if (keywords.test(word)) 54 | type = "sp-keyword"; 55 | else 56 | type = "sp-word"; 57 | return {style: type, content: word}; 58 | } 59 | } 60 | 61 | function inLiteral(quote) { 62 | return function(source, setState) { 63 | var escaped = false; 64 | while (!source.endOfLine()) { 65 | var ch = source.next(); 66 | if (ch == quote && !escaped) { 67 | setState(normal); 68 | break; 69 | } 70 | escaped = !escaped && ch == "\\"; 71 | } 72 | return "sp-literal"; 73 | }; 74 | } 75 | 76 | return function(source, startState) { 77 | return tokenizer(source, startState || normal); 78 | }; 79 | })(); 80 | 81 | function indentSparql(context) { 82 | return function(nextChars) { 83 | var firstChar = nextChars && nextChars.charAt(0); 84 | if (/[\]\}]/.test(firstChar)) 85 | while (context && context.type == "pattern") context = context.prev; 86 | 87 | var closing = context && firstChar == matching[context.type]; 88 | if (!context) 89 | return 0; 90 | else if (context.type == "pattern") 91 | return context.col; 92 | else if (context.align) 93 | return context.col - (closing ? context.width : 0); 94 | else 95 | return context.indent + (closing ? 0 : indentUnit); 96 | } 97 | } 98 | 99 | function parseSparql(source) { 100 | var tokens = tokenizeSparql(source); 101 | var context = null, indent = 0, col = 0; 102 | function pushContext(type, width) { 103 | context = {prev: context, indent: indent, col: col, type: type, width: width}; 104 | } 105 | function popContext() { 106 | context = context.prev; 107 | } 108 | 109 | var iter = { 110 | next: function() { 111 | var token = tokens.next(), type = token.style, content = token.content, width = token.value.length; 112 | 113 | if (content == "\n") { 114 | token.indentation = indentSparql(context); 115 | indent = col = 0; 116 | if (context && context.align == null) context.align = false; 117 | } 118 | else if (type == "whitespace" && col == 0) { 119 | indent = width; 120 | } 121 | else if (type != "sp-comment" && context && context.align == null) { 122 | context.align = true; 123 | } 124 | 125 | if (content != "\n") col += width; 126 | 127 | if (/[\[\{\(]/.test(content)) { 128 | pushContext(content, width); 129 | } 130 | else if (/[\]\}\)]/.test(content)) { 131 | while (context && context.type == "pattern") 132 | popContext(); 133 | if (context && content == matching[context.type]) 134 | popContext(); 135 | } 136 | else if (content == "." && context && context.type == "pattern") { 137 | popContext(); 138 | } 139 | else if ((type == "sp-word" || type == "sp-prefixed" || type == "sp-uri" || type == "sp-var" || type == "sp-literal") && 140 | context && /[\{\[]/.test(context.type)) { 141 | pushContext("pattern", width); 142 | } 143 | 144 | return token; 145 | }, 146 | 147 | copy: function() { 148 | var _context = context, _indent = indent, _col = col, _tokenState = tokens.state; 149 | return function(source) { 150 | tokens = tokenizeSparql(source, _tokenState); 151 | context = _context; 152 | indent = _indent; 153 | col = _col; 154 | return iter; 155 | }; 156 | } 157 | }; 158 | return iter; 159 | } 160 | 161 | return {make: parseSparql, electricChars: "}]"}; 162 | })(); 163 | -------------------------------------------------------------------------------- /example/codemirror/js/tokenizejavascript.js: -------------------------------------------------------------------------------- 1 | /* Tokenizer for JavaScript code */ 2 | 3 | var tokenizeJavaScript = (function() { 4 | // Advance the stream until the given character (not preceded by a 5 | // backslash) is encountered, or the end of the line is reached. 6 | function nextUntilUnescaped(source, end) { 7 | var escaped = false; 8 | while (!source.endOfLine()) { 9 | var next = source.next(); 10 | if (next == end && !escaped) 11 | return false; 12 | escaped = !escaped && next == "\\"; 13 | } 14 | return escaped; 15 | } 16 | 17 | // A map of JavaScript's keywords. The a/b/c keyword distinction is 18 | // very rough, but it gives the parser enough information to parse 19 | // correct code correctly (we don't care that much how we parse 20 | // incorrect code). The style information included in these objects 21 | // is used by the highlighter to pick the correct CSS style for a 22 | // token. 23 | var keywords = function(){ 24 | function result(type, style){ 25 | return {type: type, style: "js-" + style}; 26 | } 27 | // keywords that take a parenthised expression, and then a 28 | // statement (if) 29 | var keywordA = result("keyword a", "keyword"); 30 | // keywords that take just a statement (else) 31 | var keywordB = result("keyword b", "keyword"); 32 | // keywords that optionally take an expression, and form a 33 | // statement (return) 34 | var keywordC = result("keyword c", "keyword"); 35 | var operator = result("operator", "keyword"); 36 | var atom = result("atom", "atom"); 37 | return { 38 | "if": keywordA, "while": keywordA, "with": keywordA, 39 | "else": keywordB, "do": keywordB, "try": keywordB, "finally": keywordB, 40 | "return": keywordC, "break": keywordC, "continue": keywordC, "new": keywordC, "delete": keywordC, "throw": keywordC, 41 | "in": operator, "typeof": operator, "instanceof": operator, 42 | "var": result("var", "keyword"), "function": result("function", "keyword"), "catch": result("catch", "keyword"), 43 | "for": result("for", "keyword"), "switch": result("switch", "keyword"), 44 | "case": result("case", "keyword"), "default": result("default", "keyword"), 45 | "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom 46 | }; 47 | }(); 48 | 49 | // Some helper regexps 50 | var isOperatorChar = /[+\-*&%=<>!?|]/; 51 | var isHexDigit = /[0-9A-Fa-f]/; 52 | var isWordChar = /[\w\$_]/; 53 | 54 | // Wrapper around jsToken that helps maintain parser state (whether 55 | // we are inside of a multi-line comment and whether the next token 56 | // could be a regular expression). 57 | function jsTokenState(inside, regexp) { 58 | return function(source, setState) { 59 | var newInside = inside; 60 | var type = jsToken(inside, regexp, source, function(c) {newInside = c;}); 61 | var newRegexp = type.type == "operator" || type.type == "keyword c" || type.type.match(/^[\[{}\(,;:]$/); 62 | if (newRegexp != regexp || newInside != inside) 63 | setState(jsTokenState(newInside, newRegexp)); 64 | return type; 65 | }; 66 | } 67 | 68 | // The token reader, intended to be used by the tokenizer from 69 | // tokenize.js (through jsTokenState). Advances the source stream 70 | // over a token, and returns an object containing the type and style 71 | // of that token. 72 | function jsToken(inside, regexp, source, setInside) { 73 | function readHexNumber(){ 74 | source.next(); // skip the 'x' 75 | source.nextWhileMatches(isHexDigit); 76 | return {type: "number", style: "js-atom"}; 77 | } 78 | 79 | function readNumber() { 80 | source.nextWhileMatches(/[0-9]/); 81 | if (source.equals(".")){ 82 | source.next(); 83 | source.nextWhileMatches(/[0-9]/); 84 | } 85 | if (source.equals("e") || source.equals("E")){ 86 | source.next(); 87 | if (source.equals("-")) 88 | source.next(); 89 | source.nextWhileMatches(/[0-9]/); 90 | } 91 | return {type: "number", style: "js-atom"}; 92 | } 93 | // Read a word, look it up in keywords. If not found, it is a 94 | // variable, otherwise it is a keyword of the type found. 95 | function readWord() { 96 | source.nextWhileMatches(isWordChar); 97 | var word = source.get(); 98 | var known = keywords.hasOwnProperty(word) && keywords.propertyIsEnumerable(word) && keywords[word]; 99 | return known ? {type: known.type, style: known.style, content: word} : 100 | {type: "variable", style: "js-variable", content: word}; 101 | } 102 | function readRegexp() { 103 | nextUntilUnescaped(source, "/"); 104 | source.nextWhileMatches(/[gi]/); 105 | return {type: "regexp", style: "js-string"}; 106 | } 107 | // Mutli-line comments are tricky. We want to return the newlines 108 | // embedded in them as regular newline tokens, and then continue 109 | // returning a comment token for every line of the comment. So 110 | // some state has to be saved (inside) to indicate whether we are 111 | // inside a /* */ sequence. 112 | function readMultilineComment(start){ 113 | var newInside = "/*"; 114 | var maybeEnd = (start == "*"); 115 | while (true) { 116 | if (source.endOfLine()) 117 | break; 118 | var next = source.next(); 119 | if (next == "/" && maybeEnd){ 120 | newInside = null; 121 | break; 122 | } 123 | maybeEnd = (next == "*"); 124 | } 125 | setInside(newInside); 126 | return {type: "comment", style: "js-comment"}; 127 | } 128 | function readOperator() { 129 | source.nextWhileMatches(isOperatorChar); 130 | return {type: "operator", style: "js-operator"}; 131 | } 132 | function readString(quote) { 133 | var endBackSlash = nextUntilUnescaped(source, quote); 134 | setInside(endBackSlash ? quote : null); 135 | return {type: "string", style: "js-string"}; 136 | } 137 | 138 | // Fetch the next token. Dispatches on first character in the 139 | // stream, or first two characters when the first is a slash. 140 | if (inside == "\"" || inside == "'") 141 | return readString(inside); 142 | var ch = source.next(); 143 | if (inside == "/*") 144 | return readMultilineComment(ch); 145 | else if (ch == "\"" || ch == "'") 146 | return readString(ch); 147 | // with punctuation, the type of the token is the symbol itself 148 | else if (/[\[\]{}\(\),;\:\.]/.test(ch)) 149 | return {type: ch, style: "js-punctuation"}; 150 | else if (ch == "0" && (source.equals("x") || source.equals("X"))) 151 | return readHexNumber(); 152 | else if (/[0-9]/.test(ch)) 153 | return readNumber(); 154 | else if (ch == "/"){ 155 | if (source.equals("*")) 156 | { source.next(); return readMultilineComment(ch); } 157 | else if (source.equals("/")) 158 | { nextUntilUnescaped(source, null); return {type: "comment", style: "js-comment"};} 159 | else if (regexp) 160 | return readRegexp(); 161 | else 162 | return readOperator(); 163 | } 164 | else if (isOperatorChar.test(ch)) 165 | return readOperator(); 166 | else 167 | return readWord(); 168 | } 169 | 170 | // The external interface to the tokenizer. 171 | return function(source, startState) { 172 | return tokenizer(source, startState || jsTokenState(false, true)); 173 | }; 174 | })(); 175 | -------------------------------------------------------------------------------- /example/codemirror/js/parsexml.js: -------------------------------------------------------------------------------- 1 | /* This file defines an XML parser, with a few kludges to make it 2 | * useable for HTML. autoSelfClosers defines a set of tag names that 3 | * are expected to not have a closing tag, and doNotIndent specifies 4 | * the tags inside of which no indentation should happen (see Config 5 | * object). These can be disabled by passing the editor an object like 6 | * {useHTMLKludges: false} as parserConfig option. 7 | */ 8 | 9 | var XMLParser = Editor.Parser = (function() { 10 | var Kludges = { 11 | autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true, 12 | "meta": true, "col": true, "frame": true, "base": true, "area": true}, 13 | doNotIndent: {"pre": true, "!cdata": true} 14 | }; 15 | var NoKludges = {autoSelfClosers: {}, doNotIndent: {"!cdata": true}}; 16 | var UseKludges = Kludges; 17 | var alignCDATA = false; 18 | 19 | // Simple stateful tokenizer for XML documents. Returns a 20 | // MochiKit-style iterator, with a state property that contains a 21 | // function encapsulating the current state. See tokenize.js. 22 | var tokenizeXML = (function() { 23 | function inText(source, setState) { 24 | var ch = source.next(); 25 | if (ch == "<") { 26 | if (source.equals("!")) { 27 | source.next(); 28 | if (source.equals("[")) { 29 | if (source.lookAhead("[CDATA[", true)) { 30 | setState(inBlock("xml-cdata", "]]>")); 31 | return null; 32 | } 33 | else { 34 | return "xml-text"; 35 | } 36 | } 37 | else if (source.lookAhead("--", true)) { 38 | setState(inBlock("xml-comment", "-->")); 39 | return null; 40 | } 41 | else { 42 | return "xml-text"; 43 | } 44 | } 45 | else if (source.equals("?")) { 46 | source.next(); 47 | source.nextWhileMatches(/[\w\._\-]/); 48 | setState(inBlock("xml-processing", "?>")); 49 | return "xml-processing"; 50 | } 51 | else { 52 | if (source.equals("/")) source.next(); 53 | setState(inTag); 54 | return "xml-punctuation"; 55 | } 56 | } 57 | else if (ch == "&") { 58 | while (!source.endOfLine()) { 59 | if (source.next() == ";") 60 | break; 61 | } 62 | return "xml-entity"; 63 | } 64 | else { 65 | source.nextWhileMatches(/[^&<\n]/); 66 | return "xml-text"; 67 | } 68 | } 69 | 70 | function inTag(source, setState) { 71 | var ch = source.next(); 72 | if (ch == ">") { 73 | setState(inText); 74 | return "xml-punctuation"; 75 | } 76 | else if (/[?\/]/.test(ch) && source.equals(">")) { 77 | source.next(); 78 | setState(inText); 79 | return "xml-punctuation"; 80 | } 81 | else if (ch == "=") { 82 | return "xml-punctuation"; 83 | } 84 | else if (/[\'\"]/.test(ch)) { 85 | setState(inAttribute(ch)); 86 | return null; 87 | } 88 | else { 89 | source.nextWhileMatches(/[^\s\u00a0=<>\"\'\/?]/); 90 | return "xml-name"; 91 | } 92 | } 93 | 94 | function inAttribute(quote) { 95 | return function(source, setState) { 96 | while (!source.endOfLine()) { 97 | if (source.next() == quote) { 98 | setState(inTag); 99 | break; 100 | } 101 | } 102 | return "xml-attribute"; 103 | }; 104 | } 105 | 106 | function inBlock(style, terminator) { 107 | return function(source, setState) { 108 | while (!source.endOfLine()) { 109 | if (source.lookAhead(terminator, true)) { 110 | setState(inText); 111 | break; 112 | } 113 | source.next(); 114 | } 115 | return style; 116 | }; 117 | } 118 | 119 | return function(source, startState) { 120 | return tokenizer(source, startState || inText); 121 | }; 122 | })(); 123 | 124 | // The parser. The structure of this function largely follows that of 125 | // parseJavaScript in parsejavascript.js (there is actually a bit more 126 | // shared code than I'd like), but it is quite a bit simpler. 127 | function parseXML(source) { 128 | var tokens = tokenizeXML(source), token; 129 | var cc = [base]; 130 | var tokenNr = 0, indented = 0; 131 | var currentTag = null, context = null; 132 | var consume; 133 | 134 | function push(fs) { 135 | for (var i = fs.length - 1; i >= 0; i--) 136 | cc.push(fs[i]); 137 | } 138 | function cont() { 139 | push(arguments); 140 | consume = true; 141 | } 142 | function pass() { 143 | push(arguments); 144 | consume = false; 145 | } 146 | 147 | function markErr() { 148 | token.style += " xml-error"; 149 | } 150 | function expect(text) { 151 | return function(style, content) { 152 | if (content == text) cont(); 153 | else {markErr(); cont(arguments.callee);} 154 | }; 155 | } 156 | 157 | function pushContext(tagname, startOfLine) { 158 | var noIndent = UseKludges.doNotIndent.hasOwnProperty(tagname) || (context && context.noIndent); 159 | context = {prev: context, name: tagname, indent: indented, startOfLine: startOfLine, noIndent: noIndent}; 160 | } 161 | function popContext() { 162 | context = context.prev; 163 | } 164 | function computeIndentation(baseContext) { 165 | return function(nextChars, current) { 166 | var context = baseContext; 167 | if (context && context.noIndent) 168 | return current; 169 | if (alignCDATA && /")); 189 | else if (style == "xml-cdata") { 190 | if (!context || context.name != "!cdata") pushContext("!cdata"); 191 | if (/\]\]>$/.test(content)) popContext(); 192 | cont(); 193 | } 194 | else if (harmlessTokens.hasOwnProperty(style)) cont(); 195 | else {markErr(); cont();} 196 | } 197 | function tagname(style, content) { 198 | if (style == "xml-name") { 199 | currentTag = content.toLowerCase(); 200 | token.style = "xml-tagname"; 201 | cont(); 202 | } 203 | else { 204 | currentTag = null; 205 | pass(); 206 | } 207 | } 208 | function closetagname(style, content) { 209 | if (style == "xml-name") { 210 | token.style = "xml-tagname"; 211 | if (context && content.toLowerCase() == context.name) popContext(); 212 | else markErr(); 213 | } 214 | cont(); 215 | } 216 | function endtag(startOfLine) { 217 | return function(style, content) { 218 | if (content == "/>" || (content == ">" && UseKludges.autoSelfClosers.hasOwnProperty(currentTag))) cont(); 219 | else if (content == ">") {pushContext(currentTag, startOfLine); cont();} 220 | else {markErr(); cont(arguments.callee);} 221 | }; 222 | } 223 | function attributes(style) { 224 | if (style == "xml-name") {token.style = "xml-attname"; cont(attribute, attributes);} 225 | else pass(); 226 | } 227 | function attribute(style, content) { 228 | if (content == "=") cont(value); 229 | else if (content == ">" || content == "/>") pass(endtag); 230 | else pass(); 231 | } 232 | function value(style) { 233 | if (style == "xml-attribute") cont(value); 234 | else pass(); 235 | } 236 | 237 | return { 238 | indentation: function() {return indented;}, 239 | 240 | next: function(){ 241 | token = tokens.next(); 242 | if (token.style == "whitespace" && tokenNr == 0) 243 | indented = token.value.length; 244 | else 245 | tokenNr++; 246 | if (token.content == "\n") { 247 | indented = tokenNr = 0; 248 | token.indentation = computeIndentation(context); 249 | } 250 | 251 | if (token.style == "whitespace" || token.type == "xml-comment") 252 | return token; 253 | 254 | while(true){ 255 | consume = false; 256 | cc.pop()(token.style, token.content); 257 | if (consume) return token; 258 | } 259 | }, 260 | 261 | copy: function(){ 262 | var _cc = cc.concat([]), _tokenState = tokens.state, _context = context; 263 | var parser = this; 264 | 265 | return function(input){ 266 | cc = _cc.concat([]); 267 | tokenNr = indented = 0; 268 | context = _context; 269 | tokens = tokenizeXML(input, _tokenState); 270 | return parser; 271 | }; 272 | } 273 | }; 274 | } 275 | 276 | return { 277 | make: parseXML, 278 | electricChars: "/", 279 | configure: function(config) { 280 | if (config.useHTMLKludges != null) 281 | UseKludges = config.useHTMLKludges ? Kludges : NoKludges; 282 | if (config.alignCDATA) 283 | alignCDATA = config.alignCDATA; 284 | } 285 | }; 286 | })(); 287 | -------------------------------------------------------------------------------- /out/js2cs.coffee: -------------------------------------------------------------------------------- 1 | pegjs: require("./parser") 2 | parser: pegjs.parser 3 | sys: require("sys") 4 | fs: require("fs") 5 | p: (obj)-> 6 | obj_inspect: sys.inspect(obj, yes, 100) 7 | sys.puts(obj_inspect) 8 | String.prototype.trim: -> 9 | str_to_return: @replace(/^\s*/, "") 10 | str_to_return: str_to_return.replace(/\s*$/, "") 11 | return str_to_return 12 | _filename: process.argv[process.argv.length - 1] 13 | if process.argv[process.argv.length - 2].substr(0, 2) is "--" 14 | _runmode: process.argv[process.argv.length - 2] 15 | else 16 | _runmode: "--convert" 17 | try 18 | string_raw_js: fs.readFileSync(_filename, "utf8") 19 | catch e 20 | sys.log("Failed to read input file.. Did you specify one?") 21 | process.exit(1) 22 | try 23 | ast: parser.parse(string_raw_js) 24 | catch e 25 | sys.log(e.name + " on line " + e.line + " on column " + e.column + ": " + e.message) 26 | process.exit(1) 27 | output: "" 28 | iteration: 0 29 | indent_level: 0 30 | increaseIndent: -> 31 | indent_level: indent_level + 1 32 | decreaseIndent: -> 33 | indent_level: indent_level - 1 34 | indent: -> 35 | c: 0 36 | while c < indent_level 37 | c = c + 1 38 | addToOut(" ") 39 | addToOut: (out)-> 40 | output: out 41 | removeBlankLines: (out)-> 42 | return_me: out.replace(/\n\n/g, "\n") 43 | while(return_me.indexOf("\n\n") > 0) 44 | return_me: return_me.replace(/\n\n/g, "\n") 45 | return return_me 46 | parseChildNodes: (nodes)-> 47 | i: 0 48 | while i < nodes.length 49 | i = i + 1 50 | _node: nodes[i] 51 | is_last_statement: i < nodes.length - 1 52 | is_just_var: is_last_statement and _node.type is "Variable" 53 | is_break: _node.type is "BreakStatement" 54 | is_labelled_statement: _node.type is "LabelledStatement" 55 | if not is_break and not is_labelled_statement 56 | indent() 57 | if not is_just_var and not is_break and not is_labelled_statement 58 | parseNode(_node) 59 | addToOut("\n") 60 | parseNode: (node)-> 61 | iteration: iteration + 1 62 | if _runmode is "--debug" 63 | sys.puts(iteration + " " + node.type) 64 | p(node) 65 | if _runmode is "--ilevel" 66 | sys.puts(iteration + " (" + indent_level + ") " + node.type + " - " + node.name) 67 | switch node.type 68 | when "Program" 69 | if node.elements 70 | parseChildNodes(node.elements) 71 | when "This" 72 | addToOut("@") 73 | when "Function" 74 | if node.params.length > 0 75 | addToOut("(") 76 | i: 0 77 | while i < node.params.length 78 | i = i + 1 79 | addToOut(node.params[i]) 80 | if i < node.params.length - 1 81 | addToOut(", ") 82 | addToOut(")") 83 | addToOut("->\n") 84 | increaseIndent() 85 | if node.elements 86 | parseChildNodes(node.elements) 87 | decreaseIndent() 88 | when "Block" 89 | increaseIndent() 90 | if node.statements 91 | parseChildNodes(node.statements) 92 | decreaseIndent() 93 | when "SwitchStatement" 94 | addToOut("switch ") 95 | parseNode(node.expression) 96 | addToOut("\n") 97 | increaseIndent() 98 | parseChildNodes(node.clauses) 99 | decreaseIndent() 100 | when "CaseClause" 101 | addToOut("when ") 102 | parseNode(node.selector) 103 | addToOut("\n") 104 | increaseIndent() 105 | if node.statements 106 | parseChildNodes(node.statements) 107 | decreaseIndent() 108 | when "DefaultClause" 109 | addToOut("else ") 110 | if node.statements.length > 1 111 | addToOut("\n") 112 | increaseIndent() 113 | if node.statements 114 | parseChildNodes(node.statements) 115 | decreaseIndent() 116 | else 117 | if node.statements.length is 1 118 | if node.statements 119 | parseNode(node.statements[0]) 120 | when "IfStatement" 121 | if node.condition.operator != "!" 122 | addToOut("if ") 123 | parseNode(node.condition) 124 | else 125 | addToOut("unless ") 126 | parseNode(node.condition.expression) 127 | addToOut("\n") 128 | increaseIndent() 129 | if node.ifStatement.statements 130 | parseChildNodes(node.ifStatement.statements) 131 | decreaseIndent() 132 | if node.elseStatement != null 133 | addToOut("\n") 134 | indent() 135 | addToOut("else") 136 | addToOut("\n") 137 | increaseIndent() 138 | if node.elseStatement.statements 139 | parseChildNodes(node.elseStatement.statements) 140 | decreaseIndent() 141 | when "ForStatement" 142 | parseNode(node.initializer) 143 | addToOut("\n") 144 | indent() 145 | addToOut("while ") 146 | parseNode(node.test) 147 | addToOut("\n") 148 | increaseIndent() 149 | indent() 150 | parseNode(node.counter) 151 | decreaseIndent() 152 | if node.statement 153 | parseNode(node.statement) 154 | when "WhileStatement" 155 | addToOut("while ") 156 | parseNode(node.condition) 157 | addToOut("\n") 158 | if node.statement 159 | parseNode(node.statement) 160 | when "TryStatement" 161 | addToOut("try\n") 162 | parseNode(node.block) 163 | addToOut("\n") 164 | if node["catch"] 165 | addToOut("catch ") 166 | parseNode(node["catch"]) 167 | if node["finally"] 168 | addToOut("finally\n") 169 | parseNode(node["finally"]) 170 | when "Catch" 171 | if node.identifier 172 | addToOut(node.identifier) 173 | addToOut("\n") 174 | parseNode(node.block) 175 | addToOut("\n") 176 | when "Finally" 177 | parseNode(node.block) 178 | when "AssignmentExpression" 179 | parseNode(node.left) 180 | addToOut(": ") 181 | parseNode(node.right) 182 | when "PropertyAssignment" 183 | parseNode(node.name) 184 | addToOut(": ") 185 | parseNode(node.value) 186 | when "PropertyAccess" 187 | parseNode(node.base) 188 | if node.name.type 189 | if node.base.type != "This" 190 | if node.name.type != "FunctionCall" 191 | addToOut("[") 192 | parseNode(node.name) 193 | addToOut("]") 194 | else 195 | addToOut(".") 196 | parseNode(node.name) 197 | else 198 | parseNode(node.name) 199 | else 200 | if node.name.type is undefined or node.name.type is "null" 201 | if node.base.type != "This" 202 | addToOut(".") 203 | addToOut(node.name.trim()) 204 | when "BinaryExpression" 205 | parseNode(node.left) 206 | addToOut(" ") 207 | switch node.operator 208 | when "!" 209 | addToOut("not ") 210 | when "===" 211 | addToOut("is ") 212 | when "==" 213 | addToOut("is ") 214 | when "!==" 215 | addToOut("isnt ") 216 | when "&&" 217 | addToOut("and ") 218 | when "||" 219 | addToOut("or ") 220 | when "," 221 | addToOut("\n") 222 | else 223 | addToOut(node.operator) 224 | addToOut(" ") 225 | parseNode(node.right) 226 | when "UnaryExpression" 227 | switch node.operator 228 | when "!" 229 | addToOut("not ") 230 | else addToOut(node.operator) 231 | parseNode(node.expression) 232 | when "ConditionalExpression" 233 | addToOut("if ") 234 | parseNode(node.condition) 235 | addToOut(" ") 236 | parseNode(node.trueExpression) 237 | addToOut(" else ") 238 | parseNode(node.falseExpression) 239 | when "PostfixExpression" 240 | switch node.operator 241 | when "++" 242 | parseNode(node.expression) 243 | addToOut(" = ") 244 | parseNode(node.expression) 245 | addToOut(" + 1") 246 | when "--" 247 | parseNode(node.expression) 248 | addToOut(" = ") 249 | parseNode(node.expression) 250 | addToOut(" - 1") 251 | addToOut("\n") 252 | when "Variable" 253 | unless node.name.substr(0, 3) is "var" 254 | addToOut(node.name.trim()) 255 | else 256 | if node.name.substr(0, 3) is "var" 257 | addToOut() 258 | when "FunctionCall" 259 | parseNode(node.name) 260 | addToOut("(") 261 | if node.arguments.length > 0 262 | i: 0 263 | while i < node.arguments.length 264 | i = i + 1 265 | parseNode(node.arguments[i]) 266 | if i < node.arguments.length - 1 267 | addToOut(", ") 268 | addToOut(")") 269 | when "StringLiteral" 270 | escapedValue: node.value.replace(/\n/g, "\n") 271 | addToOut(""" + escapedValue + """) 272 | when "NumericLiteral" 273 | addToOut(node.value) 274 | when "RegularExpressionLiteral" 275 | addToOut("/") 276 | addToOut(node.body) 277 | addToOut("/" + node.flags) 278 | when "NullLiteral" 279 | addToOut("null") 280 | when "ArrayLiteral" 281 | if node.elements.length > 0 282 | addToOut("[") 283 | i: 0 284 | while i < node.elements.length 285 | i = i + 1 286 | parseNode(node.elements[i]) 287 | if i < node.elements.length - 1 288 | addToOut(", ") 289 | addToOut("]") 290 | when "ObjectLiteral" 291 | if node.properties.length > 0 292 | addToOut("{\n") 293 | increaseIndent() 294 | if node.properties 295 | parseChildNodes(node.properties) 296 | decreaseIndent() 297 | addToOut("\n}") 298 | when "BooleanLiteral" 299 | if node.value is yes 300 | addToOut("yes") 301 | else 302 | if node.value is no 303 | addToOut("no") 304 | parseNode(ast) 305 | if _runmode is "--convert" 306 | sys.puts(removeBlankLines(output)) 307 | else 308 | if _runmode is "--showjs" 309 | sys.puts("Original JavaScript: ") 310 | sys.puts(string_raw_js) 311 | sys.puts("Generated CoffeeScript: ") 312 | sys.puts(output) 313 | 314 | -------------------------------------------------------------------------------- /out/js2cs.recompiled.js: -------------------------------------------------------------------------------- 1 | var _filename, _runmode, addToOut, ast, decreaseIndent, fs, increaseIndent, indent, indent_level, iteration, output, p, parseChildNodes, parseNode, parser, pegjs, removeBlankLines, string_raw_js, sys; 2 | pegjs = require("./parser"); 3 | parser = pegjs.parser; 4 | sys = require("sys"); 5 | fs = require("fs"); 6 | p = function(obj) { 7 | var obj_inspect; 8 | obj_inspect = sys.inspect(obj, true, 100); 9 | return sys.puts(obj_inspect); 10 | }; 11 | String.prototype.trim = function() { 12 | var str_to_return; 13 | str_to_return = this.replace(/^\s*/, ""); 14 | str_to_return = str_to_return.replace(/\s*$/, ""); 15 | return str_to_return; 16 | }; 17 | _filename = process.argv[process.argv.length - 1]; 18 | process.argv[process.argv.length - 2].substr(0, 2) === "--" ? (_runmode = process.argv[process.argv.length - 2]) : (_runmode = "--convert"); 19 | try { 20 | string_raw_js = fs.readFileSync(_filename, "utf8"); 21 | } catch (e) { 22 | sys.log("Failed to read input file.. Did you specify one?"); 23 | process.exit(1); 24 | } 25 | try { 26 | ast = parser.parse(string_raw_js); 27 | } catch (e) { 28 | sys.log(e.name + " on line " + e.line + " on column " + e.column + ": " + e.message); 29 | process.exit(1); 30 | } 31 | output = ""; 32 | iteration = 0; 33 | indent_level = 0; 34 | increaseIndent = function() { 35 | indent_level = indent_level + 1; 36 | return indent_level; 37 | }; 38 | decreaseIndent = function() { 39 | indent_level = indent_level - 1; 40 | return indent_level; 41 | }; 42 | indent = function() { 43 | var _a, c; 44 | c = 0; 45 | _a = []; 46 | while (c < indent_level) { 47 | _a.push((function() { 48 | c = c + 1; 49 | return addToOut(" "); 50 | })()); 51 | } 52 | return _a; 53 | }; 54 | addToOut = function(out) { 55 | output = out; 56 | return output; 57 | }; 58 | removeBlankLines = function(out) { 59 | var return_me; 60 | return_me = out.replace(/\n\n/g, "\n"); 61 | while ((return_me.indexOf("\n\n") > 0)) { 62 | return_me = return_me.replace(/\n\n/g, "\n"); 63 | } 64 | return return_me; 65 | }; 66 | parseChildNodes = function(nodes) { 67 | var _a, _node, i, is_break, is_just_var, is_labelled_statement, is_last_statement; 68 | i = 0; 69 | _a = []; 70 | while (i < nodes.length) { 71 | _a.push((function() { 72 | i = i + 1; 73 | _node = nodes[i]; 74 | is_last_statement = i < nodes.length - 1; 75 | is_just_var = is_last_statement && _node.type === "Variable"; 76 | is_break = _node.type === "BreakStatement"; 77 | is_labelled_statement = _node.type === "LabelledStatement"; 78 | !is_break && !is_labelled_statement ? indent() : null; 79 | !is_just_var && !is_break && !is_labelled_statement ? parseNode(_node) : null; 80 | return addToOut("\n"); 81 | })()); 82 | } 83 | return _a; 84 | }; 85 | parseNode = function(node) { 86 | var _a, _b, _c, _d, escapedValue, i; 87 | iteration = iteration + 1; 88 | if (_runmode === "--debug") { 89 | sys.puts(iteration + " " + node.type); 90 | p(node); 91 | } 92 | _runmode === "--ilevel" ? sys.puts(iteration + " (" + indent_level + ") " + node.type + " - " + node.name) : null; 93 | if ((_a = node.type) === "Program") { 94 | return node.elements ? parseChildNodes(node.elements) : null; 95 | } else if (_a === "This") { 96 | return addToOut("@"); 97 | } else if (_a === "Function") { 98 | if (node.params.length > 0) { 99 | addToOut("("); 100 | i = 0; 101 | while (i < node.params.length) { 102 | i = i + 1; 103 | addToOut(node.params[i]); 104 | i < node.params.length - 1 ? addToOut(", ") : null; 105 | } 106 | addToOut(")"); 107 | } 108 | addToOut("->\n"); 109 | increaseIndent(); 110 | node.elements ? parseChildNodes(node.elements) : null; 111 | return decreaseIndent(); 112 | } else if (_a === "Block") { 113 | increaseIndent(); 114 | node.statements ? parseChildNodes(node.statements) : null; 115 | return decreaseIndent(); 116 | } else if (_a === "SwitchStatement") { 117 | addToOut("switch "); 118 | parseNode(node.expression); 119 | addToOut("\n"); 120 | increaseIndent(); 121 | parseChildNodes(node.clauses); 122 | return decreaseIndent(); 123 | } else if (_a === "CaseClause") { 124 | addToOut("when "); 125 | parseNode(node.selector); 126 | addToOut("\n"); 127 | increaseIndent(); 128 | node.statements ? parseChildNodes(node.statements) : null; 129 | return decreaseIndent(); 130 | } else if (_a === "DefaultClause") { 131 | addToOut("else "); 132 | if (node.statements.length > 1) { 133 | addToOut("\n"); 134 | increaseIndent(); 135 | node.statements ? parseChildNodes(node.statements) : null; 136 | return decreaseIndent(); 137 | } else { 138 | return node.statements.length === 1 ? node.statements ? parseNode(node.statements[0]) : null : null; 139 | } 140 | } else if (_a === "IfStatement") { 141 | if (node.condition.operator !== "!") { 142 | addToOut("if "); 143 | parseNode(node.condition); 144 | } else { 145 | addToOut("unless "); 146 | parseNode(node.condition.expression); 147 | } 148 | addToOut("\n"); 149 | increaseIndent(); 150 | node.ifStatement.statements ? parseChildNodes(node.ifStatement.statements) : null; 151 | decreaseIndent(); 152 | if (node.elseStatement !== null) { 153 | addToOut("\n"); 154 | indent(); 155 | addToOut("else"); 156 | addToOut("\n"); 157 | increaseIndent(); 158 | node.elseStatement.statements ? parseChildNodes(node.elseStatement.statements) : null; 159 | return decreaseIndent(); 160 | } 161 | } else if (_a === "ForStatement") { 162 | parseNode(node.initializer); 163 | addToOut("\n"); 164 | indent(); 165 | addToOut("while "); 166 | parseNode(node.test); 167 | addToOut("\n"); 168 | increaseIndent(); 169 | indent(); 170 | parseNode(node.counter); 171 | decreaseIndent(); 172 | return node.statement ? parseNode(node.statement) : null; 173 | } else if (_a === "WhileStatement") { 174 | addToOut("while "); 175 | parseNode(node.condition); 176 | addToOut("\n"); 177 | return node.statement ? parseNode(node.statement) : null; 178 | } else if (_a === "TryStatement") { 179 | addToOut("try\n"); 180 | parseNode(node.block); 181 | addToOut("\n"); 182 | if (node["catch"]) { 183 | addToOut("catch "); 184 | parseNode(node["catch"]); 185 | } 186 | if (node["finally"]) { 187 | addToOut("finally\n"); 188 | return parseNode(node["finally"]); 189 | } 190 | } else if (_a === "Catch") { 191 | node.identifier ? addToOut(node.identifier) : null; 192 | addToOut("\n"); 193 | parseNode(node.block); 194 | return addToOut("\n"); 195 | } else if (_a === "Finally") { 196 | return parseNode(node.block); 197 | } else if (_a === "AssignmentExpression") { 198 | parseNode(node.left); 199 | addToOut(": "); 200 | return parseNode(node.right); 201 | } else if (_a === "PropertyAssignment") { 202 | parseNode(node.name); 203 | addToOut(": "); 204 | return parseNode(node.value); 205 | } else if (_a === "PropertyAccess") { 206 | parseNode(node.base); 207 | if (node.name.type) { 208 | if (node.base.type !== "This") { 209 | if (node.name.type !== "FunctionCall") { 210 | addToOut("["); 211 | parseNode(node.name); 212 | return addToOut("]"); 213 | } else { 214 | addToOut("."); 215 | return parseNode(node.name); 216 | } 217 | } else { 218 | return parseNode(node.name); 219 | } 220 | } else { 221 | if (node.name.type === undefined || node.name.type === "null") { 222 | node.base.type !== "This" ? addToOut(".") : null; 223 | return addToOut(node.name.trim()); 224 | } 225 | } 226 | } else if (_a === "BinaryExpression") { 227 | parseNode(node.left); 228 | addToOut(" "); 229 | if ((_b = node.operator) === "!") { 230 | addToOut("not "); 231 | } else if (_b === "===") { 232 | addToOut("is "); 233 | } else if (_b === "==") { 234 | addToOut("is "); 235 | } else if (_b === "!==") { 236 | addToOut("isnt "); 237 | } else if (_b === "&&") { 238 | addToOut("and "); 239 | } else if (_b === "||") { 240 | addToOut("or "); 241 | } else if (_b === ",") { 242 | addToOut("\n"); 243 | } else { 244 | addToOut(node.operator); 245 | addToOut(" "); 246 | } 247 | return parseNode(node.right); 248 | } else if (_a === "UnaryExpression") { 249 | if ((_c = node.operator) === "!") { 250 | addToOut("not "); 251 | } else { 252 | addToOut(node.operator); 253 | } 254 | return parseNode(node.expression); 255 | } else if (_a === "ConditionalExpression") { 256 | addToOut("if "); 257 | parseNode(node.condition); 258 | addToOut(" "); 259 | parseNode(node.trueExpression); 260 | addToOut(" else "); 261 | return parseNode(node.falseExpression); 262 | } else if (_a === "PostfixExpression") { 263 | if ((_d = node.operator) === "++") { 264 | parseNode(node.expression); 265 | addToOut(" = "); 266 | parseNode(node.expression); 267 | addToOut(" + 1"); 268 | } else if (_d === "--") { 269 | parseNode(node.expression); 270 | addToOut(" = "); 271 | parseNode(node.expression); 272 | addToOut(" - 1"); 273 | } 274 | return addToOut("\n"); 275 | } else if (_a === "Variable") { 276 | return !(node.name.substr(0, 3) === "var") ? addToOut(node.name.trim()) : node.name.substr(0, 3) === "var" ? addToOut() : null; 277 | } else if (_a === "FunctionCall") { 278 | parseNode(node.name); 279 | addToOut("("); 280 | if (node.arguments.length > 0) { 281 | i = 0; 282 | while (i < node.arguments.length) { 283 | i = i + 1; 284 | parseNode(node.arguments[i]); 285 | i < node.arguments.length - 1 ? addToOut(", ") : null; 286 | } 287 | } 288 | return addToOut(")"); 289 | } else if (_a === "StringLiteral") { 290 | escapedValue = node.value.replace(/\n/g, "\n"); 291 | return addToOut("+ escapedValue +"); 292 | } else if (_a === "NumericLiteral") { 293 | return addToOut(node.value); 294 | } else if (_a === "RegularExpressionLiteral") { 295 | addToOut("/"); 296 | addToOut(node.body); 297 | return addToOut("/" + node.flags); 298 | } else if (_a === "NullLiteral") { 299 | return addToOut("null"); 300 | } else if (_a === "ArrayLiteral") { 301 | if (node.elements.length > 0) { 302 | addToOut("["); 303 | i = 0; 304 | while (i < node.elements.length) { 305 | i = i + 1; 306 | parseNode(node.elements[i]); 307 | i < node.elements.length - 1 ? addToOut(", ") : null; 308 | } 309 | return addToOut("]"); 310 | } 311 | } else if (_a === "ObjectLiteral") { 312 | if (node.properties.length > 0) { 313 | addToOut("{\n"); 314 | increaseIndent(); 315 | node.properties ? parseChildNodes(node.properties) : null; 316 | decreaseIndent(); 317 | return addToOut("\n}"); 318 | } 319 | } else if (_a === "BooleanLiteral") { 320 | return node.value === true ? addToOut("yes") : node.value === false ? addToOut("no") : null; 321 | } 322 | }; 323 | parseNode(ast); 324 | if (_runmode === "--convert") { 325 | sys.puts(removeBlankLines(output)); 326 | } else { 327 | if (_runmode === "--showjs") { 328 | sys.puts("Original JavaScript: "); 329 | sys.puts(string_raw_js); 330 | sys.puts("Generated CoffeeScript: "); 331 | sys.puts(output); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /js2cs.js: -------------------------------------------------------------------------------- 1 | /*#!/usr/bin/env node*/ 2 | var pegjs = require('./parser'); 3 | var parser = pegjs.parser; 4 | var sys = require('sys'); 5 | var fs = require('fs'); 6 | 7 | /* object inspect method */ 8 | var p = function(obj) 9 | { 10 | var obj_inspect = sys.inspect(obj, true, 100); 11 | sys.puts(obj_inspect); 12 | } 13 | 14 | /* the missing trim method */ 15 | String.prototype.trim = function() 16 | { 17 | var str_to_return = this.replace(/^\s*/, ""); 18 | str_to_return = str_to_return.replace(/\s*$/, ""); 19 | return str_to_return; 20 | } 21 | 22 | /* argument section */ 23 | var _runmode; 24 | var _filename = process.argv[process.argv.length - 1]; 25 | if(process.argv[process.argv.length - 2].substr(0,2) == "--") 26 | { 27 | _runmode = process.argv[process.argv.length - 2]; 28 | } 29 | else 30 | { 31 | _runmode = "--convert"; 32 | } 33 | 34 | /* read input (sync) */ 35 | try 36 | { 37 | var string_raw_js = fs.readFileSync(_filename, "utf8"); 38 | } catch(e) { 39 | sys.log("Failed to read input file.. Did you specify one?"); 40 | process.exit(1); 41 | } 42 | 43 | 44 | /* parse section */ 45 | try{ 46 | var ast = parser.parse(string_raw_js); 47 | } catch(e) { 48 | sys.log(e.name + " on line " + e.line + " on column " + e.column + ": " + e.message); 49 | process.exit(1); 50 | } 51 | 52 | var output = ''; 53 | var iteration = 0; 54 | var indent_level = 0; 55 | var increaseIndent = function() { 56 | indent_level = indent_level + 1; 57 | } 58 | var decreaseIndent = function() { 59 | indent_level = indent_level - 1; 60 | } 61 | var indent = function() 62 | { 63 | for(var c = 0; c < indent_level; c++) 64 | { 65 | addToOut(" "); 66 | } 67 | } 68 | var addToOut = function(out) { 69 | output += out; 70 | } 71 | var removeBlankLines = function(out) { 72 | var return_me = out.replace(/\n\n/g, "\n"); 73 | while (!(return_me.indexOf("\n\n") == -1)) 74 | { 75 | return_me = return_me.replace(/\n\n/g, "\n"); 76 | } 77 | return return_me; 78 | } 79 | 80 | /* calls parseNode on a collection of child nodes (statements, elements, properties, clauses) */ 81 | var parseChildNodes = function(nodes) { 82 | for(var i = 0; i < nodes.length; i++) { 83 | /* some logic */ 84 | _node = nodes[i]; 85 | is_last_statement = (i < nodes.length -1); 86 | is_just_var = (is_last_statement && (_node.type == "Variable")); /* variables are not declared this way in coffee */ 87 | is_break = (_node.type == "BreakStatement"); /* not used in coffee */ 88 | /* also don't parse labelledStatement. it's not used and we can't have empty cases if we wanna self host */ 89 | is_labelled_statement = (_node.type == "LabelledStatement"); 90 | /* indenter */ 91 | 92 | if(!(is_break) && !(is_labelled_statement)) { 93 | indent(); 94 | } 95 | 96 | /* token parser */ 97 | if(!(is_just_var) && !(is_break) && !(is_labelled_statement)) 98 | { 99 | parseNode(_node); 100 | } 101 | /* line breaker */ 102 | /*if((is_last_statement) && !(is_break) && !(is_just_var)) 103 | { 104 | addToOut("\n"); 105 | } 106 | */ 107 | addToOut("\n"); 108 | } 109 | } 110 | 111 | /* eats tokens and makes coffee */ 112 | var parseNode = function(node) { 113 | iteration = iteration + 1; 114 | 115 | if(_runmode == "--debug") 116 | { 117 | sys.puts(iteration + " " + node.type); 118 | p(node); 119 | } 120 | 121 | if(_runmode == "--ilevel") 122 | { 123 | sys.puts(iteration + " (" + indent_level + ") " + node.type + " - " + node.name); 124 | } 125 | 126 | switch(node.type) 127 | { 128 | case("Program"): 129 | if(node.elements) 130 | { 131 | parseChildNodes(node.elements); 132 | } 133 | break; 134 | case("This"): 135 | addToOut("@"); 136 | break; 137 | case("Function"): 138 | if(node.params.length > 0) 139 | { 140 | addToOut("("); 141 | for(var i = 0; i < node.params.length; i++) 142 | { 143 | addToOut(node.params[i]); 144 | if(i < node.params.length - 1) 145 | { 146 | addToOut(", "); 147 | } 148 | } 149 | addToOut(")"); 150 | } 151 | addToOut("->\n"); 152 | increaseIndent(); 153 | if(node.elements) 154 | { 155 | parseChildNodes(node.elements); 156 | } 157 | decreaseIndent(); 158 | break; 159 | case("Block"): 160 | increaseIndent(); 161 | if(node.statements) 162 | { 163 | parseChildNodes(node.statements); 164 | } 165 | decreaseIndent(); 166 | break; 167 | case("SwitchStatement"): 168 | addToOut("switch "); 169 | parseNode(node.expression); 170 | addToOut("\n"); 171 | increaseIndent(); 172 | parseChildNodes(node.clauses); 173 | decreaseIndent(); 174 | break; 175 | case("CaseClause"): 176 | addToOut("when "); 177 | parseNode(node.selector); 178 | addToOut("\n"); 179 | increaseIndent(); 180 | if(node.statements) 181 | { 182 | parseChildNodes(node.statements); 183 | } 184 | decreaseIndent(); 185 | break; 186 | case("DefaultClause"): 187 | addToOut("else "); 188 | if(node.statements.length > 1) 189 | { 190 | addToOut("\n"); 191 | increaseIndent(); 192 | if(node.statements) 193 | { 194 | parseChildNodes(node.statements); 195 | } 196 | decreaseIndent(); 197 | } 198 | else 199 | { 200 | if(node.statements.length == 1) 201 | { 202 | if(node.statements) 203 | { 204 | parseNode(node.statements[0]); 205 | } 206 | } 207 | } 208 | break; 209 | case("IfStatement"): 210 | /* condition */ 211 | if(node.condition.operator != "!") 212 | { 213 | addToOut("if "); 214 | parseNode(node.condition); 215 | } 216 | else 217 | { 218 | addToOut("unless "); 219 | /* skip next node, it's "not" */ 220 | parseNode(node.condition.expression); 221 | } 222 | addToOut("\n"); 223 | /* statements */ 224 | increaseIndent(); 225 | if(node.ifStatement.statements) 226 | { 227 | parseChildNodes(node.ifStatement.statements); 228 | } 229 | decreaseIndent(); 230 | if(node.elseStatement != null) { 231 | addToOut("\n"); 232 | indent(); 233 | addToOut("else"); 234 | addToOut("\n"); 235 | increaseIndent(); 236 | if(node.elseStatement.statements) 237 | { 238 | parseChildNodes(node.elseStatement.statements); 239 | } 240 | decreaseIndent(); 241 | } 242 | break; 243 | case("ForStatement"): 244 | parseNode(node.initializer); 245 | addToOut("\n"); 246 | indent(); 247 | addToOut("while "); 248 | parseNode(node.test); 249 | addToOut("\n"); 250 | increaseIndent(); 251 | indent(); 252 | parseNode(node.counter); 253 | decreaseIndent(); 254 | if(node.statement) 255 | { 256 | parseNode(node.statement); 257 | } 258 | break; 259 | case("WhileStatement"): 260 | addToOut("while "); 261 | parseNode(node.condition); 262 | addToOut("\n"); 263 | if(node.statement) 264 | { 265 | parseNode(node.statement); 266 | } 267 | break; 268 | case("TryStatement"): 269 | addToOut("try\n"); 270 | parseNode(node.block); 271 | addToOut("\n"); 272 | if(node['catch']) { 273 | addToOut("catch "); 274 | parseNode(node['catch']); 275 | } 276 | if(node['finally']) { 277 | addToOut("finally\n"); 278 | parseNode(node['finally']); 279 | } 280 | break; 281 | case("Catch"): 282 | if(node.identifier) 283 | { 284 | addToOut(node.identifier); 285 | } 286 | addToOut("\n"); 287 | parseNode(node.block); 288 | addToOut("\n"); 289 | break; 290 | case("Finally"): 291 | parseNode(node.block); 292 | break; 293 | case("AssignmentExpression"): 294 | parseNode(node.left); 295 | addToOut(": "); 296 | parseNode(node.right); 297 | break; 298 | case("PropertyAssignment"): 299 | parseNode(node.name); 300 | addToOut(": "); 301 | parseNode(node.value); 302 | break; 303 | case("PropertyAccess"): 304 | parseNode(node.base); 305 | if(node.name.type) 306 | { 307 | if(node.base.type != "This") { 308 | if(node.name.type != "FunctionCall") 309 | { 310 | addToOut("["); 311 | parseNode(node.name); 312 | addToOut("]"); 313 | } 314 | else 315 | { 316 | addToOut("."); 317 | parseNode(node.name); 318 | } 319 | } 320 | else 321 | { 322 | parseNode(node.name); 323 | } 324 | } 325 | else 326 | { 327 | if(node.name.type == undefined || node.name.type == "null") 328 | { 329 | if(node.base.type != "This") { addToOut("."); } 330 | addToOut(node.name.trim()); 331 | } 332 | } 333 | 334 | break; 335 | case("BinaryExpression"): 336 | parseNode(node.left); 337 | switch(node.operator) 338 | { 339 | /* switch to "not" and "isnt" or something here */ 340 | case("!"): 341 | addToOut(" not "); 342 | break; 343 | case("==="): 344 | addToOut(" is "); 345 | break; 346 | case("=="): 347 | addToOut(" is "); 348 | break; 349 | case("!=="): 350 | addToOut(" isnt "); 351 | break; 352 | case("&&"): 353 | addToOut(" and "); 354 | break; 355 | case("||"): 356 | addToOut(" or "); 357 | break; 358 | case(","): 359 | addToOut(", "); /* normal mode , for loop \n */ 360 | break; 361 | default: 362 | addToOut(" "); 363 | addToOut(node.operator); 364 | addToOut(" "); 365 | } 366 | parseNode(node.right); 367 | break; 368 | case("UnaryExpression"): 369 | switch(node.operator) 370 | { 371 | case('!'): 372 | addToOut("not "); 373 | break; 374 | default: 375 | addToOut(node.operator); 376 | } 377 | parseNode(node.expression); 378 | break; 379 | case("ConditionalExpression"): 380 | addToOut("if "); 381 | parseNode(node.condition); 382 | addToOut(" "); 383 | parseNode(node.trueExpression); 384 | addToOut(" else "); 385 | parseNode(node.falseExpression); 386 | break; 387 | case("PostfixExpression"): 388 | switch(node.operator) 389 | { 390 | case('++'): 391 | parseNode(node.expression); 392 | addToOut(" = "); 393 | parseNode(node.expression); 394 | addToOut(" + 1"); 395 | break; 396 | case('--'): 397 | parseNode(node.expression); 398 | addToOut(" = "); 399 | parseNode(node.expression); 400 | addToOut(" - 1"); 401 | break; 402 | } 403 | addToOut("\n"); 404 | break; 405 | case("Variable"): 406 | if(!(node.name.substr(0, 3) == "var")) 407 | { 408 | addToOut(node.name.trim()); 409 | } 410 | else 411 | { 412 | if(node.name.substr(0, 3) == "var") 413 | { 414 | addToOut(node.name.substr(4, node.name.length - 4).trim()); 415 | } 416 | } 417 | break; 418 | case("FunctionCall"): 419 | parseNode(node.name); 420 | addToOut("("); 421 | if(node.arguments.length > 0) 422 | { 423 | 424 | for(var i = 0; i < node.arguments.length; i++) 425 | { 426 | parseNode(node.arguments[i]); 427 | if(i < node.arguments.length - 1) 428 | { 429 | addToOut(", "); 430 | } 431 | } 432 | } 433 | addToOut(")"); 434 | break; 435 | case('StringLiteral'): 436 | var escapedValue = node.value.replace(/\n/g, "\\n"); 437 | addToOut('"' + escapedValue + '"'); 438 | break; 439 | case('NumericLiteral'): 440 | addToOut(node.value); 441 | break; 442 | case('RegularExpressionLiteral'): 443 | addToOut("/"); 444 | addToOut(node.body); 445 | addToOut("/" + node.flags); 446 | break; 447 | case('NullLiteral'): 448 | addToOut("null"); 449 | break; 450 | case('ArrayLiteral'): 451 | if(node.elements.length > 0) 452 | { 453 | addToOut("["); 454 | for(var i = 0; i < node.elements.length; i++) 455 | { 456 | parseNode(node.elements[i]); 457 | if(i < node.elements.length - 1) 458 | { 459 | addToOut(", "); 460 | } 461 | } 462 | addToOut("]"); 463 | } 464 | break; 465 | case('ObjectLiteral'): 466 | if(node.properties.length > 0) 467 | { 468 | addToOut("{\n"); 469 | increaseIndent(); 470 | if(node.properties) 471 | { 472 | parseChildNodes(node.properties); 473 | } 474 | decreaseIndent(); 475 | addToOut("\n}"); 476 | } 477 | break; 478 | case('BooleanLiteral'): 479 | if(node.value == true) 480 | { 481 | addToOut("yes"); 482 | } 483 | else 484 | { 485 | if(node.value == false) 486 | { 487 | addToOut("no"); 488 | } 489 | } 490 | break; 491 | } 492 | } 493 | 494 | parseNode(ast); 495 | 496 | if(_runmode == "--convert") 497 | { 498 | sys.puts(removeBlankLines(output)); 499 | } 500 | else 501 | { 502 | if(_runmode == "--showjs") 503 | { 504 | sys.puts("Original JavaScript: "); 505 | sys.puts(string_raw_js); 506 | sys.puts("Generated CoffeeScript: "); 507 | sys.puts(output); 508 | } 509 | } 510 | -------------------------------------------------------------------------------- /example/app.js: -------------------------------------------------------------------------------- 1 | String.prototype.trim = function () { 2 | return this.replace(/^\s*/, "").replace(/\s*$/, ""); 3 | } 4 | 5 | var parseJs = function(js) { 6 | return parser.parse(js); 7 | } 8 | var output = ''; 9 | var error = ''; 10 | var iteration = 0; 11 | var indent_level = 0; 12 | var increaseIndent = function() { 13 | indent_level = indent_level + 1; 14 | } 15 | var decreaseIndent = function() { 16 | indent_level = indent_level - 1; 17 | } 18 | var indent = function() 19 | { 20 | for(var c = 0; c < indent_level; c++) 21 | { 22 | addToOut(" "); 23 | } 24 | } 25 | var addToOut = function(out) { 26 | output += out; 27 | } 28 | var removeBlankLines = function(out) { 29 | var return_me = out.replace(/\n\n/g, "\n"); 30 | while (return_me.indexOf("\n\n") > 0) 31 | { 32 | return_me = return_me.replace(/\n\n/g, "\n"); 33 | } 34 | return return_me; 35 | } 36 | 37 | /* calls parseNode on a collection of child nodes (statements, elements, properties, clauses) */ 38 | var parseChildNodes = function(nodes) { 39 | for(var i = 0; i < nodes.length; i++) { 40 | /* some logic */ 41 | _node = nodes[i]; 42 | is_last_statement = (i < nodes.length -1); 43 | is_just_var = (is_last_statement && (_node.type == "Variable")); /* variables are not declared this way in coffee */ 44 | is_break = (_node.type == "BreakStatement"); /* not used in coffee */ 45 | /* also don't parse labelledStatement. it's not used and we can't have empty cases if we wanna self host */ 46 | is_labelled_statement = (_node.type == "LabelledStatement"); 47 | /* indenter */ 48 | 49 | if(!(is_break) && !(is_labelled_statement)) { 50 | indent(); 51 | } 52 | 53 | /* token parser */ 54 | if(!(is_just_var) && !(is_break) && !(is_labelled_statement)) 55 | { 56 | parseNode(_node); 57 | } 58 | /* line breaker */ 59 | /*if((is_last_statement) && !(is_break) && !(is_just_var)) 60 | { 61 | addToOut("\n"); 62 | } 63 | */ 64 | addToOut("\n"); 65 | } 66 | } 67 | 68 | /* eats tokens and makes coffee */ 69 | var parseNode = function(node) { 70 | switch(node.type) 71 | { 72 | case("Program"): 73 | if(node.elements) 74 | { 75 | parseChildNodes(node.elements); 76 | } 77 | break; 78 | case("This"): 79 | addToOut("@"); 80 | break; 81 | case("Function"): 82 | if(node.params.length > 0) 83 | { 84 | addToOut("("); 85 | for(var i = 0; i < node.params.length; i++) 86 | { 87 | /* this tokenizer is probably broken. node.params node should be parsed. */ 88 | 89 | addToOut(node.params[i]); 90 | 91 | /*arseNode(node.params[i]);*/ 92 | if(i < node.params.length - 1) 93 | { 94 | addToOut(", "); 95 | } 96 | } 97 | addToOut(")"); 98 | } 99 | addToOut("->\n"); 100 | increaseIndent(); 101 | if(node.elements) 102 | { 103 | parseChildNodes(node.elements); 104 | } 105 | decreaseIndent(); 106 | break; 107 | case("Block"): 108 | increaseIndent(); 109 | if(node.statements) 110 | { 111 | parseChildNodes(node.statements); 112 | } 113 | decreaseIndent(); 114 | break; 115 | case("SwitchStatement"): 116 | addToOut("switch "); 117 | parseNode(node.expression); 118 | addToOut("\n"); 119 | increaseIndent(); 120 | parseChildNodes(node.clauses); 121 | decreaseIndent(); 122 | break; 123 | case("CaseClause"): 124 | addToOut("when "); 125 | parseNode(node.selector); 126 | /* 2 is the minimum because break; is a statement too 127 | if((node.statements.length > 2) || (node.statements.length == 1)) 128 | { 129 | */ 130 | addToOut("\n"); 131 | increaseIndent(); 132 | if(node.statements) 133 | { 134 | parseChildNodes(node.statements); 135 | } 136 | decreaseIndent(); 137 | /* 138 | } 139 | else 140 | { 141 | if(node.statements.length == 2) 142 | { 143 | addToOut(" then "); 144 | if(node.statements) 145 | { 146 | parseNode(node.statements[0]); 147 | } 148 | } 149 | } 150 | */ 151 | break; 152 | case("DefaultClause"): 153 | addToOut("else "); 154 | if(node.statements.length > 1) 155 | { 156 | addToOut("\n"); 157 | increaseIndent(); 158 | if(node.statements) 159 | { 160 | parseChildNodes(node.statements); 161 | } 162 | decreaseIndent(); 163 | } 164 | else 165 | { 166 | if(node.statements.length == 1) 167 | { 168 | if(node.statements) 169 | { 170 | parseNode(node.statements[0]); 171 | } 172 | } 173 | } 174 | break; 175 | case("IfStatement"): 176 | /* condition */ 177 | if(node.condition.operator != "!") 178 | { 179 | addToOut("if "); 180 | parseNode(node.condition); 181 | } 182 | else 183 | { 184 | addToOut("unless "); 185 | /* skip next node, it's "not" */ 186 | parseNode(node.condition.expression); 187 | } 188 | addToOut("\n"); 189 | /* statements */ 190 | increaseIndent(); 191 | if(node.ifStatement.statements) 192 | { 193 | parseChildNodes(node.ifStatement.statements); 194 | } 195 | decreaseIndent(); 196 | if(node.elseStatement != null) { 197 | addToOut("\n"); 198 | indent(); 199 | addToOut("else"); /* limitation: javascript.pegjs doesnt know else 200 | if */ 201 | addToOut("\n"); 202 | increaseIndent(); 203 | if(node.elseStatement.statements) 204 | { 205 | parseChildNodes(node.elseStatement.statements); 206 | } 207 | decreaseIndent(); 208 | } 209 | break; 210 | case("ForStatement"): 211 | /* converts to while because this mode is unsupported */ 212 | parseNode(node.initializer); 213 | addToOut("\n"); 214 | indent(); 215 | addToOut("while "); 216 | parseNode(node.test); 217 | addToOut("\n"); 218 | increaseIndent(); 219 | indent(); 220 | parseNode(node.counter); /* if possible do a test on the counter to see if it's ++i or i++ (they are different) */ 221 | decreaseIndent(); 222 | if(node.statement) 223 | { 224 | parseNode(node.statement); 225 | } 226 | break; 227 | case("WhileStatement"): 228 | addToOut("while "); 229 | parseNode(node.condition); 230 | addToOut("\n"); 231 | if(node.statement) 232 | { 233 | parseNode(node.statement); 234 | } 235 | break; 236 | case("TryStatement"): 237 | /* epic self translating challenge here */ 238 | addToOut("try\n"); 239 | parseNode(node.block); 240 | addToOut("\n"); 241 | if(node['catch']) { 242 | addToOut("catch "); 243 | parseNode(node['catch']); 244 | } 245 | if(node['finally']) { 246 | addToOut("finally\n"); 247 | parseNode(node['finally']); 248 | } 249 | break; 250 | case("Catch"): 251 | if(node.identifier) 252 | { 253 | addToOut(node.identifier); 254 | } 255 | addToOut("\n"); 256 | parseNode(node.block); 257 | addToOut("\n"); 258 | break; 259 | case("Finally"): 260 | parseNode(node.block); 261 | break; 262 | case("AssignmentExpression"): 263 | parseNode(node.left); 264 | addToOut(": "); 265 | parseNode(node.right); 266 | break; 267 | case("PropertyAssignment"): 268 | /*addToOut(node.name);*/ 269 | parseNode(node.name); 270 | addToOut(": "); 271 | parseNode(node.value); 272 | break; 273 | case("PropertyAccess"): 274 | parseNode(node.base); 275 | if(node.name.type) 276 | { 277 | /* 278 | var node_dot_name_is_numeric_literal = (node.name.type == "NumericLiteral"); 279 | var node_dot_name_is_string_literal = (node.name.type == "StringLiteral"); 280 | if(node.base.type != "This" && !(node_dot_name_is_numeric_literal) && !(node_dot_name_is_string_literal)) { addToOut("."); } 281 | 282 | if(node_dot_name_is_numeric_literal || node_dot_name_is_string_literal) { addToOut("["); } 283 | 284 | if(node.base.type == "Variable") 285 | { 286 | addToOut("."); 287 | parseNode(node.name); 288 | } 289 | else 290 | { */ 291 | if(node.base.type != "This") { 292 | if(node.name.type != "FunctionCall") 293 | { 294 | addToOut("["); 295 | parseNode(node.name); 296 | addToOut("]"); 297 | } 298 | else 299 | { 300 | addToOut("."); 301 | parseNode(node.name); 302 | } 303 | } 304 | else 305 | { 306 | parseNode(node.name); 307 | } 308 | /* 309 | else 310 | { 311 | parseNode(node.name); 312 | } 313 | } 314 | 315 | if(node_dot_name_is_numeric_literal || node_dot_name_is_string_literal) { addToOut("]"); } 316 | */ 317 | } 318 | else 319 | { 320 | if(node.name.type == undefined || node.name.type == "null") 321 | { 322 | if(node.base.type != "This") { addToOut("."); } 323 | addToOut(node.name.trim()); 324 | /*if(node.base.type != "This") { addToOut("']"); }*/ 325 | } 326 | } 327 | 328 | break; 329 | case("BinaryExpression"): 330 | parseNode(node.left); 331 | addToOut(" "); 332 | switch(node.operator) 333 | { 334 | /* switch to "not" and "isnt" or something here */ 335 | case("!"): 336 | addToOut("not "); 337 | break; 338 | case("==="): 339 | addToOut("is "); 340 | break; 341 | case("=="): 342 | addToOut("is "); 343 | break; 344 | case("!=="): 345 | addToOut("isnt "); 346 | break; 347 | case("&&"): 348 | addToOut("and "); 349 | break; 350 | case("||"): 351 | addToOut("or "); 352 | break; 353 | case(","): 354 | addToOut("\n"); /* no support for that operator yet. try to evaluate on seperate lines. */ 355 | break; 356 | default: 357 | addToOut(node.operator); 358 | addToOut(" "); 359 | } 360 | parseNode(node.right); 361 | break; 362 | case("UnaryExpression"): 363 | switch(node.operator) 364 | { 365 | case('!'): 366 | addToOut("not "); 367 | break; 368 | default: 369 | addToOut(node.operator); 370 | } 371 | parseNode(node.expression); 372 | break; 373 | case("ConditionalExpression"): 374 | addToOut("if "); 375 | parseNode(node.condition); 376 | addToOut(" "); 377 | parseNode(node.trueExpression); 378 | addToOut(" else "); 379 | parseNode(node.falseExpression); 380 | break; 381 | case("PostfixExpression"): 382 | switch(node.operator) 383 | { 384 | case('++'): 385 | parseNode(node.expression); 386 | addToOut(" = "); 387 | parseNode(node.expression); 388 | addToOut(" + 1"); 389 | break; 390 | case('--'): 391 | parseNode(node.expression); 392 | addToOut(" = "); 393 | parseNode(node.expression); 394 | addToOut(" - 1"); 395 | break; 396 | } 397 | addToOut("\n"); 398 | break; 399 | case("Variable"): 400 | if(!(node.name.substr(0, 3) == "var")) 401 | { 402 | addToOut(node.name.trim()); 403 | } 404 | else 405 | { 406 | if(node.name.substr(0, 3) == "var") 407 | { 408 | /* -5 because of 4 for "var " and 1 for " " after */ 409 | addToOut(node.name.substr(4, node.name.length - 4).trim()); 410 | } 411 | } 412 | break; 413 | case("FunctionCall"): 414 | parseNode(node.name); 415 | addToOut("("); 416 | if(node.arguments.length > 0) 417 | { 418 | 419 | for(var i = 0; i < node.arguments.length; i++) 420 | { 421 | parseNode(node.arguments[i]); 422 | if(i < node.arguments.length - 1) 423 | { 424 | addToOut(", "); 425 | } 426 | } 427 | } 428 | addToOut(")"); 429 | break; 430 | case('StringLiteral'): 431 | /* be sure to escape any control characters you need here with \ */ 432 | var escapedValue = node.value.replace(/\n/g, "\\n"); 433 | addToOut('"' + escapedValue + '"'); 434 | break; 435 | case('NumericLiteral'): 436 | addToOut(node.value); 437 | break; 438 | case('RegularExpressionLiteral'): 439 | addToOut("/"); 440 | addToOut(node.body); 441 | addToOut("/" + node.flags); 442 | break; 443 | case('NullLiteral'): 444 | addToOut("null"); 445 | break; 446 | case('ArrayLiteral'): 447 | if(node.elements.length > 0) 448 | { 449 | addToOut("["); 450 | for(var i = 0; i < node.elements.length; i++) 451 | { 452 | parseNode(node.elements[i]); 453 | if(i < node.elements.length - 1) 454 | { 455 | addToOut(", "); 456 | } 457 | } 458 | addToOut("]"); 459 | } 460 | break; 461 | case('ObjectLiteral'): 462 | if(node.properties.length > 0) 463 | { 464 | addToOut("{\n"); 465 | increaseIndent(); 466 | if(node.properties) 467 | { 468 | parseChildNodes(node.properties); 469 | } 470 | decreaseIndent(); 471 | addToOut("\n}"); 472 | } 473 | break; 474 | case('BooleanLiteral'): 475 | if(node.value == true) 476 | { 477 | addToOut("yes"); 478 | } 479 | else 480 | { 481 | if(node.value == false) 482 | { 483 | addToOut("no"); 484 | } 485 | } 486 | break; 487 | } 488 | } 489 | 490 | /* 491 | parseJs(js); 492 | now i have ast 493 | parseNode(ast); 494 | */ 495 | /* now i have output */ 496 | 497 | 498 | 499 | -------------------------------------------------------------------------------- /example/codemirror/js/parsejavascript.js: -------------------------------------------------------------------------------- 1 | /* Parse function for JavaScript. Makes use of the tokenizer from 2 | * tokenizejavascript.js. Note that your parsers do not have to be 3 | * this complicated -- if you don't want to recognize local variables, 4 | * in many languages it is enough to just look for braces, semicolons, 5 | * parentheses, etc, and know when you are inside a string or comment. 6 | * 7 | * See manual.html for more info about the parser interface. 8 | */ 9 | 10 | var JSParser = Editor.Parser = (function() { 11 | // Token types that can be considered to be atoms. 12 | var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true}; 13 | // Setting that can be used to have JSON data indent properly. 14 | var json = false; 15 | // Constructor for the lexical context objects. 16 | function JSLexical(indented, column, type, align, prev, info) { 17 | // indentation at start of this line 18 | this.indented = indented; 19 | // column at which this scope was opened 20 | this.column = column; 21 | // type of scope ('vardef', 'stat' (statement), 'form' (special form), '[', '{', or '(') 22 | this.type = type; 23 | // '[', '{', or '(' blocks that have any text after their opening 24 | // character are said to be 'aligned' -- any lines below are 25 | // indented all the way to the opening character. 26 | if (align != null) 27 | this.align = align; 28 | // Parent scope, if any. 29 | this.prev = prev; 30 | this.info = info; 31 | } 32 | 33 | // My favourite JavaScript indentation rules. 34 | function indentJS(lexical) { 35 | return function(firstChars) { 36 | var firstChar = firstChars && firstChars.charAt(0), type = lexical.type; 37 | var closing = firstChar == type; 38 | if (type == "vardef") 39 | return lexical.indented + 4; 40 | else if (type == "form" && firstChar == "{") 41 | return lexical.indented; 42 | else if (type == "stat" || type == "form") 43 | return lexical.indented + indentUnit; 44 | else if (lexical.info == "switch" && !closing) 45 | return lexical.indented + (/^(?:case|default)\b/.test(firstChars) ? indentUnit : 2 * indentUnit); 46 | else if (lexical.align) 47 | return lexical.column - (closing ? 1 : 0); 48 | else 49 | return lexical.indented + (closing ? 0 : indentUnit); 50 | }; 51 | } 52 | 53 | // The parser-iterator-producing function itself. 54 | function parseJS(input, basecolumn) { 55 | // Wrap the input in a token stream 56 | var tokens = tokenizeJavaScript(input); 57 | // The parser state. cc is a stack of actions that have to be 58 | // performed to finish the current statement. For example we might 59 | // know that we still need to find a closing parenthesis and a 60 | // semicolon. Actions at the end of the stack go first. It is 61 | // initialized with an infinitely looping action that consumes 62 | // whole statements. 63 | var cc = [json ? expressions : statements]; 64 | // Context contains information about the current local scope, the 65 | // variables defined in that, and the scopes above it. 66 | var context = null; 67 | // The lexical scope, used mostly for indentation. 68 | var lexical = new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false); 69 | // Current column, and the indentation at the start of the current 70 | // line. Used to create lexical scope objects. 71 | var column = 0; 72 | var indented = 0; 73 | // Variables which are used by the mark, cont, and pass functions 74 | // below to communicate with the driver loop in the 'next' 75 | // function. 76 | var consume, marked; 77 | 78 | // The iterator object. 79 | var parser = {next: next, copy: copy}; 80 | 81 | function next(){ 82 | // Start by performing any 'lexical' actions (adjusting the 83 | // lexical variable), or the operations below will be working 84 | // with the wrong lexical state. 85 | while(cc[cc.length - 1].lex) 86 | cc.pop()(); 87 | 88 | // Fetch a token. 89 | var token = tokens.next(); 90 | 91 | // Adjust column and indented. 92 | if (token.type == "whitespace" && column == 0) 93 | indented = token.value.length; 94 | column += token.value.length; 95 | if (token.content == "\n"){ 96 | indented = column = 0; 97 | // If the lexical scope's align property is still undefined at 98 | // the end of the line, it is an un-aligned scope. 99 | if (!("align" in lexical)) 100 | lexical.align = false; 101 | // Newline tokens get an indentation function associated with 102 | // them. 103 | token.indentation = indentJS(lexical); 104 | } 105 | // No more processing for meaningless tokens. 106 | if (token.type == "whitespace" || token.type == "comment") 107 | return token; 108 | // When a meaningful token is found and the lexical scope's 109 | // align is undefined, it is an aligned scope. 110 | if (!("align" in lexical)) 111 | lexical.align = true; 112 | 113 | // Execute actions until one 'consumes' the token and we can 114 | // return it. 115 | while(true) { 116 | consume = marked = false; 117 | // Take and execute the topmost action. 118 | cc.pop()(token.type, token.content); 119 | if (consume){ 120 | // Marked is used to change the style of the current token. 121 | if (marked) 122 | token.style = marked; 123 | // Here we differentiate between local and global variables. 124 | else if (token.type == "variable" && inScope(token.content)) 125 | token.style = "js-localvariable"; 126 | return token; 127 | } 128 | } 129 | } 130 | 131 | // This makes a copy of the parser state. It stores all the 132 | // stateful variables in a closure, and returns a function that 133 | // will restore them when called with a new input stream. Note 134 | // that the cc array has to be copied, because it is contantly 135 | // being modified. Lexical objects are not mutated, and context 136 | // objects are not mutated in a harmful way, so they can be shared 137 | // between runs of the parser. 138 | function copy(){ 139 | var _context = context, _lexical = lexical, _cc = cc.concat([]), _tokenState = tokens.state; 140 | 141 | return function copyParser(input){ 142 | context = _context; 143 | lexical = _lexical; 144 | cc = _cc.concat([]); // copies the array 145 | column = indented = 0; 146 | tokens = tokenizeJavaScript(input, _tokenState); 147 | return parser; 148 | }; 149 | } 150 | 151 | // Helper function for pushing a number of actions onto the cc 152 | // stack in reverse order. 153 | function push(fs){ 154 | for (var i = fs.length - 1; i >= 0; i--) 155 | cc.push(fs[i]); 156 | } 157 | // cont and pass are used by the action functions to add other 158 | // actions to the stack. cont will cause the current token to be 159 | // consumed, pass will leave it for the next action. 160 | function cont(){ 161 | push(arguments); 162 | consume = true; 163 | } 164 | function pass(){ 165 | push(arguments); 166 | consume = false; 167 | } 168 | // Used to change the style of the current token. 169 | function mark(style){ 170 | marked = style; 171 | } 172 | 173 | // Push a new scope. Will automatically link the current scope. 174 | function pushcontext(){ 175 | context = {prev: context, vars: {"this": true, "arguments": true}}; 176 | } 177 | // Pop off the current scope. 178 | function popcontext(){ 179 | context = context.prev; 180 | } 181 | // Register a variable in the current scope. 182 | function register(varname){ 183 | if (context){ 184 | mark("js-variabledef"); 185 | context.vars[varname] = true; 186 | } 187 | } 188 | // Check whether a variable is defined in the current scope. 189 | function inScope(varname){ 190 | var cursor = context; 191 | while (cursor) { 192 | if (cursor.vars[varname]) 193 | return true; 194 | cursor = cursor.prev; 195 | } 196 | return false; 197 | } 198 | 199 | // Push a new lexical context of the given type. 200 | function pushlex(type, info) { 201 | var result = function(){ 202 | lexical = new JSLexical(indented, column, type, null, lexical, info) 203 | }; 204 | result.lex = true; 205 | return result; 206 | } 207 | // Pop off the current lexical context. 208 | function poplex(){ 209 | lexical = lexical.prev; 210 | } 211 | poplex.lex = true; 212 | // The 'lex' flag on these actions is used by the 'next' function 213 | // to know they can (and have to) be ran before moving on to the 214 | // next token. 215 | 216 | // Creates an action that discards tokens until it finds one of 217 | // the given type. 218 | function expect(wanted){ 219 | return function expecting(type){ 220 | if (type == wanted) cont(); 221 | else cont(arguments.callee); 222 | }; 223 | } 224 | 225 | // Looks for a statement, and then calls itself. 226 | function statements(type){ 227 | return pass(statement, statements); 228 | } 229 | function expressions(type){ 230 | return pass(expression, expressions); 231 | } 232 | // Dispatches various types of statements based on the type of the 233 | // current token. 234 | function statement(type){ 235 | if (type == "var") cont(pushlex("vardef"), vardef1, expect(";"), poplex); 236 | else if (type == "keyword a") cont(pushlex("form"), expression, statement, poplex); 237 | else if (type == "keyword b") cont(pushlex("form"), statement, poplex); 238 | else if (type == "{") cont(pushlex("}"), block, poplex); 239 | else if (type == "function") cont(functiondef); 240 | else if (type == "for") cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), poplex, statement, poplex); 241 | else if (type == "variable") cont(pushlex("stat"), maybelabel); 242 | else if (type == "switch") cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), block, poplex, poplex); 243 | else if (type == "case") cont(expression, expect(":")); 244 | else if (type == "default") cont(expect(":")); 245 | else if (type == "catch") cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), statement, poplex, popcontext); 246 | else pass(pushlex("stat"), expression, expect(";"), poplex); 247 | } 248 | // Dispatch expression types. 249 | function expression(type){ 250 | if (atomicTypes.hasOwnProperty(type)) cont(maybeoperator); 251 | else if (type == "function") cont(functiondef); 252 | else if (type == "keyword c") cont(expression); 253 | else if (type == "(") cont(pushlex(")"), expression, expect(")"), poplex, maybeoperator); 254 | else if (type == "operator") cont(expression); 255 | else if (type == "[") cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator); 256 | else if (type == "{") cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator); 257 | else cont(); 258 | } 259 | // Called for places where operators, function calls, or 260 | // subscripts are valid. Will skip on to the next action if none 261 | // is found. 262 | function maybeoperator(type){ 263 | if (type == "operator") cont(expression); 264 | else if (type == "(") cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator); 265 | else if (type == ".") cont(property, maybeoperator); 266 | else if (type == "[") cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator); 267 | } 268 | // When a statement starts with a variable name, it might be a 269 | // label. If no colon follows, it's a regular statement. 270 | function maybelabel(type){ 271 | if (type == ":") cont(poplex, statement); 272 | else pass(maybeoperator, expect(";"), poplex); 273 | } 274 | // Property names need to have their style adjusted -- the 275 | // tokenizer thinks they are variables. 276 | function property(type){ 277 | if (type == "variable") {mark("js-property"); cont();} 278 | } 279 | // This parses a property and its value in an object literal. 280 | function objprop(type){ 281 | if (type == "variable") mark("js-property"); 282 | if (atomicTypes.hasOwnProperty(type)) cont(expect(":"), expression); 283 | } 284 | // Parses a comma-separated list of the things that are recognized 285 | // by the 'what' argument. 286 | function commasep(what, end){ 287 | function proceed(type) { 288 | if (type == ",") cont(what, proceed); 289 | else if (type == end) cont(); 290 | else cont(expect(end)); 291 | } 292 | return function commaSeparated(type) { 293 | if (type == end) cont(); 294 | else pass(what, proceed); 295 | }; 296 | } 297 | // Look for statements until a closing brace is found. 298 | function block(type){ 299 | if (type == "}") cont(); 300 | else pass(statement, block); 301 | } 302 | // Variable definitions are split into two actions -- 1 looks for 303 | // a name or the end of the definition, 2 looks for an '=' sign or 304 | // a comma. 305 | function vardef1(type, value){ 306 | if (type == "variable"){register(value); cont(vardef2);} 307 | else cont(); 308 | } 309 | function vardef2(type, value){ 310 | if (value == "=") cont(expression, vardef2); 311 | else if (type == ",") cont(vardef1); 312 | } 313 | // For loops. 314 | function forspec1(type){ 315 | if (type == "var") cont(vardef1, forspec2); 316 | else if (type == ";") pass(forspec2); 317 | else if (type == "variable") cont(formaybein); 318 | else pass(forspec2); 319 | } 320 | function formaybein(type, value){ 321 | if (value == "in") cont(expression); 322 | else cont(maybeoperator, forspec2); 323 | } 324 | function forspec2(type, value){ 325 | if (type == ";") cont(forspec3); 326 | else if (value == "in") cont(expression); 327 | else cont(expression, expect(";"), forspec3); 328 | } 329 | function forspec3(type) { 330 | if (type == ")") pass(); 331 | else cont(expression); 332 | } 333 | // A function definition creates a new context, and the variables 334 | // in its argument list have to be added to this context. 335 | function functiondef(type, value){ 336 | if (type == "variable"){register(value); cont(functiondef);} 337 | else if (type == "(") cont(pushcontext, commasep(funarg, ")"), statement, popcontext); 338 | } 339 | function funarg(type, value){ 340 | if (type == "variable"){register(value); cont();} 341 | } 342 | 343 | return parser; 344 | } 345 | 346 | return { 347 | make: parseJS, 348 | electricChars: "{}:", 349 | configure: function(obj) { 350 | if (obj.json != null) json = obj.json; 351 | } 352 | }; 353 | })(); 354 | -------------------------------------------------------------------------------- /example/codemirror/js/undo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Storage and control for undo information within a CodeMirror 3 | * editor. 'Why on earth is such a complicated mess required for 4 | * that?', I hear you ask. The goal, in implementing this, was to make 5 | * the complexity of storing and reverting undo information depend 6 | * only on the size of the edited or restored content, not on the size 7 | * of the whole document. This makes it necessary to use a kind of 8 | * 'diff' system, which, when applied to a DOM tree, causes some 9 | * complexity and hackery. 10 | * 11 | * In short, the editor 'touches' BR elements as it parses them, and 12 | * the UndoHistory stores these. When nothing is touched in commitDelay 13 | * milliseconds, the changes are committed: It goes over all touched 14 | * nodes, throws out the ones that did not change since last commit or 15 | * are no longer in the document, and assembles the rest into zero or 16 | * more 'chains' -- arrays of adjacent lines. Links back to these 17 | * chains are added to the BR nodes, while the chain that previously 18 | * spanned these nodes is added to the undo history. Undoing a change 19 | * means taking such a chain off the undo history, restoring its 20 | * content (text is saved per line) and linking it back into the 21 | * document. 22 | */ 23 | 24 | // A history object needs to know about the DOM container holding the 25 | // document, the maximum amount of undo levels it should store, the 26 | // delay (of no input) after which it commits a set of changes, and, 27 | // unfortunately, the 'parent' window -- a window that is not in 28 | // designMode, and on which setTimeout works in every browser. 29 | function UndoHistory(container, maxDepth, commitDelay, editor) { 30 | this.container = container; 31 | this.maxDepth = maxDepth; this.commitDelay = commitDelay; 32 | this.editor = editor; this.parent = editor.parent; 33 | // This line object represents the initial, empty editor. 34 | var initial = {text: "", from: null, to: null}; 35 | // As the borders between lines are represented by BR elements, the 36 | // start of the first line and the end of the last one are 37 | // represented by null. Since you can not store any properties 38 | // (links to line objects) in null, these properties are used in 39 | // those cases. 40 | this.first = initial; this.last = initial; 41 | // Similarly, a 'historyTouched' property is added to the BR in 42 | // front of lines that have already been touched, and 'firstTouched' 43 | // is used for the first line. 44 | this.firstTouched = false; 45 | // History is the set of committed changes, touched is the set of 46 | // nodes touched since the last commit. 47 | this.history = []; this.redoHistory = []; this.touched = []; 48 | } 49 | 50 | UndoHistory.prototype = { 51 | // Schedule a commit (if no other touches come in for commitDelay 52 | // milliseconds). 53 | scheduleCommit: function() { 54 | var self = this; 55 | this.parent.clearTimeout(this.commitTimeout); 56 | this.commitTimeout = this.parent.setTimeout(function(){self.tryCommit();}, this.commitDelay); 57 | }, 58 | 59 | // Mark a node as touched. Null is a valid argument. 60 | touch: function(node) { 61 | this.setTouched(node); 62 | this.scheduleCommit(); 63 | }, 64 | 65 | // Undo the last change. 66 | undo: function() { 67 | // Make sure pending changes have been committed. 68 | this.commit(); 69 | 70 | if (this.history.length) { 71 | // Take the top diff from the history, apply it, and store its 72 | // shadow in the redo history. 73 | var item = this.history.pop(); 74 | this.redoHistory.push(this.updateTo(item, "applyChain")); 75 | this.notifyEnvironment(); 76 | return this.chainNode(item); 77 | } 78 | }, 79 | 80 | // Redo the last undone change. 81 | redo: function() { 82 | this.commit(); 83 | if (this.redoHistory.length) { 84 | // The inverse of undo, basically. 85 | var item = this.redoHistory.pop(); 86 | this.addUndoLevel(this.updateTo(item, "applyChain")); 87 | this.notifyEnvironment(); 88 | return this.chainNode(item); 89 | } 90 | }, 91 | 92 | clear: function() { 93 | this.history = []; 94 | this.redoHistory = []; 95 | }, 96 | 97 | // Ask for the size of the un/redo histories. 98 | historySize: function() { 99 | return {undo: this.history.length, redo: this.redoHistory.length}; 100 | }, 101 | 102 | // Push a changeset into the document. 103 | push: function(from, to, lines) { 104 | var chain = []; 105 | for (var i = 0; i < lines.length; i++) { 106 | var end = (i == lines.length - 1) ? to : this.container.ownerDocument.createElement("BR"); 107 | chain.push({from: from, to: end, text: cleanText(lines[i])}); 108 | from = end; 109 | } 110 | this.pushChains([chain], from == null && to == null); 111 | this.notifyEnvironment(); 112 | }, 113 | 114 | pushChains: function(chains, doNotHighlight) { 115 | this.commit(doNotHighlight); 116 | this.addUndoLevel(this.updateTo(chains, "applyChain")); 117 | this.redoHistory = []; 118 | }, 119 | 120 | // Retrieve a DOM node from a chain (for scrolling to it after undo/redo). 121 | chainNode: function(chains) { 122 | for (var i = 0; i < chains.length; i++) { 123 | var start = chains[i][0], node = start && (start.from || start.to); 124 | if (node) return node; 125 | } 126 | }, 127 | 128 | // Clear the undo history, make the current document the start 129 | // position. 130 | reset: function() { 131 | this.history = []; this.redoHistory = []; 132 | }, 133 | 134 | textAfter: function(br) { 135 | return this.after(br).text; 136 | }, 137 | 138 | nodeAfter: function(br) { 139 | return this.after(br).to; 140 | }, 141 | 142 | nodeBefore: function(br) { 143 | return this.before(br).from; 144 | }, 145 | 146 | // Commit unless there are pending dirty nodes. 147 | tryCommit: function() { 148 | if (!window.UndoHistory) return; // Stop when frame has been unloaded 149 | if (this.editor.highlightDirty()) this.commit(true); 150 | else this.scheduleCommit(); 151 | }, 152 | 153 | // Check whether the touched nodes hold any changes, if so, commit 154 | // them. 155 | commit: function(doNotHighlight) { 156 | this.parent.clearTimeout(this.commitTimeout); 157 | // Make sure there are no pending dirty nodes. 158 | if (!doNotHighlight) this.editor.highlightDirty(true); 159 | // Build set of chains. 160 | var chains = this.touchedChains(), self = this; 161 | 162 | if (chains.length) { 163 | this.addUndoLevel(this.updateTo(chains, "linkChain")); 164 | this.redoHistory = []; 165 | this.notifyEnvironment(); 166 | } 167 | }, 168 | 169 | // [ end of public interface ] 170 | 171 | // Update the document with a given set of chains, return its 172 | // shadow. updateFunc should be "applyChain" or "linkChain". In the 173 | // second case, the chains are taken to correspond the the current 174 | // document, and only the state of the line data is updated. In the 175 | // first case, the content of the chains is also pushed iinto the 176 | // document. 177 | updateTo: function(chains, updateFunc) { 178 | var shadows = [], dirty = []; 179 | for (var i = 0; i < chains.length; i++) { 180 | shadows.push(this.shadowChain(chains[i])); 181 | dirty.push(this[updateFunc](chains[i])); 182 | } 183 | if (updateFunc == "applyChain") 184 | this.notifyDirty(dirty); 185 | return shadows; 186 | }, 187 | 188 | // Notify the editor that some nodes have changed. 189 | notifyDirty: function(nodes) { 190 | forEach(nodes, method(this.editor, "addDirtyNode")) 191 | this.editor.scheduleHighlight(); 192 | }, 193 | 194 | notifyEnvironment: function() { 195 | if (this.onChange) this.onChange(); 196 | // Used by the line-wrapping line-numbering code. 197 | if (window.frameElement && window.frameElement.CodeMirror.updateNumbers) 198 | window.frameElement.CodeMirror.updateNumbers(); 199 | }, 200 | 201 | // Link a chain into the DOM nodes (or the first/last links for null 202 | // nodes). 203 | linkChain: function(chain) { 204 | for (var i = 0; i < chain.length; i++) { 205 | var line = chain[i]; 206 | if (line.from) line.from.historyAfter = line; 207 | else this.first = line; 208 | if (line.to) line.to.historyBefore = line; 209 | else this.last = line; 210 | } 211 | }, 212 | 213 | // Get the line object after/before a given node. 214 | after: function(node) { 215 | return node ? node.historyAfter : this.first; 216 | }, 217 | before: function(node) { 218 | return node ? node.historyBefore : this.last; 219 | }, 220 | 221 | // Mark a node as touched if it has not already been marked. 222 | setTouched: function(node) { 223 | if (node) { 224 | if (!node.historyTouched) { 225 | this.touched.push(node); 226 | node.historyTouched = true; 227 | } 228 | } 229 | else { 230 | this.firstTouched = true; 231 | } 232 | }, 233 | 234 | // Store a new set of undo info, throw away info if there is more of 235 | // it than allowed. 236 | addUndoLevel: function(diffs) { 237 | this.history.push(diffs); 238 | if (this.history.length > this.maxDepth) 239 | this.history.shift(); 240 | }, 241 | 242 | // Build chains from a set of touched nodes. 243 | touchedChains: function() { 244 | var self = this; 245 | 246 | // The temp system is a crummy hack to speed up determining 247 | // whether a (currently touched) node has a line object associated 248 | // with it. nullTemp is used to store the object for the first 249 | // line, other nodes get it stored in their historyTemp property. 250 | var nullTemp = null; 251 | function temp(node) {return node ? node.historyTemp : nullTemp;} 252 | function setTemp(node, line) { 253 | if (node) node.historyTemp = line; 254 | else nullTemp = line; 255 | } 256 | 257 | function buildLine(node) { 258 | var text = []; 259 | for (var cur = node ? node.nextSibling : self.container.firstChild; 260 | cur && !isBR(cur); cur = cur.nextSibling) 261 | if (cur.currentText) text.push(cur.currentText); 262 | return {from: node, to: cur, text: cleanText(text.join(""))}; 263 | } 264 | 265 | // Filter out unchanged lines and nodes that are no longer in the 266 | // document. Build up line objects for remaining nodes. 267 | var lines = []; 268 | if (self.firstTouched) self.touched.push(null); 269 | forEach(self.touched, function(node) { 270 | if (node && node.parentNode != self.container) return; 271 | 272 | if (node) node.historyTouched = false; 273 | else self.firstTouched = false; 274 | 275 | var line = buildLine(node), shadow = self.after(node); 276 | if (!shadow || shadow.text != line.text || shadow.to != line.to) { 277 | lines.push(line); 278 | setTemp(node, line); 279 | } 280 | }); 281 | 282 | // Get the BR element after/before the given node. 283 | function nextBR(node, dir) { 284 | var link = dir + "Sibling", search = node[link]; 285 | while (search && !isBR(search)) 286 | search = search[link]; 287 | return search; 288 | } 289 | 290 | // Assemble line objects into chains by scanning the DOM tree 291 | // around them. 292 | var chains = []; self.touched = []; 293 | forEach(lines, function(line) { 294 | // Note that this makes the loop skip line objects that have 295 | // been pulled into chains by lines before them. 296 | if (!temp(line.from)) return; 297 | 298 | var chain = [], curNode = line.from, safe = true; 299 | // Put any line objects (referred to by temp info) before this 300 | // one on the front of the array. 301 | while (true) { 302 | var curLine = temp(curNode); 303 | if (!curLine) { 304 | if (safe) break; 305 | else curLine = buildLine(curNode); 306 | } 307 | chain.unshift(curLine); 308 | setTemp(curNode, null); 309 | if (!curNode) break; 310 | safe = self.after(curNode); 311 | curNode = nextBR(curNode, "previous"); 312 | } 313 | curNode = line.to; safe = self.before(line.from); 314 | // Add lines after this one at end of array. 315 | while (true) { 316 | if (!curNode) break; 317 | var curLine = temp(curNode); 318 | if (!curLine) { 319 | if (safe) break; 320 | else curLine = buildLine(curNode); 321 | } 322 | chain.push(curLine); 323 | setTemp(curNode, null); 324 | safe = self.before(curNode); 325 | curNode = nextBR(curNode, "next"); 326 | } 327 | chains.push(chain); 328 | }); 329 | 330 | return chains; 331 | }, 332 | 333 | // Find the 'shadow' of a given chain by following the links in the 334 | // DOM nodes at its start and end. 335 | shadowChain: function(chain) { 336 | var shadows = [], next = this.after(chain[0].from), end = chain[chain.length - 1].to; 337 | while (true) { 338 | shadows.push(next); 339 | var nextNode = next.to; 340 | if (!nextNode || nextNode == end) 341 | break; 342 | else 343 | next = nextNode.historyAfter || this.before(end); 344 | // (The this.before(end) is a hack -- FF sometimes removes 345 | // properties from BR nodes, in which case the best we can hope 346 | // for is to not break.) 347 | } 348 | return shadows; 349 | }, 350 | 351 | // Update the DOM tree to contain the lines specified in a given 352 | // chain, link this chain into the DOM nodes. 353 | applyChain: function(chain) { 354 | // Some attempt is made to prevent the cursor from jumping 355 | // randomly when an undo or redo happens. It still behaves a bit 356 | // strange sometimes. 357 | var cursor = select.cursorPos(this.container, false), self = this; 358 | 359 | // Remove all nodes in the DOM tree between from and to (null for 360 | // start/end of container). 361 | function removeRange(from, to) { 362 | var pos = from ? from.nextSibling : self.container.firstChild; 363 | while (pos != to) { 364 | var temp = pos.nextSibling; 365 | removeElement(pos); 366 | pos = temp; 367 | } 368 | } 369 | 370 | var start = chain[0].from, end = chain[chain.length - 1].to; 371 | // Clear the space where this change has to be made. 372 | removeRange(start, end); 373 | 374 | // Insert the content specified by the chain into the DOM tree. 375 | for (var i = 0; i < chain.length; i++) { 376 | var line = chain[i]; 377 | // The start and end of the space are already correct, but BR 378 | // tags inside it have to be put back. 379 | if (i > 0) 380 | self.container.insertBefore(line.from, end); 381 | 382 | // Add the text. 383 | var node = makePartSpan(fixSpaces(line.text), this.container.ownerDocument); 384 | self.container.insertBefore(node, end); 385 | // See if the cursor was on this line. Put it back, adjusting 386 | // for changed line length, if it was. 387 | if (cursor && cursor.node == line.from) { 388 | var cursordiff = 0; 389 | var prev = this.after(line.from); 390 | if (prev && i == chain.length - 1) { 391 | // Only adjust if the cursor is after the unchanged part of 392 | // the line. 393 | for (var match = 0; match < cursor.offset && 394 | line.text.charAt(match) == prev.text.charAt(match); match++); 395 | if (cursor.offset > match) 396 | cursordiff = line.text.length - prev.text.length; 397 | } 398 | select.setCursorPos(this.container, {node: line.from, offset: Math.max(0, cursor.offset + cursordiff)}); 399 | } 400 | // Cursor was in removed line, this is last new line. 401 | else if (cursor && (i == chain.length - 1) && cursor.node && cursor.node.parentNode != this.container) { 402 | select.setCursorPos(this.container, {node: line.from, offset: line.text.length}); 403 | } 404 | } 405 | 406 | // Anchor the chain in the DOM tree. 407 | this.linkChain(chain); 408 | return start; 409 | } 410 | }; 411 | -------------------------------------------------------------------------------- /example/example.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | JS2CS Converter 8 | 9 | 10 | 11 | 12 | 13 | 42 | 43 | 44 | 45 |
46 |

JavaScript to CoffeeScript Compiler

47 |

Syntax error:

48 | 49 |

Input JavaScript

50 | 615 |
616 |

Resultant CoffeeScript

617 | 618 | 619 |

this is a product of mindynamics and jsilver. made with love

620 |
621 | 622 | 623 | -------------------------------------------------------------------------------- /example/codemirror/js/codemirror.js: -------------------------------------------------------------------------------- 1 | /* CodeMirror main module 2 | * 3 | * Implements the CodeMirror constructor and prototype, which take care 4 | * of initializing the editor frame, and providing the outside interface. 5 | */ 6 | 7 | // The CodeMirrorConfig object is used to specify a default 8 | // configuration. If you specify such an object before loading this 9 | // file, the values you put into it will override the defaults given 10 | // below. You can also assign to it after loading. 11 | var CodeMirrorConfig = window.CodeMirrorConfig || {}; 12 | 13 | var CodeMirror = (function(){ 14 | function setDefaults(object, defaults) { 15 | for (var option in defaults) { 16 | if (!object.hasOwnProperty(option)) 17 | object[option] = defaults[option]; 18 | } 19 | } 20 | function forEach(array, action) { 21 | for (var i = 0; i < array.length; i++) 22 | action(array[i]); 23 | } 24 | 25 | // These default options can be overridden by passing a set of 26 | // options to a specific CodeMirror constructor. See manual.html for 27 | // their meaning. 28 | setDefaults(CodeMirrorConfig, { 29 | stylesheet: [], 30 | path: "", 31 | parserfile: [], 32 | basefiles: ["util.js", "stringstream.js", "select.js", "undo.js", "editor.js", "tokenize.js"], 33 | iframeClass: null, 34 | passDelay: 200, 35 | passTime: 50, 36 | lineNumberDelay: 200, 37 | lineNumberTime: 50, 38 | continuousScanning: false, 39 | saveFunction: null, 40 | onChange: null, 41 | undoDepth: 50, 42 | undoDelay: 800, 43 | disableSpellcheck: true, 44 | textWrapping: true, 45 | readOnly: false, 46 | width: "", 47 | height: "300px", 48 | minHeight: 100, 49 | autoMatchParens: false, 50 | parserConfig: null, 51 | tabMode: "indent", // or "spaces", "default", "shift" 52 | reindentOnLoad: false, 53 | activeTokens: null, 54 | cursorActivity: null, 55 | lineNumbers: false, 56 | indentUnit: 2, 57 | domain: null 58 | }); 59 | 60 | function addLineNumberDiv(container) { 61 | var nums = document.createElement("DIV"), 62 | scroller = document.createElement("DIV"); 63 | nums.style.position = "absolute"; 64 | nums.style.height = "100%"; 65 | if (nums.style.setExpression) { 66 | try {nums.style.setExpression("height", "this.previousSibling.offsetHeight + 'px'");} 67 | catch(e) {} // Seems to throw 'Not Implemented' on some IE8 versions 68 | } 69 | nums.style.top = "0px"; 70 | nums.style.left = "0px"; 71 | nums.style.overflow = "hidden"; 72 | container.appendChild(nums); 73 | scroller.className = "CodeMirror-line-numbers"; 74 | nums.appendChild(scroller); 75 | scroller.innerHTML = "
1
"; 76 | return nums; 77 | } 78 | 79 | function frameHTML(options) { 80 | if (typeof options.parserfile == "string") 81 | options.parserfile = [options.parserfile]; 82 | if (typeof options.stylesheet == "string") 83 | options.stylesheet = [options.stylesheet]; 84 | 85 | var html = [""]; 86 | // Hack to work around a bunch of IE8-specific problems. 87 | html.push(""); 88 | forEach(options.stylesheet, function(file) { 89 | html.push(""); 90 | }); 91 | forEach(options.basefiles.concat(options.parserfile), function(file) { 92 | if (!/^https?:/.test(file)) file = options.path + file; 93 | html.push("