├── 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
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 |
Syntax error:
48 | 49 |Input JavaScript
50 | 615 |Resultant CoffeeScript
617 | 618 | 619 |this is a product of mindynamics and jsilver. made with love
620 |