├── web ├── js │ ├── chapter │ │ ├── error.js │ │ ├── event.js │ │ ├── basics.js │ │ ├── control.js │ │ ├── functions.js │ │ ├── modularity.js │ │ ├── regexp.js │ │ ├── xhr.js │ │ ├── binaryheap.js │ │ ├── web.js │ │ ├── intro.js │ │ ├── oo.js │ │ ├── dom.js │ │ ├── event_example.js │ │ ├── data.js │ │ ├── fp.js │ │ └── search.js │ ├── before.js │ ├── params.js │ ├── TestModule.js │ ├── sendreaction.html │ ├── Dictionary.js │ ├── ObjectTools.js │ ├── DOMTools.js │ ├── FunctionalTools.js │ ├── base-env.js │ └── ejs.js ├── files │ ├── fruit.txt │ ├── fruit2.json │ ├── fruit.json │ ├── fruit2.xml │ └── fruit.xml ├── img │ ├── hr.png │ ├── ejs.png │ ├── eyes.png │ ├── heap.png │ ├── hide.png │ ├── html.png │ ├── show.png │ ├── tree.png │ ├── Hiva Oa.png │ ├── close.png │ ├── height.png │ ├── ostrich.png │ ├── react.png │ ├── resize.png │ ├── runcode.png │ ├── sheep.png │ ├── yinyang.png │ ├── loadcode.png │ ├── diagonalpath.png │ ├── hr-diamonds.png │ ├── objectchain.png │ ├── runcode_done.png │ ├── sokoban │ │ ├── exit.png │ │ ├── wall.png │ │ ├── empty.png │ │ ├── player.gif │ │ ├── player.png │ │ ├── boulder.gif │ │ └── boulder.png │ ├── florence_flask.png │ └── xkcd_regular_expressions.png ├── view.html ├── css │ ├── sokoban.css │ ├── highlight.css │ ├── console.css │ ├── book.css │ └── codemirror.css ├── example_alchemy.html ├── example_events.html ├── imprint.html ├── example_getinfo.html ├── paper.html ├── donate.html ├── index.html └── errata.html ├── renderer ├── make.sh ├── Html.hs ├── Highlight.hs └── Main.hs ├── renderer_tex ├── make.sh └── Main.hs ├── publish.sh └── README /web/js/chapter/error.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/js/chapter/event.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/js/chapter/basics.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/js/chapter/control.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/js/chapter/functions.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/js/chapter/modularity.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/js/chapter/regexp.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/files/fruit.txt: -------------------------------------------------------------------------------- 1 | apples, oranges, bananas -------------------------------------------------------------------------------- /renderer/make.sh: -------------------------------------------------------------------------------- 1 | ghc --make Main.hs -o ../render 2 | -------------------------------------------------------------------------------- /web/js/chapter/xhr.js: -------------------------------------------------------------------------------- 1 | load("FunctionalTools.js"); 2 | -------------------------------------------------------------------------------- /web/js/chapter/binaryheap.js: -------------------------------------------------------------------------------- 1 | load("FunctionalTools.js"); 2 | -------------------------------------------------------------------------------- /renderer_tex/make.sh: -------------------------------------------------------------------------------- 1 | ghc --make Main.hs -o render 2 | mkdir tex 3 | 4 | -------------------------------------------------------------------------------- /web/files/fruit2.json: -------------------------------------------------------------------------------- 1 | {"banana": "yellow", 2 | "lemon": "yellow", 3 | "cherry": "red"} -------------------------------------------------------------------------------- /web/img/hr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/hr.png -------------------------------------------------------------------------------- /web/img/ejs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/ejs.png -------------------------------------------------------------------------------- /web/img/eyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/eyes.png -------------------------------------------------------------------------------- /web/img/heap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/heap.png -------------------------------------------------------------------------------- /web/img/hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/hide.png -------------------------------------------------------------------------------- /web/img/html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/html.png -------------------------------------------------------------------------------- /web/img/show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/show.png -------------------------------------------------------------------------------- /web/img/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/tree.png -------------------------------------------------------------------------------- /web/img/Hiva Oa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/Hiva Oa.png -------------------------------------------------------------------------------- /web/img/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/close.png -------------------------------------------------------------------------------- /web/img/height.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/height.png -------------------------------------------------------------------------------- /web/img/ostrich.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/ostrich.png -------------------------------------------------------------------------------- /web/img/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/react.png -------------------------------------------------------------------------------- /web/img/resize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/resize.png -------------------------------------------------------------------------------- /web/img/runcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/runcode.png -------------------------------------------------------------------------------- /web/img/sheep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/sheep.png -------------------------------------------------------------------------------- /web/img/yinyang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/yinyang.png -------------------------------------------------------------------------------- /web/img/loadcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/loadcode.png -------------------------------------------------------------------------------- /web/img/diagonalpath.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/diagonalpath.png -------------------------------------------------------------------------------- /web/img/hr-diamonds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/hr-diamonds.png -------------------------------------------------------------------------------- /web/img/objectchain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/objectchain.png -------------------------------------------------------------------------------- /web/img/runcode_done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/runcode_done.png -------------------------------------------------------------------------------- /web/img/sokoban/exit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/sokoban/exit.png -------------------------------------------------------------------------------- /web/img/sokoban/wall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/sokoban/wall.png -------------------------------------------------------------------------------- /web/img/florence_flask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/florence_flask.png -------------------------------------------------------------------------------- /web/img/sokoban/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/sokoban/empty.png -------------------------------------------------------------------------------- /web/img/sokoban/player.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/sokoban/player.gif -------------------------------------------------------------------------------- /web/img/sokoban/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/sokoban/player.png -------------------------------------------------------------------------------- /web/js/before.js: -------------------------------------------------------------------------------- 1 | document.body.style.visibility = "hidden"; 2 | 3 | var JSEOptions = {stylesheet: "css/highlight.css"}; 4 | -------------------------------------------------------------------------------- /web/img/sokoban/boulder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/sokoban/boulder.gif -------------------------------------------------------------------------------- /web/img/sokoban/boulder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/sokoban/boulder.png -------------------------------------------------------------------------------- /web/img/xkcd_regular_expressions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript-1st-edition/HEAD/web/img/xkcd_regular_expressions.png -------------------------------------------------------------------------------- /web/files/fruit.json: -------------------------------------------------------------------------------- 1 | [{name: "apple", 2 | color: "red"}, 3 | {name: "orange", 4 | color: "orange"}, 5 | {name: "banana", 6 | color: "yellow"}] 7 | -------------------------------------------------------------------------------- /web/files/fruit2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | cd ~/ejs/book 2 | ./render book 3 | cp book web/book.txt 4 | mv web Eloquent\ JavaScript 5 | zip -qr ../www/Eloquent\ JavaScript.zip Eloquent\ JavaScript -x Eloquent\ JavaScript/.htaccess 6 | mv Eloquent\ JavaScript web 7 | cd web 8 | cp -r * ../../www 9 | -------------------------------------------------------------------------------- /web/files/fruit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | apple 4 | red 5 | 6 | 7 | orange 8 | orange 9 | 10 | 11 | banana 12 | yellow 13 | 14 | 15 | -------------------------------------------------------------------------------- /web/js/chapter/web.js: -------------------------------------------------------------------------------- 1 | load("FunctionalTools.js"); 2 | 3 | var timeWriter = "\n The time\n \n

The time

\n

The time is\n \n

\n \n"; 4 | -------------------------------------------------------------------------------- /web/js/params.js: -------------------------------------------------------------------------------- 1 | function getParams() { 2 | var question = document.URL.indexOf("?"); 3 | var result = {}; 4 | if (question > -1) { 5 | var params = document.URL.slice(question); 6 | while (params) { 7 | var match = params.match(/^.([^=]+)=([^&]*)(&.*)?$/); 8 | result[match[1]] = unescape(match[2]); 9 | params = match[3]; 10 | } 11 | } 12 | return result; 13 | } 14 | -------------------------------------------------------------------------------- /web/view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | -------------------------------------------------------------------------------- /web/js/chapter/intro.js: -------------------------------------------------------------------------------- 1 | function range(start, end) { 2 | if (arguments.length < 2) { 3 | end = start; 4 | start = 0; 5 | } 6 | var result = []; 7 | for (var i = start; i <= end; i++) 8 | result.push(i); 9 | return result; 10 | } 11 | 12 | function sum(numbers) { 13 | var total = 0; 14 | for (var i = 0; i < numbers.length; i++) 15 | total += numbers[i]; 16 | return total; 17 | } 18 | -------------------------------------------------------------------------------- /web/js/TestModule.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | var tests = [["FunctionalTools", "map(partial(op[\"*\"], 5), [3, 6, 15])"], 3 | ["ObjectTools", "clone({x: 10, y: 5}).x"], 4 | ["Dictionary", "(new Dictionary({left: true})).contains(\"right\")"]]; 5 | for (var i = 0; i < tests.length; i++) { 6 | var test = tests[i]; 7 | print("Testing " , test[0], ": ", test[1]); 8 | show(eval(test[1])); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This is the source from which I build http://eloquentjavascript.net 2 | 3 | To build it yourself, install GHC (with the parsec2, mtl, and 4 | utf8-string libraries), and run `make.sh` in the `renderer/` dir. You 5 | should get a `render` binary, which you can run on a text file to 6 | produce the HTML for the book. 7 | 8 | The file `book` contains the source for the actual book. Its format is 9 | completely undocumented, but relatively straightforward. 10 | -------------------------------------------------------------------------------- /web/css/sokoban.css: -------------------------------------------------------------------------------- 1 | table.sokoban { 2 | border-collapse: collapse; 3 | } 4 | 5 | table.sokoban td { 6 | width: 32px; 7 | height: 32px; 8 | padding: 0; 9 | vertical-align: middle; 10 | text-align: center; 11 | } 12 | 13 | table.sokoban td.empty { 14 | background-image: url(../img/sokoban/empty.png); 15 | } 16 | table.sokoban td.wall { 17 | background-image: url(../img/sokoban/wall.png); 18 | } 19 | table.sokoban td.exit { 20 | background-image: url(../img/sokoban/exit.png); 21 | } 22 | -------------------------------------------------------------------------------- /web/js/sendreaction.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /web/js/chapter/oo.js: -------------------------------------------------------------------------------- 1 | load("FunctionalTools.js"); 2 | 3 | function inPlacePrinter() { 4 | var div = __ENV.parent.DIV(); 5 | var first = true; 6 | __ENV.output(div); 7 | return function(text) { 8 | text = String(text); 9 | if (__ENV.parent.preNewline != "\n") 10 | text = text.replace(/\n/g, __ENV.parent.preNewline); 11 | __ENV.parent.replaceChildNodes(div, __ENV.parent.document.createTextNode(String(text))); 12 | if (first) { 13 | __ENV.parent.scrollToBottom(div.parentNode.parentNode); 14 | first = false; 15 | } 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /web/js/Dictionary.js: -------------------------------------------------------------------------------- 1 | function Dictionary(startValues) { 2 | this.values = startValues || {}; 3 | } 4 | 5 | Dictionary.prototype.store = function(name, value) { 6 | this.values[name] = value; 7 | }; 8 | Dictionary.prototype.lookup = function(name) { 9 | return this.values[name]; 10 | }; 11 | Dictionary.prototype.contains = function(name) { 12 | return Object.prototype.hasOwnProperty.call(this.values, name) && 13 | Object.prototype.propertyIsEnumerable.call(this.values, name); 14 | }; 15 | Dictionary.prototype.each = function(action) { 16 | forEachIn(this.values, action); 17 | }; 18 | -------------------------------------------------------------------------------- /web/example_alchemy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Alchemy for beginners 4 | 5 | 6 | 10 | 11 |

Chapter 1: Equipment

12 |

This is what an alchemists' bottle looks like:

13 | a fat bottle 14 | 15 | -------------------------------------------------------------------------------- /web/css/highlight.css: -------------------------------------------------------------------------------- 1 | .editbox { 2 | border-width: 0; 3 | margin: .4em; 4 | padding: 0; 5 | font-family: monospace; 6 | font-size: 10pt; 7 | color: black; 8 | } 9 | 10 | .editbox pre, .editbox p { 11 | margin: 0; 12 | padding: 0; 13 | } 14 | 15 | span.keyword { 16 | color: #770088; 17 | } 18 | 19 | span.atom { 20 | color: #448888; 21 | } 22 | 23 | span.variable { 24 | color: black; 25 | } 26 | 27 | span.variabledef { 28 | color: #0000FF; 29 | } 30 | 31 | span.localvariable { 32 | color: #004499; 33 | } 34 | 35 | span.property { 36 | color: black; 37 | } 38 | 39 | span.comment { 40 | color: #AA7700; 41 | } 42 | 43 | span.string { 44 | color: #AA2222; 45 | } 46 | 47 | pre.code, .editbox { 48 | color: #666666; 49 | } 50 | -------------------------------------------------------------------------------- /renderer/Html.hs: -------------------------------------------------------------------------------- 1 | module Html where 2 | 3 | data HTML = Tx String | Tg String [(String, String)] [HTML] 4 | 5 | instance Show HTML where 6 | show (Tx s) = escapeHtml s 7 | show (Tg tag props []) = "<" ++ tag ++ (propString props) ++ "/>" 8 | show (Tg tag props children) = "<" ++ tag ++ (propString props) ++ ">" ++ (concatMap show children) ++ "" 9 | 10 | tg tag children = Tg tag [] children 11 | 12 | propString [] = "" 13 | propString ((prop, value):props) = " " ++ prop ++ "=\"" ++ (escapeHtml value) ++ "\"" ++ (propString props) 14 | 15 | escapeHtml = concatMap fixChar 16 | where fixChar '<' = "<" 17 | fixChar '>' = ">" 18 | fixChar '&' = "&" 19 | fixChar '"' = """ 20 | fixChar c = [c] 21 | -------------------------------------------------------------------------------- /web/example_events.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Event examples 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

13 | 14 | (Its id is 'button'.) 15 |

16 |

17 | ('textfield') 18 |

19 | 20 | 21 | -------------------------------------------------------------------------------- /web/js/ObjectTools.js: -------------------------------------------------------------------------------- 1 | function clone(object) { 2 | function OneShotConstructor(){} 3 | OneShotConstructor.prototype = object; 4 | return new OneShotConstructor(); 5 | } 6 | 7 | Object.prototype.inherit = function(baseConstructor) { 8 | this.prototype = clone(baseConstructor.prototype); 9 | this.prototype.constructor = this; 10 | }; 11 | 12 | Object.prototype.method = function(name, func) { 13 | this.prototype[name] = func; 14 | }; 15 | 16 | Object.prototype.create = function() { 17 | var object = clone(this); 18 | if (typeof object.construct == "function") 19 | object.construct.apply(object, arguments); 20 | return object; 21 | }; 22 | 23 | Object.prototype.extend = function(properties) { 24 | var result = clone(this); 25 | forEachIn(properties, function(name, value) { 26 | result[name] = value; 27 | }); 28 | return result; 29 | }; 30 | -------------------------------------------------------------------------------- /web/js/DOMTools.js: -------------------------------------------------------------------------------- 1 | function $(id) { 2 | return document.getElementById(id); 3 | } 4 | 5 | function removeElement(node) { 6 | if (node.parentNode) 7 | node.parentNode.removeChild(node); 8 | } 9 | 10 | function setNodeAttribute(node, attribute, value) { 11 | if (attribute == "class") 12 | node.className = value; 13 | else if (attribute == "checked") 14 | node.defaultChecked = value; 15 | else if (attribute == "for") 16 | node.htmlFor = value; 17 | else if (attribute == "style") 18 | node.style.cssText = value; 19 | else 20 | node.setAttribute(attribute, value); 21 | } 22 | 23 | function dom(name, attributes) { 24 | var node = document.createElement(name); 25 | if (attributes) { 26 | forEachIn(attributes, function(name, value) { 27 | setNodeAttribute(node, name, value); 28 | }); 29 | } 30 | for (var i = 2; i < arguments.length; i++) { 31 | var child = arguments[i]; 32 | if (typeof child == "string") 33 | child = document.createTextNode(child); 34 | node.appendChild(child); 35 | } 36 | return node; 37 | } 38 | -------------------------------------------------------------------------------- /web/imprint.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Eloquent JavaScript: Imprint 4 | 5 | 9 | 10 | 11 | 12 |
13 |

Imprint

14 |

Eloquent JavaScript

15 | 16 |

Eloquentjavascript.net is a provided and maintained by:

17 |

Marijn Haverbeke
Ostpreußendamm 66 H
12207 Berlin
Germany

Email: marijnh@gmail.com

18 |

Registered as an Einzelunternehmen in Berlin Steglitz.

19 | 20 | 21 | 22 | 23 |
Tax number:20/334/00047
VAT ID:DE275453213
24 |

The person responsible for the contents of eloquentjavascript.net as per Art. 55 RStV [German Interstate Broadcasting Treaty]: Marijn Haverbeke.

25 |

← back to cover

26 |
27 | 28 | -------------------------------------------------------------------------------- /web/example_getinfo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Register your own Planet Junkmail account 4 | 5 | 6 | 7 |

Planet Junkmail

8 |

Registration

9 |

Register now, and be the first to hear about all our 10 | spectacular deals on our unique products!

11 |
12 |

Name:

13 |

E-Mail:

14 |

Sex:

19 |

20 |
21 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /web/js/chapter/dom.js: -------------------------------------------------------------------------------- 1 | function escapeHTML(text) { 2 | var replacements = {"<": "<", ">": ">", "&": "&", "\"": """}; 3 | return text.replace(/[<>&\"]/g, function(character) { 4 | return replacements[character]; 5 | }); 6 | } 7 | 8 | function makeTable(data, columns) { 9 | var thead = dom("THEAD", null, dom("TR")), tbody = dom("TBODY"); 10 | forEach(columns, function(name) { 11 | var cell = dom("TD", null, name); 12 | thead.firstChild.appendChild(cell); 13 | cell.onclick = partial(reSort, name); 14 | }); 15 | forEach(data, function(element) { 16 | element._row = dom("TR"); 17 | tbody.appendChild(element._row); 18 | forEach(columns, function(name) { 19 | element._row.appendChild(dom("TD", null, element[name])); 20 | }); 21 | }); 22 | 23 | function compare(a, b) { 24 | if (a < b) return -1; 25 | else if (b > a) return 1; 26 | else return 0; 27 | } 28 | 29 | function reSort(name) { 30 | data.sort(function(a, b) {return compare(a[name], b[name]);}); 31 | forEach(data, function(element) { 32 | tbody.appendChild(element._row); 33 | }); 34 | } 35 | 36 | return dom("TABLE", null, thead, tbody); 37 | } 38 | 39 | function test() { 40 | document.body.appendChild(makeTable([{foo: 1, bar: "flub"}, {foo: 8, bar: "aap"}, {foo: 0, bar: "zruty"}], ["foo", "bar"])); 41 | } 42 | -------------------------------------------------------------------------------- /web/js/chapter/event_example.js: -------------------------------------------------------------------------------- 1 | // Sokoban levels based on those in the Nethack game 2 | // (http://www.nethack.org). 3 | 4 | var sokobanLevels = [ 5 | {field: ["###### ##### ", 6 | "# # # # ", 7 | "# 0 #### 0 # ", 8 | "# 0 @ 0 # ", 9 | "# #######0 # ", 10 | "#### ### ###", 11 | " # #", 12 | " #0 #", 13 | " # 0 #", 14 | " ## 0 #", 15 | " #*0 0 #", 16 | " ########"], 17 | boulders: 10}, 18 | 19 | {field: ["########### ", 20 | "# # # ", 21 | "# 00#00 @# ", 22 | "# 0 # ", 23 | "# # # ", 24 | "## ######### ", 25 | "# 0 # # ", 26 | "# 00 #0 0 0# ", 27 | "# 0 0 # ", 28 | "# 000#0 0 ###", 29 | "# # 0 0 *#", 30 | "##############"], 31 | boulders: 20}, 32 | 33 | {field: ["########## ", 34 | "#@ *# ", 35 | "# ## ", 36 | "####### ######", 37 | " # #", 38 | " # 0 0 0 0 0 #", 39 | "######## #####", 40 | "# 0 0 0 0 #", 41 | "# 0 #", 42 | "##### ########", 43 | " # 0 0 0 # ", 44 | " # 0 # ", 45 | " # 0 0 0 ## ", 46 | "####### #### ", 47 | "# 0 # ", 48 | "# # ", 49 | "# ###### ", 50 | "##### "], 51 | boulders: 16}, 52 | 53 | {field: [" #### ", 54 | "## @######## ", 55 | "# # ", 56 | "# 0#####0# # ", 57 | "# # # 0 # ", 58 | "# 0 0 0## ", 59 | "# 0 0 # # ", 60 | "# ####0 ## # ", 61 | "# 0 0 # ## ", 62 | "# ###0# 0 ##", 63 | "# # 0# 0 *#", 64 | "# 0 ####", 65 | "##### # # ", 66 | " ####### "], 67 | boulders: 12}, 68 | 69 | {field: ["###### #####", 70 | "# #*## ## #", 71 | "# #### 0 #", 72 | "# 00 # # 0 #", 73 | "## 00# 00 ##", 74 | " #0 0 #0 # ", 75 | " # 00 # # 0# ", 76 | " # 0 0#### 0 # ", 77 | " # # ## ", 78 | " #### 0 # ## ", 79 | " ### ## # ", 80 | " # 0 # ", 81 | " #@ # # ", 82 | " ####### "], 83 | boulders: 18}]; 84 | 85 | function Point(x, y) { 86 | this.x = x; 87 | this.y = y; 88 | } 89 | Point.prototype.add = function(other) { 90 | return new Point(this.x + other.x, this.y + other.y); 91 | }; 92 | Point.prototype.isEqualTo = function(other) { 93 | return this.x == other.x && this.y == other.y; 94 | }; 95 | -------------------------------------------------------------------------------- /web/js/FunctionalTools.js: -------------------------------------------------------------------------------- 1 | var Break = {name: "Break"}; 2 | 3 | function forEach(array, action) { 4 | var len = array.length; 5 | try { 6 | for (var i = 0; i < len; i++) 7 | action(array[i]); 8 | } 9 | catch(e) { 10 | if (e != Break) 11 | throw e; 12 | } 13 | } 14 | 15 | function forEachIn(object, action) { 16 | try { 17 | for (var property in object) { 18 | if (Object.prototype.hasOwnProperty.call(object, property)) 19 | action(property, object[property]); 20 | } 21 | } 22 | catch(e) { 23 | if (e != Break) 24 | throw e; 25 | } 26 | } 27 | 28 | function map(func, array) { 29 | var len = array.length; 30 | var result = new Array(len); 31 | for (var i = 0; i < len; i++) 32 | result[i] = func(array[i]); 33 | return result; 34 | } 35 | 36 | function reduce(func, start, array) { 37 | var len = array.length; 38 | for (var i = 0; i < len; i++) 39 | start = func(start, array[i]); 40 | return start; 41 | } 42 | 43 | function filter(test, array) { 44 | var result = [], len = array.length; 45 | for (var i = 0; i < len; i++) { 46 | var current = array[i]; 47 | if (test(current)) 48 | result.push(current); 49 | } 50 | return result; 51 | } 52 | 53 | function any(test, array) { 54 | for (var i = 0; i < array.length; i++) { 55 | var found = test(array[i]); 56 | if (found) 57 | return found; 58 | } 59 | return false; 60 | } 61 | 62 | function partial(func) { 63 | function asArray(quasiArray, start) { 64 | var result = [], len = quasiArray.length; 65 | for (var i = (start || 0); i < len; i++) 66 | result.push(quasiArray[i]); 67 | return result; 68 | } 69 | var fixedArgs = asArray(arguments, 1); 70 | return function(){ 71 | return func.apply(null, fixedArgs.concat(asArray(arguments))); 72 | }; 73 | } 74 | 75 | function bind(func, object) { 76 | return function(){ 77 | return func.apply(object, arguments); 78 | }; 79 | } 80 | 81 | function method(object, name) { 82 | return function() { 83 | object[name].apply(object, arguments); 84 | }; 85 | } 86 | 87 | function compose(func1, func2) { 88 | return function() { 89 | return func1(func2.apply(null, arguments)); 90 | }; 91 | } 92 | 93 | var op = function(){ 94 | var result = { 95 | "-": function(a, b) { 96 | if (arguments.length < 2) 97 | return -a; 98 | else 99 | return a - b; 100 | }, 101 | "!": function(a) {return !a;}, 102 | "typeof": function(a) {return typeof a;}, 103 | "?": function(a, b, c) {return a ? b : c;} 104 | }; 105 | var ops = ["+", "*", "/", "%", "&&", "||", "==", "!=", "===", 106 | "!==", "<", ">", ">=", "<=", "in", "instanceof"]; 107 | forEach(ops, function(op){ 108 | result[op] = eval("[function(a, b){return a " + op + " b;}][0]"); 109 | }); 110 | return result; 111 | }(); 112 | -------------------------------------------------------------------------------- /web/js/chapter/data.js: -------------------------------------------------------------------------------- 1 | function retrieveMails() { 2 | return [ 3 | "Nephew,\n\nI bought a computer as soon as I received your letter. It took me two days to make it do 'internet', but I just kept calling the nice man at the computer shop, and in the end he came down to help personally. Send me something back if you receive this, so I know whether it actually works.\n\nLove,\nAunt Emily", 4 | "Dear Nephew,\n\nVery good! I feel quite proud about being so technologically minded, having a computer and all. I bet Mrs. Goor down the street wouldn't even know how to plug it in, that witch.\n\nAnyway, thanks for sending me that game, it was great fun. After three days, I beat it. My friend Mrs. Johnson was quite worried when I didn't come outside or answer the phone for three days, but I explained to her that I was working with my computer.\n\nMy cat had two kittens yesterday! I didn't even realize the thing was pregnant. I've listed the names at the bottom of my letter, so that you will know how to greet them the next time you come over.\n\nSincerely,\nAunt Emily\n\nborn 15/02/1999 (mother Spot): Clementine, Fireball", 5 | "[... and so on ...]\n\nborn 21/09/2000 (mother Spot): Yellow Emperor, Black Leclère", 6 | "...\n\nborn 02/04/2001 (mother Clementine): Bugeye, Wolverine, Miss Bushtail", 7 | "...\n\ndied 12/12/2002: Clementine\n\ndied 15/12/2002: Wolverine", 8 | "...\n\nborn 15/11/2003 (mother Spot): White Fang", 9 | "...\n\nborn 10/04/2003 (mother Miss Bushtail): Yellow Bess", 10 | "...\n\ndied 30/05/2004: Yellow Emperor", 11 | "...\n\nborn 01/06/2004 (mother Miss Bushtail): Catharina, Fat Igor", 12 | "...\n\nborn 20/09/2004 (mother Yellow Bess): Doctor Hobbles the 2nd, Noog", 13 | "...\n\nborn 15/01/2005 (mother Yellow Bess): The Moose, Liger\n\ndied 17/01/2005: Liger", 14 | "Dear nephew,\n\nYour mother told me you have taken up skydiving. Is this true? You watch yourself, young man! Remember what happened to my husband? And that was only from the second floor!\n\nAnyway, things are very exciting here. I have spent all week trying to get the attention of Mr. Drake, the nice gentleman who moved in next\ndoor, but I think he is afraid of cats. Or allergic to them? I am\ngoing to try putting Fat Igor on his shoulder next time I see him, very curious what will happen.\n\nAlso, the scam I told you about is going better than expected. I have already gotten back five 'payments', and only one complaint. It is starting to make me feel a bit bad though. And you are right that it is probably illegal in some way.\n\n(... etc ...)\n\nMuch love,\nAunt Emily\n\ndied 27/04/2006: Black Leclère\n\nborn 05/04/2006 (mother Lady Penelope): Red Lion, Doctor Hobbles the 3rd, Little Iroquois", 15 | "...\n\nborn 22/07/2006 (mother Noog): Goblin, Reginald, Little Maggie", 16 | "...\n\ndied 13/02/2007: Spot\n\ndied 21/02/2007: Fireball", 17 | "...\n\nborn 05/02/2007 (mother Noog): Long-ear Johnson", 18 | "...\n\nborn 03/03/2007 (mother Catharina): Asoka, Dark Empress, Rabbitface"]; 19 | } 20 | -------------------------------------------------------------------------------- /web/paper.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Eloquent JavaScript 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 19 | 20 | 21 |
22 | Eloquent JavaScript 23 |   Code sandbox for the paper book 24 | [help] 25 |
26 | 45 |
46 | 47 | 52 |
48 |
49 |
50 |
51 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /web/donate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Donating -- Eloquent JavaScript 4 | 5 | 6 | 7 | 8 |
9 |

Donating

10 | 11 |

I wasn't really planning to make any money off this thing, 12 | but a few people have suggested I should put up a donation 13 | link. So here it is -- if you feel the inclination, and you 14 | have a PayPal account, the 15 | button below can be used to donate something.

16 | 17 |

18 | 19 | 20 | 21 | 23 |

© Marijn Haverbeke, (license), last modified 27 | on August 6 2007.
28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Eloquent JavaScript: A Modern Introduction to Programming 4 | 5 | 37 | 38 | 39 | 40 | 41 | 42 |
43 |
44 |

Eloquent JavaScript

45 |

A Modern Introduction to Programming

46 |
by Marijn Haverbeke
47 |
48 | 49 |
50 | 51 |
Get the book
52 |
53 | 54 |

Eloquent JavaScript is a book providing an 55 | introduction to the JavaScript programming language and 56 | programming in general.

57 | 58 |
59 | A concise and balanced mix of principles and pragmatics. I loved the tutorial-style game-like program development. This book rekindled my earliest joys of programming. Plus, JavaScript!
60 | Brendan Eich, the man who gave us JavaScript 61 |
62 | 63 |

The book exists in two forms. It was originally written and 64 | published in digital form, which 65 | includes interactive examples and a mechanism for playing with 66 | all the example code. This version is released under an open 68 | license.

69 | 70 |

I have published a revised version of the book on paper. The 71 | structure of this version remained largely the same, but the 72 | whole text has been thoroughly edited and updated. You can order 73 | from Amazon 75 | here. There is still an interactive coding environment for this 76 | version, as a separate page. Errata are 77 | here.

78 | 79 |

The digital version is available in the following formats:

80 | 81 | 86 | 87 |

The following translations are available:

88 | 89 | 94 | 95 | 97 | 98 |
99 | 100 | 101 | -------------------------------------------------------------------------------- /web/css/console.css: -------------------------------------------------------------------------------- 1 | div.frame { 2 | overflow: auto; 3 | width: 100%; 4 | position: relative; 5 | } 6 | 7 | div.console { 8 | position: relative; 9 | background-color: #b5c9c6; 10 | overflow: visible; 11 | z-index: 12; 12 | } 13 | 14 | div.output, div.editor { 15 | position: absolute; 16 | background-color:white; 17 | } 18 | 19 | div.editor, div.repl { 20 | overflow: hidden; 21 | } 22 | 23 | div.output { 24 | overflow: hidden; 25 | } 26 | 27 | div.outputhead { 28 | font-size: 70%; 29 | line-height: 90%; 30 | padding: 2px .4em; 31 | color: #7c9490; 32 | border-bottom: 1px solid #CCDDDD; 33 | background-color: #EEF4F4; 34 | } 35 | 36 | div.outputbutton { 37 | cursor: pointer; 38 | margin-left: .4em; 39 | float: right; 40 | } 41 | 42 | div.outputinner { 43 | overflow: auto; 44 | } 45 | 46 | div.outputinner pre { 47 | margin: 0; 48 | font-family: courier new, courier, monospace; 49 | padding: .2em .4em; 50 | font-size: 80%; 51 | } 52 | 53 | span.atomvalue { 54 | color: #448888; 55 | font-weight: normal; 56 | } 57 | span.stringvalue { 58 | color: #AA2222; 59 | font-weight: normal; 60 | } 61 | span.functionvalue { 62 | color: #770088; 63 | font-weight: normal; 64 | } 65 | span.objectvalue, span.arrayvalue { 66 | color: #227722; 67 | font-weight: bold; 68 | cursor: pointer; 69 | } 70 | 71 | table.objecttable { 72 | border-spacing: 0; 73 | } 74 | 75 | table.objecttable th, table.objecttable td { 76 | font-size: 80%; 77 | padding: 0; 78 | white-space: pre; 79 | } 80 | 81 | table.objecttable th { 82 | color: #666666; 83 | text-align: right; 84 | font-weight: normal; 85 | padding-right: .3em; 86 | } 87 | 88 | div.editor input { 89 | border: 0; 90 | padding: 2px; 91 | font-family: courier new, courier, monospace; 92 | font-size: 80%; 93 | } 94 | 95 | div.editor textarea { 96 | border: 0; 97 | padding: 3px; 98 | } 99 | 100 | #controls { 101 | position: absolute; 102 | } 103 | 104 | #controls select { 105 | width: 8em; 106 | vertical-align: top; 107 | } 108 | 109 | #controls button, #controls select { 110 | background-color: #b5c9c6; 111 | color: #3d4e4b; 112 | margin-right: .5em; 113 | } 114 | 115 | #controls button:hover, #controls select:hover { 116 | background-color: #d0e0de; 117 | } 118 | 119 | button.showhide, button.resize { 120 | border: 0; 121 | margin-left: .5em; 122 | height: 10px; 123 | vertical-align: top; 124 | padding: 0; 125 | } 126 | 127 | button.showhide { 128 | background-image: url(../img/show.png); 129 | width: 13px; 130 | cursor: pointer; 131 | } 132 | 133 | div.open button.showhide { 134 | background-image: url(../img/hide.png); 135 | width: 13px; 136 | } 137 | 138 | button.resize { 139 | background-image: url(../img/resize.png); 140 | width: 30px; 141 | cursor: n-resize; 142 | } 143 | 144 | div.console div.header { 145 | text-align: right; 146 | letter-spacing: .8em; 147 | font-size: 50%; 148 | color: #7c9490; 149 | font-weight: bold; 150 | font-family: times, serif; 151 | padding-top: 1px; 152 | padding-right: .5em; 153 | } 154 | 155 | div.console div.header span { 156 | cursor: pointer; 157 | } 158 | 159 | /* hand-made inset/outset borders to make them look non-ugly in IE */ 160 | 161 | #controls button, #controls select, div.console { 162 | border-width: 1px; 163 | border-style: solid; 164 | border-top-color: #e1ecea; 165 | border-left-color: #e1ecea; 166 | border-bottom-color: #7c9490; 167 | border-right-color: #7c9490; 168 | } 169 | 170 | div.output, div.editor, div.repl { 171 | border-width: 1px; 172 | border-style: solid; 173 | border-top-color: #7c9490; 174 | border-left-color: #7c9490; 175 | border-bottom-color: #e1ecea; 176 | border-right-color: #e1ecea; 177 | } 178 | 179 | button.codebutton { 180 | float: right; 181 | margin-top: -.25em; 182 | margin-left: .2em; 183 | height: 15px; 184 | width: 15px; 185 | padding: 0; 186 | border: 0; 187 | z-index: 11; 188 | position: relative; 189 | } 190 | 191 | button.run { 192 | background-image: url(../img/runcode_done.png); 193 | margin-right: -.25em; 194 | } 195 | 196 | pre.not-run button.run { 197 | background-image: url(../img/runcode.png); 198 | } 199 | 200 | button.load { 201 | background-image: url(../img/loadcode.png); 202 | } 203 | 204 | div.marker { 205 | position: absolute; 206 | width: 5px; 207 | height: 5px; 208 | background-color: red; 209 | } 210 | 211 | div.outputimage { 212 | position: relative; 213 | margin: .5em; 214 | } 215 | 216 | div.resizemarker { 217 | width: 100%; 218 | z-index: 15; 219 | background: transparent; 220 | border-bottom: 1px solid #444444; 221 | position: absolute; 222 | left: 0px; 223 | } 224 | -------------------------------------------------------------------------------- /web/js/chapter/fp.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function recluseFile() { 4 | return "% The Book of Programming\n\n%% The Two Aspects\n\nBelow the surface of the machine, the program moves. Without effort,\nit expands and contracts. In great harmony, electrons scatter and\nregroup. The forms on the monitor are but ripples on the water. The\nessence stays invisibly below.\n\nWhen the creators built the machine, they put in the processor and the\nmemory. From these arise the two aspects of the program.\n\nThe aspect of the processor is the active substance. It is called\nControl. The aspect of the memory is the passive substance. It is\ncalled Data.\n\nData is made of merely bits, yet it takes complex forms. Control\nconsists only of simple instructions, yet it performs difficult\ntasks. From the small and trivial, the large and complex arise.\n\nThe program source is Data. Control arises from it. The Control\nproceeds to create new Data. The one is born from the other, the\nother is useless without the one. This is the harmonious cycle of\nData and Control.\n\nOf themselves, Data and Control are without structure. The programmers\nof old moulded their programs out of this raw substance. Over time,\nthe amorphous Data has crystallised into data types, and the chaotic\nControl was restricted into control structures and functions.\n\n%% Short Sayings\n\nWhen a student asked Fu-Tzu about the nature of the cycle of Data and\nControl, Fu-Tzu replied 'Think of a compiler, compiling itself.'\n\nA student asked 'The programmers of old used only simple machines and\nno programming languages, yet they made beautiful programs. Why do we\nuse complicated machines and programming languages?'. Fu-Tzu replied\n'The builders of old used only sticks and clay, yet they made\nbeautiful huts.'\n\nA hermit spent ten years writing a program. 'My program can compute\nthe motion of the stars on a 286-computer running MS DOS', he proudly\nannounced. 'Nobody own a 286-computer or uses MS DOS anymore.', Fu-Tzu\nresponded.\n\nFu-Tzu had written a small program that was full of global state and\ndubious shortcuts. Reading it, a student asked 'You warned us against\nthese techniques, yet I find them in your program. How can this be?'\nFu-Tzu said 'There is no need to fetch water hose when the house is\nnot on fire.'{This is not to be read as an encouragement of sloppy\nprogramming, but rather as a warning against neurotic adherence to\nrules of thumb.}\n\n%% Wisdom\n\nA student was complaining about digital numbers. 'When I take the root\nof two and then square it again, the result is already inaccurate!'.\nOverhearing him, Fu-Tzu laughed. 'Here is a sheet of paper. Write down\nthe precise value of the square root of two for me.'\n\nFu-Tzu said 'When you cut against the grain of the wood, much strength\nis needed. When you program against the grain of a problem, much code\nis needed.'\n\nTzu-li and Tzu-ssu were boasting about the size of their latest\nprograms. 'Two-hundred thousand lines', said Tzu-li, 'not counting\ncomments!'. 'Psah', said Tzu-ssu, 'mine is almost a *million* lines\nalready.' Fu-Tzu said 'My best program has five hundred lines.'\nHearing this, Tzu-li and Tzu-ssu were enlightened.\n\nA student had been sitting motionless behind his computer for hours,\nfrowning darkly. He was trying to write a beautiful solution to a\ndifficult problem, but could not find the right approach. Fu-Tzu hit\nhim on the back of his head and shouted '*Type something!*' The student\nstarted writing an ugly solution. After he had finished, he suddenly\nunderstood the beautiful solution.\n\n%% Progression\n\nA beginning programmer writes his programs like an ant builds her\nhill, one piece at a time, without thought for the bigger structure.\nHis programs will be like loose sand. They may stand for a while, but\ngrowing too big they fall apart{Referring to the danger of internal\ninconsistency and duplicated structure in unorganised code.}.\n\nRealising this problem, the programmer will start to spend a lot of\ntime thinking about structure. His programs will be rigidly\nstructured, like rock sculptures. They are solid, but when they must\nchange, violence must be done to them{Referring to the fact that\nstructure tends to put restrictions on the evolution of a program.}.\n\nThe master programmer knows when to apply structure and when to leave\nthings in their simple form. His programs are like clay, solid yet\nmalleable.\n\n%% Language\n\nWhen a programming language is created, it is given syntax and\nsemantics. The syntax describes the form of the program, the semantics\ndescribe the function. When the syntax is beautiful and the semantics\nare clear, the program will be like a stately tree. When the syntax is\nclumsy and the semantics confusing, the program will be like a bramble\nbush.\n\nTzu-ssu was asked to write a program in the language called Java,\nwhich takes a very primitive approach to functions. Every morning, as\nhe sat down in front of his computer, he started complaining. All day\nhe cursed, blaming the language for all that went wrong. Fu-Tzu\nlistened for a while, and then reproached him, saying 'Every language\nhas its own way. Follow its form, do not try to program as if you\nwere using another language.'\n"; 5 | } 6 | 7 | var stroustrupQuote = "A quote

A quote

The connection between the language in which we think/program and the problems and solutions we can imagine is very close. For this reason restricting language features with the intent of eliminating programmer errors is at best dangerous.

-- Bjarne Stroustrup

Mr. Stroustrup is the inventor of the C++ programming language, but quite an insightful person nevertheless.

Also, here is a picture of an ostrich:

"; 8 | -------------------------------------------------------------------------------- /web/css/book.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | div.content { 6 | font-family: tahoma, arial, sans-serif; 7 | padding: 3em 5em; 8 | padding-right: 4em; 9 | max-width: 850px; 10 | color: #222; 11 | line-height: 150%; 12 | } 13 | 14 | h1 { 15 | font-size: 200%; 16 | margin-left: -1em; 17 | margin-bottom: 1.4em; 18 | page-break-before: left; 19 | } 20 | 21 | h1 .number { 22 | display: block; 23 | font-size: 45%; 24 | font-weight: normal; 25 | } 26 | 27 | h2 { 28 | font-size: 150%; 29 | } 30 | 31 | div.block { 32 | padding: 0 .5em; 33 | position: relative; 34 | } 35 | 36 | div.exercisenum { 37 | top: 0; 38 | width: 7em; 39 | margin-left: -8em; 40 | position: absolute; 41 | text-align: right; 42 | z-index: 11; 43 | font-size: 85%; 44 | color: #AAAAAA; 45 | } 46 | 47 | div.exercise { 48 | border-left: .2em solid #FFBBBB; 49 | margin-left: -.7em; 50 | padding-left: .5em; 51 | } 52 | 53 | div.solution { 54 | border-left: .2em solid #AAEEAA; 55 | margin-left: -.7em; 56 | padding-left: .5em; 57 | position: relative; 58 | } 59 | 60 | div.solutionarrow { 61 | position: absolute; 62 | top: 0; 63 | margin-left: -4em; 64 | width: 3em; 65 | text-align: right; 66 | z-index: 11; 67 | color: #AAAAAA; 68 | cursor: pointer; 69 | } 70 | 71 | code { 72 | font-family: courier new, courier, monospace; 73 | color: #033; 74 | } 75 | 76 | p { 77 | margin: 1em 0; 78 | orphans: 2; 79 | } 80 | 81 | pre.preformatted, pre.code { 82 | margin: 1.1em 12px; 83 | border: 1px solid #CCCCCC; 84 | padding: .4em; 85 | font-family: courier new, courier, monospace; 86 | font-size: 90%; 87 | page-break-inside: avoid; 88 | } 89 | 90 | pre.invalid { 91 | border: 1px solid #FF9999; 92 | } 93 | 94 | a:link { 95 | color: #223388; 96 | text-decoration: none; 97 | } 98 | 99 | a:visited { 100 | color: #554477; 101 | text-decoration: none; 102 | } 103 | 104 | a:hover { 105 | text-decoration: underline; 106 | } 107 | 108 | .footref, span.exponent { 109 | vertical-align: super; 110 | font-size: 60%; 111 | } 112 | 113 | span.footref { 114 | color: #223388; 115 | cursor: pointer; 116 | } 117 | 118 | ol.footnotes { 119 | font-size: 70%; 120 | padding-left: 2em; 121 | } 122 | 123 | div.footnotefloat { 124 | font-size: 80%; 125 | } 126 | 127 | div.footnotefloat code { 128 | color: #002222; 129 | } 130 | 131 | /* Revolting hack to make image-rulers work in IE */ 132 | hr { 133 | background: url(../img/hr.png) no-repeat top center; 134 | filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='img/hr.png'); 135 | width: 1px; 136 | height: 1px; 137 | border: 0; 138 | } 139 | * > hr { 140 | width: 100%; 141 | height: 10px; 142 | } 143 | 144 | blockquote { 145 | font-style: italic; 146 | page-break-inside: avoid; 147 | } 148 | 149 | blockquote em { 150 | font-style: normal; 151 | } 152 | 153 | div.illustration { 154 | padding-left: 1em; 155 | page-break-inside: avoid; 156 | } 157 | 158 | div.picture { 159 | text-align: center; 160 | page-break-inside: avoid; 161 | } 162 | 163 | div.navigation { 164 | font-size: 85%; 165 | color: #AAAAAA; 166 | margin-top: 2em; 167 | } 168 | 169 | div.navigation a:link { 170 | color: #888888; 171 | } 172 | 173 | div.navigation a:visited { 174 | color: #888888; 175 | } 176 | 177 | ul.index { 178 | list-style-type: none; 179 | margin: 1em 0; 180 | padding: 0; 181 | } 182 | 183 | div.toggle { 184 | color: #AA0000; 185 | cursor: pointer; 186 | } 187 | 188 | div.toggle:hover { 189 | text-decoration: underline; 190 | } 191 | 192 | div.floater { 193 | position: absolute; 194 | border: 1px solid #888888; 195 | background-color: #EEEEEE; 196 | padding: .3em; 197 | z-index: 12; 198 | } 199 | 200 | button.react { 201 | position: absolute; 202 | z-index: 10; 203 | left: 3px; 204 | top: 3px; 205 | border: 0; 206 | height: 23px; 207 | width: 30px; 208 | padding: 0; 209 | background-image: url(../img/react.png); 210 | background-color: transparent; 211 | cursor: pointer; 212 | } 213 | 214 | div.reactpopup { 215 | position: absolute; 216 | font-family: tahoma, arial, sans-serif; 217 | padding: .2em .5em; 218 | border: 1px solid #888888; 219 | background-color: #EEEEEE; 220 | top: 27px; 221 | left: 3px; 222 | z-index: 12; 223 | overflow: auto; /* workaround for FF invisible-cursor bug */ 224 | } 225 | 226 | div.reactpopup p { 227 | margin: .3em 0; 228 | } 229 | 230 | div.reactpopup span { 231 | width: 7.5em; 232 | display: block; 233 | float: left; 234 | } 235 | 236 | div.reactpopup input { 237 | border: 1px solid #888888; 238 | padding: 2px; 239 | vertical-align: middle; 240 | width: 12.5em; 241 | } 242 | 243 | div.reactpopup textarea { 244 | width: 24em; 245 | height: 10em; 246 | border: 1px solid #888888; 247 | padding: 2px; 248 | font-family: tahoma, arial, sans-serif; 249 | } 250 | 251 | div.reactpopup button { 252 | border: 1px solid #888888; 253 | background-color: white; 254 | } 255 | 256 | div.footer { 257 | font-size: 80%; 258 | color: #AAAAAA; 259 | margin-top: 2em; 260 | } 261 | 262 | div.footer a:link, div.footer a:visited { 263 | color: #6688BB; 264 | } 265 | 266 | a.donate { 267 | position: absolute; 268 | font-family: tahoma, arial, sans-serif; 269 | z-index: 12; 270 | left: 6px; 271 | top: 33px; 272 | text-decoration: none; 273 | color: #CCCCCC; 274 | font-size: 70%; 275 | font-weight: bold; 276 | } 277 | 278 | a.paragraph { 279 | position: absolute; 280 | color: white; 281 | color: transparent; 282 | display: block; 283 | right: 0px; 284 | padding: 0 1em; 285 | margin-right: -2em; 286 | text-decoration: none; 287 | } 288 | 289 | p:hover a.paragraph, a.paragraph:hover { color: #aaa; } 290 | -------------------------------------------------------------------------------- /web/errata.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Eloquent JavaScript -- errata 4 | 5 | 6 | 9 | 10 | 11 |
12 |

Eloquent JavaScript: Errata

13 | 14 |

These are the known mistakes in the first print of the 15 | paper edition of Eloquent JavaScript. If you think you 16 | have found another one, please send me an email.

18 | 19 |

Chapter 1

20 | 21 |

Page 12: In the example 'You 22 | ain't never seen a donkey fly!', the quote 23 | in ain't should be escaped.

24 | 25 |

Page 17: "can be evoked" should say 26 | "can be invoked".

27 | 28 |

Page 21: Bottom paragraph, "writen" 29 | should be "written".

30 | 31 |

Page 24: In the paragraph above 32 | "Capitalization" header, an "s" is missing in "Then it start 33 | executing".

34 | 35 |

Page 27: In the top paragraph, 36 | NaN should also be listed as a value that is 37 | falsy.

38 | 39 |

Chapter 2

40 | 41 |

Page 35: In the code snippet 42 | that defines a power function, the first argument 43 | to the function should be base, not 44 | number.

45 | 46 |

Page 39: The part "all the operators 47 | that were performed" should be "all the operations...".

48 | 49 |

Chapter 3

50 | 51 |

Page 48: Third paragraph, "the value 52 | of which is a property" should say "the value of which it is 53 | a property".

54 | 55 |

Chapter 5

56 | 57 |

Pages 79, 81, 82, and 85: All 58 | instances of the word 'header' should say 'heading' instead.

59 | 60 |

Page 82: The second line of code 61 | snippet at the top of the page should be 62 | paragraphs.length (with an s), not 63 | paragraph.length.

64 | 65 |

Page 83: At the bottom of the 66 | page, the line that calls indexOrEnd has one too 67 | many closing parentheses at the end.

68 | 69 |

Page 87: At the very end of the page, there's a double quote missing at the end of the string " src=\"picture.png\" alt=\"The Picture\"".

70 | 71 |

Page 88: In the code fragment at the 72 | top, a semicolon is missing after the first return, though I claimed 73 | I'd write out all semicolons.

74 | 75 |

Page 89: In the 76 | renderFragment code snippet, the references to 77 | number should say fragment.number 78 | instead. Further down, the paragraph that starts "Rending a 79 | whole paragraph..." should say "Rendering a whole 81 | paragraph" instead.

82 | 83 |

Chapter 6

84 | 85 |

Page 106: Second paragraph, 86 | where it says "above and to the left", it should say "above and 87 | to the right".

88 | 89 |

Page 108: In the code snippet 90 | that introduces bind, as well as the text below 91 | that, any references to testArray should actually 92 | read x.

93 | 94 |

Page 109: In the definition of 95 | method, a return should be written 96 | before the expression in the inner function.

97 | 98 |

Page 110 and 99 | 116: In both definitions of processCreate, 100 | all references to creature should be replaced by 101 | creature.object, and all references to 102 | point with creature.point.

103 | 104 |

Page 117: In the code that 105 | defines creatureMove, the boolean operator in the 106 | if should be &&, not 107 | ||.

108 | 109 |

Page 120: In the code example, 110 | the inner loop variable should be j, not 111 | i.

112 | 113 |

Page 122: In the code snippet, 114 | the condition surroundings[this.direction] != "" 115 | should have && emptySpace.length > 0 116 | appended to it.

117 | 118 |

Page 125: In the call to 119 | print at the bottom of the page, the comma and the 120 | dot before this.details should be switched (dot in 121 | quotes, comma after them).

122 | 123 |

Chapter 7

124 | 125 |

Page 131: First paragraph under "The 126 | Shape of a Module", "come up some techniques" should be "come 127 | up with some techniques".

128 | 129 |

Page 136: Third paragraph, first 130 | sentence, "return true" should be "returns true".

131 | 132 |

Chapter 8

133 | 134 |

Page 140: In the code example at 135 | the bottom of the page, the result of the search 136 | call should be 2, not 3.

137 | 138 |

Chapter 11

139 | 140 |

Page 181: In the code, the 141 | semicolon after boulders should be a colon.

142 | 143 | 145 | 146 |
147 | 148 | 149 | -------------------------------------------------------------------------------- /web/js/chapter/search.js: -------------------------------------------------------------------------------- 1 | load("FunctionalTools.js"); 2 | 3 | var heightAt = function(){ 4 | var heights = [[111,111,122,137,226,192,246,275,285,333,328,264,202,175,151,222,250,222,219,146], 5 | [205,186,160,218,217,233,268,300,316,357,276,240,240,253,215,201,256,312,224,200], 6 | [228,176,232,258,246,289,306,351,374,388,319,333,299,307,261,286,291,355,277,258], 7 | [228,207,263,264,284,348,368,358,391,387,320,344,366,382,372,394,360,314,259,207], 8 | [238,237,275,315,353,355,341,332,350,315,283,310,355,350,336,405,361,273,264,228], 9 | [245,264,289,340,359,349,336,303,267,259,285,340,315,290,333,372,306,254,220,220], 10 | [264,287,331,365,382,381,386,360,299,258,254,284,264,276,295,323,281,233,202,160], 11 | [300,327,360,355,365,402,393,343,307,274,232,226,221,262,289,250,252,228,160,160], 12 | [343,379,373,337,309,336,378,352,303,290,294,241,176,204,235,205,203,206,169,132], 13 | [348,348,364,369,337,276,321,390,347,354,309,259,208,147,158,165,169,169,200,147], 14 | [320,328,334,348,354,316,254,315,303,297,283,238,229,207,156,129,128,161,174,165], 15 | [297,331,304,283,283,279,250,243,264,251,226,204,155,144,154,147,120,111,129,138], 16 | [302,347,332,326,314,286,223,205,202,178,160,172,171,132,118,116,114, 96, 80, 75], 17 | [287,317,310,293,284,235,217,305,286,229,211,234,227,243,188,160,152,129,138,101], 18 | [260,277,269,243,236,255,343,312,280,220,252,280,298,288,252,210,176,163,133,112], 19 | [266,255,254,254,265,307,350,311,267,276,292,355,305,250,223,200,197,193,166,158], 20 | [306,312,328,279,287,320,377,359,289,328,367,355,271,250,198,163,139,155,153,190], 21 | [367,357,339,330,290,323,363,374,330,331,415,446,385,308,241,190,145, 99, 88,145], 22 | [342,362,381,359,353,353,369,391,384,372,408,448,382,358,256,178,143,125, 85,109], 23 | [311,337,358,376,330,341,342,374,411,408,421,382,271,311,246,166,132,116,108, 72]]; 24 | return function(point) { 25 | return heights[point.y][point.x]; 26 | }; 27 | }(); 28 | 29 | function BinaryHeap(scoreFunction){ 30 | this.content = []; 31 | this.scoreFunction = scoreFunction; 32 | } 33 | 34 | BinaryHeap.prototype = { 35 | push: function(element) { 36 | // Add the new element to the end of the array. 37 | this.content.push(element); 38 | // Allow it to bubble up. 39 | this.bubbleUp(this.content.length - 1); 40 | }, 41 | 42 | pop: function() { 43 | // Store the first element so we can return it later. 44 | var result = this.content[0]; 45 | // Get the element at the end of the array. 46 | var end = this.content.pop(); 47 | // If there are any elements left, put the end element at the 48 | // start, and let it sink down. 49 | if (this.content.length > 0) { 50 | this.content[0] = end; 51 | this.sinkDown(0); 52 | } 53 | return result; 54 | }, 55 | 56 | remove: function(node) { 57 | var length = this.content.length; 58 | // To remove a value, we must search through the array to find 59 | // it. 60 | for (var i = 0; i < length; i++) { 61 | if (this.content[i] != node) continue; 62 | // When it is found, the process seen in 'pop' is repeated 63 | // to fill up the hole. 64 | var end = this.content.pop(); 65 | // If the element we popped was the one we needed to remove, 66 | // we're done. 67 | if (i == length - 1) break; 68 | // Otherwise, we replace the removed element with the popped 69 | // one, and allow it to float up or sink down as appropriate. 70 | this.content[i] = end; 71 | this.bubbleUp(i); 72 | this.sinkDown(i); 73 | break; 74 | } 75 | }, 76 | 77 | size: function() { 78 | return this.content.length; 79 | }, 80 | 81 | bubbleUp: function(n) { 82 | // Fetch the element that has to be moved. 83 | var element = this.content[n], score = this.scoreFunction(element); 84 | // When at 0, an element can not go up any further. 85 | while (n > 0) { 86 | // Compute the parent element's index, and fetch it. 87 | var parentN = Math.floor((n + 1) / 2) - 1, 88 | parent = this.content[parentN]; 89 | // If the parent has a lesser score, things are in order and we 90 | // are done. 91 | if (score >= this.scoreFunction(parent)) 92 | break; 93 | 94 | // Otherwise, swap the parent with the current element and 95 | // continue. 96 | this.content[parentN] = element; 97 | this.content[n] = parent; 98 | n = parentN; 99 | } 100 | }, 101 | 102 | sinkDown: function(n) { 103 | // Look up the target element and its score. 104 | var length = this.content.length, 105 | element = this.content[n], 106 | elemScore = this.scoreFunction(element); 107 | 108 | while(true) { 109 | // Compute the indices of the child elements. 110 | var child2N = (n + 1) * 2, child1N = child2N - 1; 111 | // This is used to store the new position of the element, 112 | // if any. 113 | var swap = null; 114 | // If the first child exists (is inside the array)... 115 | if (child1N < length) { 116 | // Look it up and compute its score. 117 | var child1 = this.content[child1N], 118 | child1Score = this.scoreFunction(child1); 119 | // If the score is less than our element's, we need to swap. 120 | if (child1Score < elemScore) 121 | swap = child1N; 122 | } 123 | // Do the same checks for the other child. 124 | if (child2N < length) { 125 | var child2 = this.content[child2N], 126 | child2Score = this.scoreFunction(child2); 127 | if (child2Score < (swap == null ? elemScore : child1Score)) 128 | swap = child2N; 129 | } 130 | 131 | // No need to swap further, we are done. 132 | if (swap == null) break; 133 | 134 | // Otherwise, swap and continue. 135 | this.content[n] = this.content[swap]; 136 | this.content[swap] = element; 137 | n = swap; 138 | } 139 | } 140 | }; 141 | 142 | function showRoute() { 143 | var routes = arguments; 144 | parent.withDocument(document, function() { 145 | var img = parent.DIV({"class": "outputimage"}, parent.IMG({src: "img/height.png"})); 146 | __ENV.output(img); 147 | forEach(routes, function(route) { 148 | while(route) { 149 | var marker = parent.DIV({"class": "marker"}); 150 | img.appendChild(marker); 151 | parent.centerElement(marker, {x: route.point.x * 20 + 12, y: route.point.y * 20 + 12}); 152 | route = route.from; 153 | } 154 | }); 155 | }); 156 | } 157 | -------------------------------------------------------------------------------- /web/css/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | } 8 | .CodeMirror-scroll { 9 | /* Set scrolling behaviour here */ 10 | overflow: auto; 11 | } 12 | 13 | /* PADDING */ 14 | 15 | .CodeMirror-lines { 16 | padding: 4px 0; /* Vertical padding around content */ 17 | } 18 | .CodeMirror pre { 19 | padding: 0 4px; /* Horizontal padding of content */ 20 | } 21 | 22 | .CodeMirror-scrollbar-filler { 23 | background-color: white; /* The little square between H and V scrollbars */ 24 | } 25 | 26 | /* GUTTER */ 27 | 28 | .CodeMirror-gutters { 29 | border-right: 1px solid #ddd; 30 | background-color: #f7f7f7; 31 | } 32 | .CodeMirror-linenumbers {} 33 | .CodeMirror-linenumber { 34 | padding: 0 3px 0 5px; 35 | min-width: 20px; 36 | text-align: right; 37 | color: #999; 38 | } 39 | 40 | /* CURSOR */ 41 | 42 | .CodeMirror div.CodeMirror-cursor { 43 | border-left: 1px solid black; 44 | } 45 | /* Shown when moving in bi-directional text */ 46 | .CodeMirror div.CodeMirror-secondarycursor { 47 | border-left: 1px solid silver; 48 | } 49 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { 50 | width: auto; 51 | border: 0; 52 | background: transparent; 53 | background: rgba(0, 200, 0, .4); 54 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800); 55 | } 56 | /* Kludge to turn off filter in ie9+, which also accepts rgba */ 57 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor:not(#nonsense_id) { 58 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 59 | } 60 | /* Can style cursor different in overwrite (non-insert) mode */ 61 | .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {} 62 | 63 | /* DEFAULT THEME */ 64 | 65 | .cm-s-default .cm-keyword {color: #708;} 66 | .cm-s-default .cm-atom {color: #219;} 67 | .cm-s-default .cm-number {color: #164;} 68 | .cm-s-default .cm-def {color: #00f;} 69 | .cm-s-default .cm-variable {color: black;} 70 | .cm-s-default .cm-variable-2 {color: #05a;} 71 | .cm-s-default .cm-variable-3 {color: #085;} 72 | .cm-s-default .cm-property {color: black;} 73 | .cm-s-default .cm-operator {color: black;} 74 | .cm-s-default .cm-comment {color: #a50;} 75 | .cm-s-default .cm-string {color: #a11;} 76 | .cm-s-default .cm-string-2 {color: #f50;} 77 | .cm-s-default .cm-meta {color: #555;} 78 | .cm-s-default .cm-error {color: #f00;} 79 | .cm-s-default .cm-qualifier {color: #555;} 80 | .cm-s-default .cm-builtin {color: #30a;} 81 | .cm-s-default .cm-bracket {color: #997;} 82 | .cm-s-default .cm-tag {color: #170;} 83 | .cm-s-default .cm-attribute {color: #00c;} 84 | .cm-s-default .cm-header {color: blue;} 85 | .cm-s-default .cm-quote {color: #090;} 86 | .cm-s-default .cm-hr {color: #999;} 87 | .cm-s-default .cm-link {color: #00c;} 88 | 89 | .cm-negative {color: #d44;} 90 | .cm-positive {color: #292;} 91 | .cm-header, .cm-strong {font-weight: bold;} 92 | .cm-em {font-style: italic;} 93 | .cm-emstrong {font-style: italic; font-weight: bold;} 94 | .cm-link {text-decoration: underline;} 95 | 96 | .cm-invalidchar {color: #f00;} 97 | 98 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 99 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 100 | 101 | /* STOP */ 102 | 103 | /* The rest of this file contains styles related to the mechanics of 104 | the editor. You probably shouldn't touch them. */ 105 | 106 | .CodeMirror { 107 | line-height: 1; 108 | position: relative; 109 | overflow: hidden; 110 | } 111 | 112 | .CodeMirror-scroll { 113 | /* 30px is the magic margin used to hide the element's real scrollbars */ 114 | /* See overflow: hidden in .CodeMirror, and the paddings in .CodeMirror-sizer */ 115 | margin-bottom: -30px; margin-right: -30px; 116 | padding-bottom: 30px; padding-right: 30px; 117 | height: 100%; 118 | outline: none; /* Prevent dragging from highlighting the element */ 119 | position: relative; 120 | } 121 | .CodeMirror-sizer { 122 | position: relative; 123 | } 124 | 125 | /* The fake, visible scrollbars. Used to force redraw during scrolling 126 | before actuall scrolling happens, thus preventing shaking and 127 | flickering artifacts. */ 128 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler { 129 | position: absolute; 130 | z-index: 6; 131 | display: none; 132 | } 133 | .CodeMirror-vscrollbar { 134 | right: 0; top: 0; 135 | overflow-x: hidden; 136 | overflow-y: scroll; 137 | } 138 | .CodeMirror-hscrollbar { 139 | bottom: 0; left: 0; 140 | overflow-y: hidden; 141 | overflow-x: scroll; 142 | } 143 | .CodeMirror-scrollbar-filler { 144 | right: 0; bottom: 0; 145 | z-index: 6; 146 | } 147 | 148 | .CodeMirror-gutters { 149 | position: absolute; left: 0; top: 0; 150 | height: 100%; 151 | z-index: 3; 152 | } 153 | .CodeMirror-gutter { 154 | height: 100%; 155 | display: inline-block; 156 | /* Hack to make IE7 behave */ 157 | *zoom:1; 158 | *display:inline; 159 | } 160 | .CodeMirror-gutter-elt { 161 | position: absolute; 162 | cursor: default; 163 | z-index: 4; 164 | } 165 | 166 | .CodeMirror-lines { 167 | cursor: text; 168 | } 169 | .CodeMirror pre { 170 | /* Reset some styles that the rest of the page might have set */ 171 | -moz-border-radius: 0; -webkit-border-radius: 0; -o-border-radius: 0; border-radius: 0; 172 | border-width: 0; 173 | background: transparent; 174 | font-family: inherit; 175 | font-size: inherit; 176 | margin: 0; 177 | white-space: pre; 178 | word-wrap: normal; 179 | line-height: inherit; 180 | color: inherit; 181 | z-index: 2; 182 | position: relative; 183 | overflow: visible; 184 | } 185 | .CodeMirror-wrap pre { 186 | word-wrap: break-word; 187 | white-space: pre-wrap; 188 | word-break: normal; 189 | } 190 | .CodeMirror-linebackground { 191 | position: absolute; 192 | left: 0; right: 0; top: 0; bottom: 0; 193 | z-index: 0; 194 | } 195 | 196 | .CodeMirror-linewidget { 197 | position: relative; 198 | z-index: 2; 199 | overflow: auto; 200 | } 201 | 202 | .CodeMirror-wrap .CodeMirror-scroll { 203 | overflow-x: hidden; 204 | } 205 | 206 | .CodeMirror-measure { 207 | position: absolute; 208 | width: 100%; height: 0px; 209 | overflow: hidden; 210 | visibility: hidden; 211 | } 212 | .CodeMirror-measure pre { position: static; } 213 | 214 | .CodeMirror div.CodeMirror-cursor { 215 | position: absolute; 216 | visibility: hidden; 217 | border-right: none; 218 | width: 0; 219 | } 220 | .CodeMirror-focused div.CodeMirror-cursor { 221 | visibility: visible; 222 | } 223 | 224 | .CodeMirror-selected { background: #d9d9d9; } 225 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 226 | 227 | .cm-searching { 228 | background: #ffa; 229 | background: rgba(255, 255, 0, .4); 230 | } 231 | 232 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 233 | .CodeMirror span { *vertical-align: text-bottom; } 234 | 235 | @media print { 236 | /* Hide the cursor when printing */ 237 | .CodeMirror div.CodeMirror-cursor { 238 | visibility: hidden; 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /web/js/base-env.js: -------------------------------------------------------------------------------- 1 | var ARCHIVE = 2 | ["Nephew,\n\nI bought a computer as soon as I received your letter. It took me two days to make it do 'internet', but I just kept calling the nice man at the computer shop, and in the end he came down to help personally. Send me something back if you receive this, so I know whether it actually works.\n\nLove,\nAunt Emily", 3 | "Dear Nephew,\n\nVery good! I feel quite proud about being so technologically minded, having a computer and all. I bet Mrs. Goor down the street wouldn't even know how to plug it in, that witch.\n\nAnyway, thanks for sending me that game, it was great fun. After three days, I beat it. My friend Mrs. Johnson was quite worried when I didn't come outside or answer the phone for three days, but I explained to her that I was working with my computer.\n\nMy cat had two kittens yesterday! I didn't even realize the thing was pregnant. I've listed the names at the bottom of my letter, so that you will know how to greet them the next time you come over.\n\nSincerely,\nAunt Emily\n\nborn 15/02/1999 (mother Spot): Clementine, Fireball", 4 | "[... and so on ...]\n\nborn 21/09/2000 (mother Spot): Yellow Emperor, Black Leclère", 5 | "...\n\nborn 02/04/2001 (mother Clementine): Bugeye, Wolverine, Miss Bushtail", 6 | "...\n\ndied 12/12/2002: Clementine\n\ndied 15/12/2002: Wolverine", 7 | "...\n\nborn 15/11/2003 (mother Spot): White Fang", 8 | "...\n\nborn 10/04/2003 (mother Miss Bushtail): Yellow Bess", 9 | "...\n\ndied 30/05/2004: Yellow Emperor", 10 | "...\n\nborn 01/06/2004 (mother Miss Bushtail): Catharina, Fat Igor", 11 | "...\n\nborn 20/09/2004 (mother Yellow Bess): Doctor Hobbles the 2nd, Noog", 12 | "...\n\nborn 15/01/2005 (mother Yellow Bess): The Moose, Liger\n\ndied 17/01/2005: Liger", 13 | "Dear nephew,\n\nYour mother told me you have taken up skydiving. Is this true? You watch yourself, young man! Remember what happened to my husband? And that was only from the second floor!\n\nAnyway, things are very exciting here. I have spent all week trying to get the attention of Mr. Drake, the nice gentleman who moved in next\ndoor, but I think he is afraid of cats. Or allergic to them? I am\ngoing to try putting Fat Igor on his shoulder next time I see him, very curious what will happen.\n\nAlso, the scam I told you about is going better than expected. I have already gotten back five 'payments', and only one complaint. It is starting to make me feel a bit bad though. And you are right that it is probably illegal in some way.\n\n(... etc ...)\n\nMuch love,\nAunt Emily\n\ndied 27/04/2006: Black Leclère\n\nborn 05/04/2006 (mother Lady Penelope): Red Lion, Doctor Hobbles the 3rd, Little Iroquois", 14 | "...\n\nborn 22/07/2006 (mother Noog): Goblin, Reginald, Little Maggie", 15 | "...\n\ndied 13/02/2007: Spot\n\ndied 21/02/2007: Fireball", 16 | "...\n\nborn 05/02/2007 (mother Noog): Long-ear Johnson", 17 | "...\n\nborn 03/03/2007 (mother Catharina): Asoka, Dark Empress, Rabbitface"]; 18 | 19 | var RECLUSEFILE = "% The Book of Programming\n\n%% The Two Aspects\n\nBelow the surface of the machine, the program moves. Without effort,\nit expands and contracts. In great harmony, electrons scatter and\nregroup. The forms on the monitor are but ripples on the water. The\nessence stays invisibly below.\n\nWhen the creators built the machine, they put in the processor and the\nmemory. From these arise the two aspects of the program.\n\nThe aspect of the processor is the active substance. It is called\nControl. The aspect of the memory is the passive substance. It is\ncalled Data.\n\nData is made of merely bits, yet it takes complex forms. Control\nconsists only of simple instructions, yet it performs difficult\ntasks. From the small and trivial, the large and complex arise.\n\nThe program source is Data. Control arises from it. The Control\nproceeds to create new Data. The one is born from the other, the\nother is useless without the one. This is the harmonious cycle of\nData and Control.\n\nOf themselves, Data and Control are without structure. The programmers\nof old molded their programs out of this raw substance. Over time,\nthe amorphous Data has crystallized into data types, and the chaotic\nControl was wrung into control structures and functions.\n\n%% Short Sayings\n\nWhen a student asked Fu-Tzu about the nature of the cycle of Data and\nControl, Fu-Tzu replied 'Think of a compiler, compiling itself.'\n\nA student asked, 'The programmers of old used only simple machines and\nno programming languages, yet they made beautiful programs. Why do we\nuse complicated machines and programming languages?' Fu-Tzu replied\n'The builders of old used only sticks and clay, yet they made\nbeautiful huts.'\n\nA hermit spent ten years writing a program. 'My program can compute\nthe motion of the stars on a 286-computer running MS DOS,' he proudly\nannounced. 'Nobody owns a 286-computer or uses MS DOS anymore,'\nFu-Tzu responded.\n\nFu-Tzu had written a small program that was full of global state and\ndubious shortcuts. Reading it, a student asked 'You warned us against\nthese techniques, yet I find them in your program. How can this be?'\nFu-Tzu said, 'There is no need to fetch a water hose when the house is\nnot on fire.'{This is not to be read as an encouragement of sloppy\nprogramming, but rather as a warning against neurotic adherence to\nrules of thumb.}\n\n%% Wisdom\n\nA student was complaining about digital numbers. 'When I take the root\nof two and then square it again, the result is already inaccurate!'\nOverhearing him, Fu-Tzu laughed. 'Here is a sheet of paper. Write down\nthe precise value of the square root of two for me.'\n\nFu-Tzu said, 'When you cut against the grain of the wood, much strength\nis needed. When you program against the grain of a problem, much code\nis needed.'\n\nTzu-li and Tzu-ssu were boasting about the size of their latest\nprograms. 'Two-hundred thousand lines,' said Tzu-li, 'not counting\ncomments!' Tzu-ssu responded, 'Psah, mine is almost a *million* lines\nalready.' Fu-Tzu said, 'My best program has five hundred lines.'\nHearing this, Tzu-li and Tzu-ssu were enlightened.\n\nA student had been sitting motionless behind his computer for hours,\nfrowning darkly. He was trying to write a beautiful solution to a\ndifficult problem but could not find the right approach. Fu-Tzu hit\nhim on the back of his head and shouted, '*Type something!*' The student\nstarted writing an ugly solution. After he had finished, he suddenly\nunderstood the beautiful solution.\n\n%% Progression\n\nA beginning programmer writes his programs like an ant builds her\nhill, one piece at a time, without thought for the bigger structure.\nHis programs will be like loose sand. They may stand for a while, but\ngrowing too big they fall apart{Referring to the danger of internal\ninconsistency and duplicated structure in unorganized code.}.\n\nRealizing this problem, the programmer will start to spend a lot of\ntime thinking about structure. His programs will be rigidly\nstructured, like rock sculptures. They are solid, but when they must\nchange, violence must be done to them{Referring to the fact that\nstructure tends to put restrictions on the evolution of a program.}.\n\nThe master programmer knows when to apply structure and when to leave\nthings in their simple form. His programs are like clay, solid yet\nmalleable.\n"; 20 | 21 | var animateTerrarium = function() { 22 | var animating = null, ie = /MSIE \d+/.test(navigator.userAgent); 23 | return function(t) { 24 | function stop() {clearTimeout(animating); animating = null;} 25 | if (animating) stop(); 26 | document.body.innerHTML = ""; 27 | var pre = document.body.appendChild(document.createElement("PRE")); 28 | animating = setInterval(function() { 29 | if (!pre.parentNode) return stop(); 30 | t.step(); 31 | pre.innerHTML = ""; 32 | var text = t.toString(); 33 | if (ie) text = text.replace(/\n/g, "\r"); 34 | pre.appendChild(document.createTextNode(text)); 35 | }, 500); 36 | return animating; 37 | }; 38 | }(); 39 | 40 | function forEach(array, action) { 41 | for (var i = 0, l = array.length; i < l; i++) 42 | action(array[i]); 43 | } 44 | 45 | function forEachIn(object, action) { 46 | for (var property in object) 47 | if (Object.prototype.hasOwnProperty.call(object, property)) 48 | action(property, object[property]); 49 | } 50 | 51 | function map(func, array) { 52 | var l = array.length, result = new Array(l); 53 | for (var i = 0; i < l; i++) 54 | result[i] = func(array[i]); 55 | return result; 56 | } 57 | 58 | // Sokoban levels based on those in the Nethack game 59 | // (http://www.nethack.org). 60 | 61 | var SOKOBANLEVELS = [ 62 | {field: ["###### ##### ", 63 | "# # # # ", 64 | "# 0 #### 0 # ", 65 | "# 0 @ 0 # ", 66 | "# #######0 # ", 67 | "#### ### ###", 68 | " # #", 69 | " #0 #", 70 | " # 0 #", 71 | " ## 0 #", 72 | " #*0 0 #", 73 | " ########"], 74 | boulders: 10}, 75 | 76 | {field: ["########### ", 77 | "# # # ", 78 | "# 00#00 @# ", 79 | "# 0 # ", 80 | "# # # ", 81 | "## ######### ", 82 | "# 0 # # ", 83 | "# 00 #0 0 0# ", 84 | "# 0 0 # ", 85 | "# 000#0 0 ###", 86 | "# # 0 0 *#", 87 | "##############"], 88 | boulders: 20}, 89 | 90 | {field: ["########## ", 91 | "#@ *# ", 92 | "# ## ", 93 | "####### ######", 94 | " # #", 95 | " # 0 0 0 0 0 #", 96 | "######## #####", 97 | "# 0 0 0 0 #", 98 | "# 0 #", 99 | "##### ########", 100 | " # 0 0 0 # ", 101 | " # 0 # ", 102 | " # 0 0 0 ## ", 103 | "####### #### ", 104 | "# 0 # ", 105 | "# # ", 106 | "# ###### ", 107 | "##### "], 108 | boulders: 16}, 109 | 110 | {field: [" #### ", 111 | "## @######## ", 112 | "# # ", 113 | "# 0#####0# # ", 114 | "# # # 0 # ", 115 | "# 0 0 0## ", 116 | "# 0 0 # # ", 117 | "# ####0 ## # ", 118 | "# 0 0 # ## ", 119 | "# ###0# 0 ##", 120 | "# # 0# 0 *#", 121 | "# 0 ####", 122 | "##### # # ", 123 | " ####### "], 124 | boulders: 12}, 125 | 126 | {field: [" ### ", 127 | "####*# #####", 128 | "# #0## ## #", 129 | "# #### 0 #", 130 | "# 00 # # 0 #", 131 | "## 00# 00 ##", 132 | "#0 0 #0 # ", 133 | "# 00 # # 0# ", 134 | "# 0 0#### 0 # ", 135 | "# # ## ", 136 | "#### 0 #### ", 137 | " ### ## # ", 138 | " # 0 # ", 139 | " #@ # # ", 140 | " ####### "], 141 | boulders: 18}]; 142 | 143 | function Point(x, y) { 144 | this.x = x; 145 | this.y = y; 146 | } 147 | Point.prototype.add = function(other) { 148 | return new Point(this.x + other.x, this.y + other.y); 149 | }; 150 | Point.prototype.isEqualTo = function(other) { 151 | return this.x == other.x && this.y == other.y; 152 | }; 153 | 154 | function dom(name, attributes) { 155 | var node = document.createElement(name); 156 | if (attributes) { 157 | forEachIn(attributes, function(name, value) { 158 | node.setAttribute(name, value); 159 | }); 160 | } 161 | for (var i = 2; i < arguments.length; i++) { 162 | var child = arguments[i]; 163 | if (typeof child == "string") 164 | child = document.createTextNode(child); 165 | node.appendChild(child); 166 | } 167 | return node; 168 | } 169 | 170 | function method(object, name) { 171 | return function() { 172 | object[name].apply(object, arguments); 173 | }; 174 | } 175 | -------------------------------------------------------------------------------- /renderer/Highlight.hs: -------------------------------------------------------------------------------- 1 | module Highlight (highlightStatements, highlightExpression) where 2 | 3 | import Html 4 | import Text.ParserCombinators.Parsec 5 | import Data.Char 6 | import Data.Map hiding (map, null) 7 | import Data.Set hiding (map, null) 8 | 9 | -- Parser 10 | 11 | data WordType = LikeIf | LikeDo | LikeNew | Function | Var | Catch | For | Case | Operator | Atom | Variable 12 | deriving (Show, Eq) 13 | 14 | wordTypes :: Map String WordType 15 | wordTypes = Data.Map.fromList([("if", LikeIf), ("switch", LikeIf), ("while", LikeIf), ("with", LikeIf), 16 | ("else", LikeDo), ("do", LikeDo), ("try", LikeDo), ("finally", LikeDo), 17 | ("return", LikeNew), ("break", LikeNew), ("continue", LikeNew), 18 | ("new", LikeNew), ("delete", LikeNew), ("throw", LikeNew), 19 | ("in", Operator), ("typeof", Operator), ("instanceof", Operator), 20 | ("var", Var), ("for", For), ("case", Case), ("function", Function), ("catch", Catch), 21 | ("true", Atom), ("false", Atom), ("undefined", Atom), ("null", Atom), 22 | ("NaN", Atom), ("Infinity", Atom)]); 23 | 24 | wordType :: String -> WordType 25 | wordType word = findWithDefault Variable word wordTypes 26 | 27 | data PState = PS [[String]] [(String, String)] 28 | 29 | output :: String -> String -> CharParser PState () 30 | output text style = updateState write 31 | where write (PS env out) = PS env ((text, style):out) 32 | 33 | getOutput :: CharParser PState [(String, String)] 34 | getOutput = do (PS _ ps) <- getState 35 | return ps 36 | 37 | pushContext :: CharParser PState () 38 | pushContext = updateState push 39 | where push (PS env out) = PS (["arguments", "this"]:env) out 40 | 41 | popContext :: CharParser PState () 42 | popContext = updateState pop 43 | where pop (PS (e:es) out) = PS es out 44 | 45 | registerVariable :: String -> CharParser PState Bool 46 | registerVariable name = do PS env out <- getState 47 | if (null env) 48 | then return False 49 | else do setState (PS (update env) out) 50 | return True 51 | where update (e:es) = ((name:e):es) 52 | 53 | isLocalVariable :: String -> CharParser PState Bool 54 | isLocalVariable name = do PS env _ <- getState 55 | return ((any (elem name)) env) 56 | 57 | punctuation :: Char -> CharParser PState () 58 | punctuation c = do char c 59 | output [c] "punctuation" 60 | ws 61 | 62 | commaSep :: CharParser PState a -> CharParser PState [a] 63 | commaSep p = sepBy p (punctuation ',') 64 | 65 | zeroOrOne :: CharParser st t -> CharParser st [t] 66 | zeroOrOne p = do one <- p 67 | return [one] 68 | <|> return [] 69 | 70 | symbolOperator :: CharParser st [Char] 71 | symbolOperator = many1 (oneOf "+-*&%/=<>!|") 72 | 73 | wordOperator :: CharParser st [Char] 74 | wordOperator = do name <- word 75 | if (wordType name /= Operator) 76 | then unexpected name 77 | else return name 78 | 79 | operator :: CharParser PState () 80 | operator = do op <- (symbolOperator <|> wordOperator) 81 | output op "operator" 82 | ws 83 | 84 | keyword :: String -> CharParser PState () 85 | keyword name = do string name 86 | output name "keyword" 87 | ws 88 | 89 | untilUnescaped :: Char -> CharParser st [Char] 90 | untilUnescaped c = do next <- anyChar 91 | if (next == c) 92 | then return [next] 93 | else if (next == '\\') 94 | then do next' <- anyChar 95 | rest <- untilUnescaped c 96 | return (next:next':rest) 97 | else do rest <- untilUnescaped c 98 | return (next:rest) 99 | 100 | regexp, stringValue, numberValue, hexNumberValue, ws :: CharParser PState () 101 | 102 | regexp = do char '/' 103 | content <- untilUnescaped '/' 104 | options <- many (oneOf "ig") 105 | output ('/':(content ++ options)) "string" 106 | ws 107 | 108 | stringValue = do delimiter <- (char '"' <|> char '\'') 109 | content <- untilUnescaped delimiter 110 | output (delimiter:content) "string" 111 | ws 112 | 113 | numberValue = do digits <- many1 digit 114 | dot <- ((try afterDot) <|> return "") 115 | exp <- (exponent <|> return "") 116 | output (digits ++ dot ++ exp) "atom" 117 | ws 118 | where afterDot = do char '.' 119 | digits <- many1 digit 120 | return ('.':digits) 121 | exponent = do exp <- oneOf "eE" 122 | minus <- zeroOrOne (char '-') 123 | digits <- many digit 124 | return (exp:(minus ++ digits)) 125 | 126 | hexNumberValue = do x <- try (do char '0' 127 | oneOf "xX") 128 | digits <- many1 (satisfy isHexDigit) 129 | output ('0':x:digits) "atom" 130 | ws 131 | 132 | ws = spaces <|> (try lineComment) <|> (try comment) <|> return () 133 | where spaces = do space <- many1 (satisfy isSpace) 134 | output space "whitespace" 135 | ws 136 | lineComment = do string "//" 137 | content <- many (satisfy (/='\n')) 138 | output ("//" ++ content) "comment" 139 | ws 140 | comment = do string "/*" 141 | content <- manyTill anyChar (try (string "*/")) 142 | output ("/*" ++ content ++ "*/") "comment" 143 | ws 144 | 145 | word :: CharParser st [Char] 146 | word = do start <- satisfy varStart 147 | rest <- many (satisfy varLetter) 148 | return (start:rest) 149 | where varStart c = isAlpha c || c == '_' || c == '$' 150 | varLetter c = varStart c || isDigit c 151 | 152 | statement :: CharParser PState () 153 | statement = (punctuation ';') <|> block <|> wordStatement <|> exprStatement 154 | where block = do punctuation '{' 155 | many statement 156 | punctuation '}' 157 | exprStatement = do expression 158 | punctuation ';' 159 | 160 | wordStatement, wordExpression :: CharParser PState () 161 | wordStatement = do start <- word 162 | perform start (action start) 163 | where perform start (Just act) = do output start "keyword" 164 | ws 165 | act 166 | perform start Nothing = (try (label start)) <|> (expr start) 167 | label start = do output start "property" 168 | ws 169 | punctuation ':' 170 | expr start = do wordExpression' start 171 | punctuation ';' 172 | action word = 173 | case (wordType word) 174 | of LikeIf -> Just (do punctuation '(' 175 | expression 176 | punctuation ')' 177 | statement) 178 | LikeDo -> Just statement 179 | Function -> Just functionDef 180 | Var -> Just (do commaSep variableDef 181 | punctuation ';') 182 | Catch -> Just (do pushContext 183 | punctuation '(' 184 | newVariable 185 | punctuation ')' 186 | statement 187 | popContext) 188 | For -> Just (do punctuation '(' 189 | (try forIn) <|> normalFor 190 | punctuation ')' 191 | statement) 192 | Case -> Just (do expression 193 | punctuation ':' 194 | statement) 195 | otherwise -> Nothing 196 | forIn = do optional (keyword "var") 197 | newVariable 198 | keyword "in" 199 | expression 200 | normalFor = do statement 201 | optional expression 202 | punctuation ';' 203 | optional expression 204 | 205 | wordExpression = do start <- word 206 | wordExpression' start 207 | 208 | wordExpression' :: String -> CharParser PState () 209 | wordExpression' start = 210 | case (wordType start) 211 | of LikeNew -> do name "keyword" 212 | optional expression 213 | Operator -> do name "operator" 214 | expression 215 | Function -> do name "keyword" 216 | functionDef 217 | Atom -> do name "atom" 218 | maybeOperator 219 | Variable -> do local <- isLocalVariable start 220 | name (case local of {True -> "localvariable"; False -> "variable"}) 221 | maybeOperator 222 | where name n = do output start n 223 | ws 224 | 225 | newVariable, variableDef, functionDef, propertyName, objectLiteral, arrayLiteral, 226 | expression, postFixOperator, maybeOperator :: CharParser PState () 227 | 228 | newVariable = do name <- word 229 | local <- registerVariable name 230 | output name (case local of {True -> "variabledef"; False -> "variable"}) 231 | ws 232 | 233 | variableDef = do newVariable 234 | optional assignment 235 | where assignment = do punctuation '=' 236 | expression 237 | 238 | functionDef = do optional newVariable 239 | pushContext 240 | punctuation '(' 241 | commaSep newVariable 242 | punctuation ')' 243 | statement 244 | popContext 245 | 246 | propertyName = do name <- word 247 | output name "property" 248 | ws 249 | 250 | objectLiteral = do punctuation '{' 251 | commaSep property 252 | punctuation '}' 253 | where property = do (stringValue <|> numberValue <|> hexNumberValue <|> propertyName) 254 | punctuation ':' 255 | expression 256 | 257 | arrayLiteral = do punctuation '[' 258 | commaSep expression 259 | punctuation ']' 260 | 261 | expression = do (parenthesed <|> regexp <|> stringValue <|> hexNumberValue <|> numberValue <|> 262 | objectLiteral <|> arrayLiteral <|> wordExpression) 263 | maybeOperator 264 | <|> do operator 265 | expression 266 | where parenthesed = do punctuation '(' 267 | expression 268 | punctuation ')' 269 | 270 | 271 | postFixOperator = do op <- (string "++") <|> (string "--") 272 | output op "operator" 273 | ws 274 | 275 | maybeOperator = funCall <|> propValue <|> propSubscript <|> (try postFixOperator) <|> choiceOperator 276 | <|> operator' <|> return () 277 | where operator' = do operator 278 | expression 279 | choiceOperator = do punctuation '?' 280 | expression 281 | punctuation ':' 282 | expression 283 | funCall = do punctuation '(' 284 | commaSep expression 285 | punctuation ')' 286 | maybeOperator 287 | propValue = do punctuation '.' 288 | propertyName 289 | maybeOperator 290 | propSubscript = do punctuation '[' 291 | expression 292 | punctuation ']' 293 | maybeOperator 294 | 295 | -- Html generation 296 | 297 | significantStyles :: Set String 298 | significantStyles = Data.Set.fromList ["keyword", "atom", "variable", "string", "variabledef", 299 | "localvariable", "property", "comment"]; 300 | 301 | simplifyOutput :: [(String, String)] -> [(String, String)] 302 | simplifyOutput output = simplify (map removeStyle output) 303 | where removeStyle (value, style) | Data.Set.member style significantStyles = (value, style) 304 | | otherwise = (value, "") 305 | simplify [] = [] 306 | simplify [o] = [o] 307 | simplify ((v1, s1):rest) = let a@((v2, s2):rest') = simplify rest 308 | in if (s2 == s1) 309 | then ((v1 ++ v2, s1):rest') 310 | else ((v1, s1):a) 311 | 312 | toHtml :: (String, String) -> HTML 313 | toHtml (value, "") = Tx value 314 | toHtml (value, style) = Tg "span" [("class", style)] [Tx value] 315 | 316 | highlightOutput :: [(String, String)] -> [HTML] 317 | highlightOutput = (map toHtml) . simplifyOutput . reverse 318 | 319 | highlightStatements, highlightExpression :: String -> [HTML] 320 | highlightStatements = highlight (do {ws; many statement; eof; getOutput}) 321 | highlightExpression = highlight (do {ws; expression; eof; getOutput}) 322 | 323 | highlight :: CharParser PState [(String, String)] -> String -> [HTML] 324 | highlight parser code = case runParser parser (PS [] []) "" code 325 | of Right ps -> highlightOutput ps 326 | Left err -> error ("In code:\n\n" ++ code ++ "\n\nParse error at " ++ (show err)) 327 | -------------------------------------------------------------------------------- /renderer_tex/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Data.Time.Clock 4 | import Data.Time.Calendar 5 | import System.Environment(getArgs) 6 | import Data.Char 7 | import Data.Maybe 8 | import Data.List 9 | import Data.Ord 10 | import Control.Monad 11 | import Control.Monad.State 12 | import System.IO.UTF8 13 | 14 | main = do args <- getArgs 15 | renderFile (args !! 0) 16 | 17 | -- Utils 18 | 19 | forEach [] f = return () 20 | forEach (x:xs) f = (f x) >> (forEach xs f) 21 | 22 | trim pred [] = [] 23 | trim pred (c:cs) | pred c = trim pred cs 24 | | otherwise = trimBack (c:cs) 25 | where trimBack [] = [] 26 | trimBack (c:cs) | pred c = case trimBack cs 27 | of [] -> [] 28 | remainder -> c : remainder 29 | | otherwise = c : trimBack cs 30 | 31 | trimWS = trim (oneOf "\n ") 32 | 33 | paragraphs [] = [] 34 | paragraphs ('\n':'\n':cs) = [] : (paragraphs $ dropWhile (=='\n') cs) 35 | paragraphs (c:cs) = case paragraphs cs 36 | of [] -> [[c]] 37 | (p:ps) -> (c:p):ps 38 | 39 | alternate [] _ = [] 40 | alternate [e] _ = e 41 | alternate (e:es) x = e ++ x ++ (alternate es x) 42 | 43 | findIf _ [] = Nothing 44 | findIf p (x:xs) | p x = Just x 45 | | otherwise = findIf p xs 46 | 47 | member x [] = False 48 | member x (y:ys) = if (x == y) then True else (member x ys) 49 | 50 | oneOf = flip elem 51 | 52 | capitalize (c:cs) = (toUpper c):cs 53 | maybeCapitalise True cs = capitalize cs 54 | maybeCapitalise False cs = cs 55 | 56 | infixr 4 $> 57 | infixr 4 $$> 58 | f $> (fst, snd) = (f fst, snd) 59 | c $$> (fst, snd) = (c:fst, snd) 60 | 61 | monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", 62 | "October", "November", "December"] 63 | 64 | formatDate (year, month, day) = (monthNames !! (month - 1)) ++ " " ++ (show day) ++ " " ++ (show year) 65 | 66 | -- Data Structures 67 | 68 | data Chapter = Chapter {ctype::ChapterType, num::Int, title::String, tag::String, content::[Block], footnotes::[Paragraph]} 69 | deriving Show 70 | 71 | data ChapterType = Normal | Appendix 72 | deriving (Show, Eq) 73 | 74 | data Block = Block [Paragraph] | Exercise String Int [Paragraph] [Paragraph] 75 | deriving Show 76 | 77 | data CodeType = Regular | Invalid | Expression 78 | deriving Show 79 | 80 | data Paragraph = Paragraph [Fragment] | Code CodeType String | Pre String | Quote [Fragment] | 81 | List ListType [[Fragment]] | Footnote Int [Fragment] | 82 | Illustration String | Picture String 83 | deriving Show 84 | 85 | data ListType = Bullet | Numbered 86 | deriving (Show, Eq) 87 | 88 | data Fragment = Plain String | CodeFrag String | Emp String | Keyword String Bool Bool Int | ExRef Bool String | 89 | ChapRef Bool String | FootRef Int | Break | Exponent String | Link String String 90 | deriving Show 91 | 92 | -- Parser 93 | 94 | parse text = chapters 1 1 (paragraphs text) 95 | where chapters _ _ [] = [] 96 | chapters nc na ps@(('=':_):_) = let (chap, rest) = chapter Normal nc ps in chap : chapters (nc + 1) na rest 97 | chapters nc na ps@(('+':_):_) = let (chap, rest) = chapter Appendix na ps in chap : chapters nc (na + 1) rest 98 | 99 | chapter ctype n (header:ps) = (Chapter ctype n title tag parts [], rest) 100 | where (title', tag') = span (/='/') (trim (oneOf "\n=+") header) 101 | title = trim (==' ') title' 102 | tag = trim (oneOf "/ ") tag' 103 | (parts, rest) = blocks ps 104 | 105 | isFootnote (Footnote _ _) = True 106 | isFootnote _ = False 107 | 108 | withoutFootnotes = map removeNotes 109 | where remove = filter (not . isFootnote) 110 | removeNotes (Block ps) = Block $ remove ps 111 | removeNotes (Exercise name id eps sps) = Exercise name id (remove eps) (remove sps) 112 | 113 | gatherFootnotes bs = concatMap findFootnotes bs 114 | where getFootnotes = filter isFootnote 115 | findFootnotes (Block ps) = getFootnotes ps 116 | findFootnotes (Exercise _ _ eps sps) = getFootnotes eps ++ getFootnotes sps 117 | 118 | gatherKeywords ch = concatMap keywords' (content ch) 119 | where keywords' (Block ps) = concatMap keywords'' ps 120 | keywords' (Exercise _ _ qs as) = concatMap keywords'' (qs ++ as) 121 | keywords'' (Paragraph fs) = keywords fs 122 | keywords'' (Quote fs) = keywords fs 123 | keywords'' (List _ fss) = concatMap keywords fss 124 | keywords'' (Footnote _ fs) = keywords fs 125 | keywords'' _ = [] 126 | keywords fs = filter isKeyword fs 127 | isKeyword (Keyword _ _ _ _) = True 128 | isKeyword _ = False 129 | 130 | blocks = block (Block []) 131 | where block b [] = ([b], []) 132 | block b ps@(('=':'=':_):_) = ([b], ps) 133 | block b ps@(('+':'+':_):_) = ([b], ps) 134 | block b ("---":ps) = b $$> block (Block []) ps 135 | block b (('*':'*':'*':tag):ps) = b $$> block (Exercise (trim (==' ') tag) 0 [] []) ps 136 | block (Block xs) (p:ps) = block (Block $ xs ++ [makeParagraph p]) ps 137 | block (Exercise tag id qs []) ("///":p:ps) = block (Exercise tag id qs [makeParagraph p]) ps 138 | block (Exercise tag id qs []) (p:ps) = block (Exercise tag id (qs ++ [makeParagraph p]) []) ps 139 | block (Exercise tag id qs as) (p:ps) = block (Exercise tag id qs (as ++ [makeParagraph p])) ps 140 | 141 | makeParagraph ('>':'>':' ':cs) = Code Expression (Main.stripPrefix 2 cs) 142 | makeParagraph ('>':' ':cs) = Code Regular (Main.stripPrefix 2 cs) 143 | makeParagraph ('!':'>':' ':cs) = Code Invalid (Main.stripPrefix 3 cs) 144 | makeParagraph (']':' ':cs) = Pre (Main.stripPrefix 2 cs) 145 | makeParagraph ('|':' ':cs) = Quote (splitFrag (Main.stripPrefix 2 cs)) 146 | makeParagraph ('[':'[':'[':cs) = Picture (takeWhile (/=']') cs) 147 | makeParagraph ('[':'[':cs) = Illustration (takeWhile (/=']') cs) 148 | makeParagraph cs@(' ':'*':' ':_) = List Bullet (map splitFrag (linesFrom "* " cs)) 149 | makeParagraph cs@(' ':'1':'.':' ':_) = List Numbered (map splitFrag (linesFrom ". " cs)) 150 | makeParagraph ('#':'#':' ':cs) = Footnote 0 (splitFrag cs) 151 | makeParagraph cs = Paragraph (splitFrag cs) 152 | 153 | stripPrefix n [] = [] 154 | stripPrefix n ('\n':cs) = '\n' : (Main.stripPrefix n $ drop' n cs) 155 | where drop' 0 xs = xs 156 | drop' _ [] = [] 157 | drop' _ xs@('\n':_) = xs 158 | drop' n (x:xs) = drop' (n-1) xs 159 | stripPrefix n (c:cs) = c : (Main.stripPrefix n cs) 160 | 161 | linesFrom p cs = map (removeUpto p) $ lines cs 162 | where removeUpto [] cs = cs 163 | removeUpto _ [] = [] 164 | removeUpto (p:ps) (c:cs) | p == c = removeUpto ps cs 165 | | otherwise = removeUpto (p:ps) cs 166 | 167 | -- Fragment parsers 168 | 169 | codeFrag xs = case xs 170 | of ('|':'|':'|':xs') -> CodeFrag $> codeFrag' xs 171 | ('|':'|':xs) -> (CodeFrag "|", xs) 172 | otherwise -> CodeFrag $> codeFrag' xs 173 | where codeFrag' [] = ([], []) 174 | codeFrag' ('|':'|':xs) = '|' $$> '|' $$> codeFrag' xs 175 | codeFrag' ('|':xs) = ([], xs) 176 | codeFrag' (x:xs) = x $$> codeFrag' xs 177 | 178 | empFrag xs = Emp $> empFrag' xs 179 | where empFrag' [] = ([], []) 180 | empFrag' (x:'*':xs) | isAlphaNum(x) = ([x], xs) 181 | | otherwise = x $$> empFrag' ('*':xs) 182 | empFrag' (x:xs) = x $$> empFrag' xs 183 | 184 | refFrag (x:xs) = ((constructor x) tag, rest) 185 | where (tag, rest) = span isAlphaNum xs 186 | constructor 'c' = ChapRef False 187 | constructor 'C' = ChapRef True 188 | constructor 'e' = ExRef False 189 | constructor 'E' = ExRef True 190 | 191 | keywordFrag xs visible = makeKey $> keyFrag' xs 192 | where keyFrag' [] = ([], []) 193 | keyFrag' ('_':xs) = ([], xs) 194 | keyFrag' (x:xs) = x $$> keyFrag' xs 195 | isCode ('|':xs) = (True, take (length xs - 1) xs) 196 | isCode xs = (False, xs) 197 | makeKey x = let (code, keyword) = isCode x 198 | in Keyword keyword visible code 0 199 | 200 | expFrag xs = Exponent $> expFrag' xs 201 | where expFrag' [] = ([], []) 202 | expFrag' (x:xs) | isNumber x = x $$> expFrag' xs 203 | | otherwise = ([], x:xs) 204 | 205 | linkFrag xs = (Link (trimWS href) (trimWS title), rest') 206 | where getTitle [] = ([], []) 207 | getTitle ('|':xs) = ([], xs) 208 | getTitle (x:xs) = x $$> getTitle xs 209 | getHref [] = ([], []) 210 | getHref (']':xs) = ([], xs) 211 | getHref (x:xs) = x $$> getHref xs 212 | (title, rest) = getTitle xs 213 | (href, rest') = getHref rest 214 | 215 | splitFrag xs = splitFrag' xs True 216 | where splitFrag' ('^':xs) _ = continue (expFrag xs) 217 | splitFrag' ('\\':'\\':xs) _ = continue (refFrag xs) 218 | splitFrag' ('[':xs) _ = continue (linkFrag xs) 219 | splitFrag' ('#':'#':xs) _ = continue (FootRef 0, xs) 220 | splitFrag' ('\n':'\n':xs) _ = continue (Break, xs) 221 | splitFrag' ('-':'-':xs) _ = add' "---" (splitFrag' xs True) 222 | splitFrag' ('@':'_':xs) _ = continue (keywordFrag xs False) 223 | splitFrag' ('_':x:xs) True | isAlphaNum x || x == '|' = continue (keywordFrag (x:xs) True) 224 | | otherwise = add '_' (splitFrag' (x:xs) True) 225 | splitFrag' ('*':x:xs) True | isAlphaNum x = continue (empFrag (x:xs)) 226 | | otherwise = add '*' (splitFrag' (x:xs) True) 227 | splitFrag' ('|':xs) True = continue (codeFrag xs) 228 | splitFrag' (x:xs) _ = add x (splitFrag' xs (not (isAlphaNum x))) 229 | splitFrag' [] _ = [] 230 | continue (frag, []) = [frag] 231 | continue (frag, xs) = frag : splitFrag' xs True 232 | add c (Plain xs: rest) = (Plain (c:xs)) : rest 233 | add c fs = (Plain [c]):fs 234 | add' s (Plain xs: rest) = (Plain (s ++ xs)) : rest 235 | add' s fs = (Plain s):fs 236 | 237 | -- Id assignment 238 | 239 | data Nums = Nums {exNum::Int, footNum::Int, refNum::Int, keyNum::Int} 240 | 241 | setExNum ns num = ns {exNum = num} 242 | setFootNum ns num = ns {footNum = num} 243 | setRefNum ns num = ns {refNum = num} 244 | setKeyNum ns num = ns {keyNum = num} 245 | 246 | nextNum getter setter = do current <- get 247 | let num = getter current 248 | put (setter current (num + 1)) 249 | return num 250 | 251 | nextExercise, nextFootnote, nextFootref :: State Nums Int 252 | nextExercise = nextNum exNum setExNum 253 | nextFootnote = nextNum footNum setFootNum 254 | nextFootref = nextNum refNum setRefNum 255 | nextKeyword = nextNum keyNum setKeyNum 256 | 257 | numberChapter ch = ch {content = numberedContent, footnotes = numberedFootnotes} 258 | where (numberedContent, numbers) = runState (numberBlocks (content ch)) (Nums 1 1 1 1) 259 | numberedFootnotes = fst (runState (numberParagraphs (footnotes ch)) numbers) 260 | 261 | numberBlocks bs = mapM number bs 262 | where number (Block ps) = do nps <- numberParagraphs ps 263 | return $ Block nps 264 | number (Exercise tag _ eps sps) = do exNum <- nextExercise 265 | neps <- numberParagraphs eps 266 | nsps <- numberParagraphs sps 267 | return $ Exercise tag exNum neps nsps 268 | 269 | numberParagraphs ps = mapM number ps 270 | where number (Paragraph fs) = do nfs <- numberFragments fs 271 | return $ Paragraph nfs 272 | number (Quote fs) = do nfs <- numberFragments fs 273 | return $ Quote nfs 274 | number (List t fss) = do nfss <- mapM numberFragments fss 275 | return (List t nfss) 276 | number (Footnote _ fs) = do nfs <- numberFragments fs 277 | id <- nextFootnote 278 | return $ Footnote id nfs 279 | number p = return p 280 | 281 | numberFragments fs = mapM number fs 282 | where number (FootRef _) = do id <- nextFootref 283 | return (FootRef id) 284 | number (Keyword s v c _) = do id <- nextKeyword 285 | return (Keyword s v c id) 286 | number f = return f 287 | 288 | -- Rendering 289 | 290 | typeName c = case (ctype c) 291 | of Normal -> "chapter" 292 | Appendix -> "appendix" 293 | fileName c = padz (show (num c)) ++ "_" ++ (tag c) ++ ".tex" 294 | where padz [nm] | (ctype c) == Normal = "0" ++ [nm] 295 | | otherwise = "a" ++ [nm] 296 | padz x = x 297 | 298 | outputPath = "tex/" 299 | imgPath = "img/" 300 | 301 | renderFile file = do input <- System.IO.UTF8.readFile file 302 | let chapters = map numberChapter (parse input) 303 | renderChapters chapters 304 | 305 | renderChapters cs = forEach cs (\c -> System.IO.UTF8.writeFile (outputPath ++ (fileName c)) (renderChapter c)) 306 | 307 | renderChapter c = header ++ (alternate blocks sep) 308 | where blocks = map (renderBlock c) (content c) 309 | sep = "\\sep\n\n" 310 | tp = case (ctype c) of 311 | Normal -> "chapter" 312 | Appendix -> "appendix" 313 | header = "\\" ++ tp ++ "{" ++ (title c) ++ "}\\label{chap:" ++ (tag c) ++ "}\n\n" 314 | 315 | latexWrap' content tp arg = "\\begin" ++ (arg' arg) ++ "{" ++ tp ++ "}\n" ++ content ++ "\\end{" ++ tp ++ "}\n" 316 | where arg' Nothing = "" 317 | arg' (Just x) = "[" ++ x ++ "]" 318 | latexWrap content tp = latexWrap' content tp Nothing 319 | 320 | renderBlock c (Block ps) = concat (map renderParagraph ps) 321 | renderBlock c (Exercise tag n eps sps) = (latexWrap (lbl ++ (concatMap renderParagraph eps)) "exercise") ++ 322 | (latexWrap (concatMap renderParagraph sps) "solution") 323 | where lbl = (case tag 324 | of "" -> "" 325 | x -> "\\label{ex:" ++ tag ++ "}") 326 | 327 | needEsc c = member c ['$', '{', '}', '\\', '^', '_', '&', '#', '%'] 328 | latexEsc [] = [] 329 | latexEsc ('^':cs) = "\\verb1^1" ++ (latexEsc cs) 330 | latexEsc ('\\':cs) = "\\bs " ++ (latexEsc cs) 331 | latexEsc (c:cs) = if (needEsc c) then ('\\':c:(latexEsc cs)) else (c:(latexEsc cs)) 332 | 333 | nonewlines [] = [] 334 | nonewlines ('\n':cs) = ' ':(nonewlines cs) 335 | nonewlines (c:cs) = c:(nonewlines cs) 336 | 337 | renderParagraph (Paragraph fs) = (renderFragments fs) 338 | renderParagraph (Quote fs) = latexWrap (renderFragments fs) "quote" 339 | renderParagraph (Code _ content) = "\\begin{Code}\n" ++ content ++ "\n\\end{Code}\n\n" 340 | renderParagraph (Pre content) = "\\begin{verbatim}\n" ++ content ++ "\n\\end{verbatim}\n\n" 341 | renderParagraph (List t is) = latexWrap items (blockType t) 342 | where blockType Bullet = "itemize" 343 | blockType Numbered = "enumerate" 344 | items = concatMap ("\\item " ++) (map renderFragments is) 345 | renderParagraph (Footnote n fs) = "TODO footnote: " ++ (renderFragments fs) 346 | renderParagraph (Illustration src) = "\\includegraphics{" ++ imgPath ++ src ++ "}" 347 | renderParagraph (Picture src) = "\\includegraphics{" ++ imgPath ++ src ++ "}" 348 | 349 | renderFragment (Plain s) = s 350 | renderFragment (CodeFrag s) = "\\verb`" ++ (nonewlines s) ++ "`" 351 | renderFragment (Emp s) = "{\\em " ++ s ++ "}" 352 | renderFragment (Keyword s v c n) = index ++ content 353 | where index = "\\index{" ++ (latexEsc s) ++ "}" 354 | content = case (v, c) 355 | of (False, _) -> "" 356 | (True, False) -> s 357 | (True, True) -> "\\verb`" ++ (nonewlines s) ++ "`" 358 | renderFragment (ExRef capit tag) = "\\exref" ++ cap ++ "{" ++ tag ++ "}" 359 | where cap | capit = "[1]" 360 | | True = "" 361 | renderFragment (ChapRef capit tag) = "\\chapref" ++ cap ++ "{" ++ tag ++ "}" 362 | where cap | capit = "[1]" 363 | | True = "" 364 | renderFragment (FootRef n) = "(TODO footnote)" 365 | renderFragment Break = "\n\n" 366 | renderFragment (Exponent n) = "$^{" ++ n ++ "}$" 367 | renderFragment (Link href title) = "\\href{" ++ href ++ "}{" ++ title ++ "}" 368 | 369 | renderFragments fs = (concatMap renderFragment fs) ++ "\n\n" 370 | -------------------------------------------------------------------------------- /renderer/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Data.Time.Clock 4 | import Data.Time.Calendar 5 | import System.Environment(getArgs) 6 | import Data.Char 7 | import Data.Maybe 8 | import Data.List hiding (stripPrefix) 9 | import Data.Ord 10 | import Control.Monad 11 | import Control.Monad.State 12 | import System.IO.UTF8 13 | import Data.Hashable 14 | import Numeric 15 | 16 | import Html 17 | import Highlight 18 | 19 | main = do args <- getArgs 20 | renderFile (args !! 0) 21 | 22 | -- Utils 23 | 24 | forEach [] f = return () 25 | forEach (x:xs) f = (f x) >> (forEach xs f) 26 | 27 | trim pred [] = [] 28 | trim pred (c:cs) | pred c = trim pred cs 29 | | otherwise = trimBack (c:cs) 30 | where trimBack [] = [] 31 | trimBack (c:cs) | pred c = case trimBack cs 32 | of [] -> [] 33 | remainder -> c : remainder 34 | | otherwise = c : trimBack cs 35 | 36 | trimWS = trim (oneOf "\n ") 37 | 38 | paragraphs [] = [] 39 | paragraphs ('\n':'\n':cs) = [] : (paragraphs $ dropWhile (=='\n') cs) 40 | paragraphs (c:cs) = case paragraphs cs 41 | of [] -> [[c]] 42 | (p:ps) -> (c:p):ps 43 | 44 | alternate [] _ = [] 45 | alternate [e] _ = [e] 46 | alternate (e:es) x = e : x : alternate es x 47 | 48 | findIf _ [] = Nothing 49 | findIf p (x:xs) | p x = Just x 50 | | otherwise = findIf p xs 51 | 52 | oneOf = flip elem 53 | 54 | capitalize (c:cs) = (toUpper c):cs 55 | maybeCapitalise True cs = capitalize cs 56 | maybeCapitalise False cs = cs 57 | 58 | infixr 4 $> 59 | infixr 4 $$> 60 | f $> (fst, snd) = (f fst, snd) 61 | c $$> (fst, snd) = (c:fst, snd) 62 | 63 | monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", 64 | "October", "November", "December"] 65 | 66 | formatDate (year, month, day) = (monthNames !! (month - 1)) ++ " " ++ (show day) ++ " " ++ (show year) 67 | 68 | -- Data Structures 69 | 70 | data Chapter = Chapter {ctype::ChapterType, num::Int, title::String, tag::String, content::[Block], footnotes::[Paragraph]} 71 | deriving Show 72 | 73 | data ChapterType = Normal | Appendix 74 | deriving (Show, Eq) 75 | 76 | data Block = Block [Paragraph] | Exercise String Int [Paragraph] [Paragraph] 77 | deriving Show 78 | 79 | data CodeType = Regular | Invalid | Expression 80 | deriving Show 81 | 82 | data Paragraph = Paragraph [Fragment] | Code CodeType String | Pre String | Quote [Fragment] | 83 | List ListType [[Fragment]] | Footnote Int [Fragment] | 84 | Illustration String | Picture String 85 | deriving Show 86 | 87 | data ListType = Bullet | Numbered 88 | deriving (Show, Eq) 89 | 90 | data Fragment = Plain String | CodeFrag String | Emp String | Keyword String Bool Bool Int | ExRef Bool String | 91 | ChapRef Bool String | FootRef Int | Break | Exponent String | Link String String 92 | deriving Show 93 | 94 | -- Parser 95 | 96 | parse text = chapters 1 1 (paragraphs text) 97 | where chapters _ _ [] = [] 98 | chapters nc na ps@(('=':_):_) = let (chap, rest) = chapter Normal nc ps in chap : chapters (nc + 1) na rest 99 | chapters nc na ps@(('+':_):_) = let (chap, rest) = chapter Appendix na ps in chap : chapters nc (na + 1) rest 100 | 101 | chapter ctype n (header:ps) = (Chapter ctype n title tag (withoutFootnotes parts) (gatherFootnotes parts), rest) 102 | where (title', tag') = span (/='/') (trim (oneOf "\n=+") header) 103 | title = trim (==' ') title' 104 | tag = trim (oneOf "/ ") tag' 105 | (parts, rest) = blocks ps 106 | 107 | isFootnote (Footnote _ _) = True 108 | isFootnote _ = False 109 | 110 | withoutFootnotes = map removeNotes 111 | where remove = filter (not . isFootnote) 112 | removeNotes (Block ps) = Block $ remove ps 113 | removeNotes (Exercise name id eps sps) = Exercise name id (remove eps) (remove sps) 114 | 115 | gatherFootnotes bs = concatMap findFootnotes bs 116 | where getFootnotes = filter isFootnote 117 | findFootnotes (Block ps) = getFootnotes ps 118 | findFootnotes (Exercise _ _ eps sps) = getFootnotes eps ++ getFootnotes sps 119 | 120 | gatherKeywords ch = concatMap keywords' (content ch) 121 | where keywords' (Block ps) = concatMap keywords'' ps 122 | keywords' (Exercise _ _ qs as) = concatMap keywords'' (qs ++ as) 123 | keywords'' (Paragraph fs) = keywords fs 124 | keywords'' (Quote fs) = keywords fs 125 | keywords'' (List _ fss) = concatMap keywords fss 126 | keywords'' (Footnote _ fs) = keywords fs 127 | keywords'' _ = [] 128 | keywords fs = filter isKeyword fs 129 | isKeyword (Keyword _ _ _ _) = True 130 | isKeyword _ = False 131 | 132 | blocks = block (Block []) 133 | where block b [] = ([b], []) 134 | block b ps@(('=':'=':_):_) = ([b], ps) 135 | block b ps@(('+':'+':_):_) = ([b], ps) 136 | block b ("---":ps) = b $$> block (Block []) ps 137 | block b (('*':'*':'*':tag):ps) = b $$> block (Exercise (trim (==' ') tag) 0 [] []) ps 138 | block (Block xs) (p:ps) = block (Block $ xs ++ [makeParagraph p]) ps 139 | block (Exercise tag id qs []) ("///":p:ps) = block (Exercise tag id qs [makeParagraph p]) ps 140 | block (Exercise tag id qs []) (p:ps) = block (Exercise tag id (qs ++ [makeParagraph p]) []) ps 141 | block (Exercise tag id qs as) (p:ps) = block (Exercise tag id qs (as ++ [makeParagraph p])) ps 142 | 143 | makeParagraph ('>':'>':' ':cs) = Code Expression (stripPrefix 2 cs) 144 | makeParagraph ('>':' ':cs) = Code Regular (stripPrefix 2 cs) 145 | makeParagraph ('!':'>':' ':cs) = Code Invalid (stripPrefix 3 cs) 146 | makeParagraph (']':' ':cs) = Pre (stripPrefix 2 cs) 147 | makeParagraph ('|':' ':cs) = Quote (splitFrag (stripPrefix 2 cs)) 148 | makeParagraph ('[':'[':'[':cs) = Picture (takeWhile (/=']') cs) 149 | makeParagraph ('[':'[':cs) = Illustration (takeWhile (/=']') cs) 150 | makeParagraph cs@(' ':'*':' ':_) = List Bullet (map splitFrag (linesFrom "* " cs)) 151 | makeParagraph cs@(' ':'1':'.':' ':_) = List Numbered (map splitFrag (linesFrom ". " cs)) 152 | makeParagraph ('#':'#':' ':cs) = Footnote 0 (splitFrag cs) 153 | makeParagraph cs = Paragraph (splitFrag cs) 154 | 155 | stripPrefix n [] = [] 156 | stripPrefix n ('\n':cs) = '\n' : (stripPrefix n $ drop' n cs) 157 | where drop' 0 xs = xs 158 | drop' _ [] = [] 159 | drop' _ xs@('\n':_) = xs 160 | drop' n (x:xs) = drop' (n-1) xs 161 | stripPrefix n (c:cs) = c : (stripPrefix n cs) 162 | 163 | linesFrom p cs = map (removeUpto p) $ lines cs 164 | where removeUpto [] cs = cs 165 | removeUpto _ [] = [] 166 | removeUpto (p:ps) (c:cs) | p == c = removeUpto ps cs 167 | | otherwise = removeUpto (p:ps) cs 168 | 169 | -- Fragment parsers 170 | 171 | codeFrag xs = case xs 172 | of ('|':'|':'|':xs') -> CodeFrag $> codeFrag' xs 173 | ('|':'|':xs) -> (CodeFrag "|", xs) 174 | otherwise -> CodeFrag $> codeFrag' xs 175 | where codeFrag' [] = ([], []) 176 | codeFrag' ('|':'|':xs) = '|' $$> '|' $$> codeFrag' xs 177 | codeFrag' ('|':xs) = ([], xs) 178 | codeFrag' (x:xs) = x $$> codeFrag' xs 179 | 180 | empFrag xs = Emp $> empFrag' xs 181 | where empFrag' [] = ([], []) 182 | empFrag' (x:'*':xs) | isAlphaNum(x) = ([x], xs) 183 | | otherwise = x $$> empFrag' ('*':xs) 184 | empFrag' (x:xs) = x $$> empFrag' xs 185 | 186 | refFrag (x:xs) = ((constructor x) tag, rest) 187 | where (tag, rest) = span isAlphaNum xs 188 | constructor 'c' = ChapRef False 189 | constructor 'C' = ChapRef True 190 | constructor 'e' = ExRef False 191 | constructor 'E' = ExRef True 192 | 193 | keywordFrag xs visible = makeKey $> keyFrag' xs 194 | where keyFrag' [] = ([], []) 195 | keyFrag' ('_':xs) = ([], xs) 196 | keyFrag' (x:xs) = x $$> keyFrag' xs 197 | isCode ('|':xs) = (True, take (length xs - 1) xs) 198 | isCode xs = (False, xs) 199 | makeKey x = let (code, keyword) = isCode x 200 | in Keyword keyword visible code 0 201 | 202 | expFrag xs = Exponent $> expFrag' xs 203 | where expFrag' [] = ([], []) 204 | expFrag' (x:xs) | isNumber x = x $$> expFrag' xs 205 | | otherwise = ([], x:xs) 206 | 207 | linkFrag xs = (Link (trimWS href) (trimWS title), rest') 208 | where getTitle [] = ([], []) 209 | getTitle ('|':xs) = ([], xs) 210 | getTitle (x:xs) = x $$> getTitle xs 211 | getHref [] = ([], []) 212 | getHref (']':xs) = ([], xs) 213 | getHref (x:xs) = x $$> getHref xs 214 | (title, rest) = getTitle xs 215 | (href, rest') = getHref rest 216 | 217 | splitFrag xs = splitFrag' xs True 218 | where splitFrag' ('^':xs) _ = continue (expFrag xs) 219 | splitFrag' ('\\':'\\':xs) _ = continue (refFrag xs) 220 | splitFrag' ('[':xs) _ = continue (linkFrag xs) 221 | splitFrag' ('#':'#':xs) _ = continue (FootRef 0, xs) 222 | splitFrag' ('\n':'\n':xs) _ = continue (Break, xs) 223 | splitFrag' ('-':'-':xs) _ = add '\x2015' (splitFrag' xs True) 224 | splitFrag' ('@':'_':xs) _ = continue (keywordFrag xs False) 225 | splitFrag' ('_':x:xs) True | isAlphaNum x || x == '|' = continue (keywordFrag (x:xs) True) 226 | | otherwise = add '_' (splitFrag' (x:xs) True) 227 | splitFrag' ('*':x:xs) True | isAlphaNum x = continue (empFrag (x:xs)) 228 | | otherwise = add '*' (splitFrag' (x:xs) True) 229 | splitFrag' ('|':xs) True = continue (codeFrag xs) 230 | splitFrag' (x:xs) _ = add x (splitFrag' xs (not (isAlphaNum x))) 231 | splitFrag' [] _ = [] 232 | continue (frag, []) = [frag] 233 | continue (frag, xs) = frag : splitFrag' xs True 234 | add c (Plain xs: rest) = (Plain (c:xs)) : rest 235 | add c fs = (Plain [c]):fs 236 | 237 | -- Id assignment 238 | 239 | data Nums = Nums {exNum::Int, footNum::Int, refNum::Int, keyNum::Int} 240 | 241 | setExNum ns num = ns {exNum = num} 242 | setFootNum ns num = ns {footNum = num} 243 | setRefNum ns num = ns {refNum = num} 244 | setKeyNum ns num = ns {keyNum = num} 245 | 246 | nextNum getter setter = do current <- get 247 | let num = getter current 248 | put (setter current (num + 1)) 249 | return num 250 | 251 | nextExercise, nextFootnote, nextFootref :: State Nums Int 252 | nextExercise = nextNum exNum setExNum 253 | nextFootnote = nextNum footNum setFootNum 254 | nextFootref = nextNum refNum setRefNum 255 | nextKeyword = nextNum keyNum setKeyNum 256 | 257 | numberChapter ch = ch {content = numberedContent, footnotes = numberedFootnotes} 258 | where (numberedContent, numbers) = runState (numberBlocks (content ch)) (Nums 1 1 1 1) 259 | numberedFootnotes = fst (runState (numberParagraphs (footnotes ch)) numbers) 260 | 261 | numberBlocks bs = mapM number bs 262 | where number (Block ps) = do nps <- numberParagraphs ps 263 | return $ Block nps 264 | number (Exercise tag _ eps sps) = do exNum <- nextExercise 265 | neps <- numberParagraphs eps 266 | nsps <- numberParagraphs sps 267 | return $ Exercise tag exNum neps nsps 268 | 269 | numberParagraphs ps = mapM number ps 270 | where number (Paragraph fs) = do nfs <- numberFragments fs 271 | return $ Paragraph nfs 272 | number (Quote fs) = do nfs <- numberFragments fs 273 | return $ Quote nfs 274 | number (List t fss) = do nfss <- mapM numberFragments fss 275 | return (List t nfss) 276 | number (Footnote _ fs) = do nfs <- numberFragments fs 277 | id <- nextFootnote 278 | return $ Footnote id nfs 279 | number p = return p 280 | 281 | numberFragments fs = mapM number fs 282 | where number (FootRef _) = do id <- nextFootref 283 | return (FootRef id) 284 | number (Keyword s v c _) = do id <- nextKeyword 285 | return (Keyword s v c id) 286 | number f = return f 287 | 288 | -- Rendering 289 | 290 | findChapter chapters t = info 291 | where info = case (findIf ((== t) . tag) chapters) 292 | of Just c -> (fileName c, (typeName c) ++ " " ++ (show (num c))) 293 | 294 | findExercise chapters tag = info 295 | where hasTag (Exercise t _ _ _) = t == tag 296 | hasTag _ = False 297 | findExercise c = case (findIf hasTag (content c)) 298 | of Nothing -> Nothing 299 | Just e -> Just (c, e) 300 | info = case (catMaybes (map findExercise chapters)) 301 | of ((c, (Exercise _ n _ _)):_) -> ((fileName c) ++ "#exercise" ++ (show n), "exercise " ++ (show (num c)) ++ "." ++ (show n)) 302 | 303 | typeName c = case (ctype c) 304 | of Normal -> "chapter" 305 | Appendix -> "appendix" 306 | fileName c = (typeName c) ++ (show (num c)) ++ ".html" 307 | 308 | outputPath = "web/" 309 | imgPath = "img/" 310 | 311 | renderFile file = do input <- System.IO.UTF8.readFile file 312 | now <- (getCurrentTime >>= return . toGregorian . utctDay) 313 | let chapters = map numberChapter (parse input) 314 | renderChapters chapters now 315 | System.IO.UTF8.writeFile (outputPath ++ "contents.html") (showPage (renderContent chapters now)) 316 | System.IO.UTF8.writeFile (outputPath ++ "terms.html") (showPage (renderIndex chapters now)) 317 | 318 | -- "" 319 | showPage html = (show html) 320 | 321 | styleSheet href = Tg "link" [("rel", "stylesheet"), ("type", "text/css"), ("href", href)] [] 322 | loadScript src = Tg "script" [("type", "text/javascript"), ("src", src)] [Tx " "] 323 | uft8Type = Tg "meta" [("http-equiv", "Content-Type"), ("content", "text/html; charset=utf-8")] [] 324 | 325 | divClass cls children = Tg "div" [("class", cls)] children 326 | spanClass cls children = Tg "span" [("class", cls)] children 327 | listitem content = tg "li" content 328 | 329 | block es = divClass "block" es 330 | anchor name = Tg "a" [("name", name)] [Tx ""] 331 | link href text = Tg "a" [("href", href)] [Tx text] 332 | 333 | --scripts = ["js/Mochi.js", "js/util.js", "js/select.js", "js/tokenize.js", "js/highlight.js", "js/bookutil.js", "js/initenv.js", "js/env.js", "js/book.js"]; 334 | scripts = ["js/mochi.js", "js/codemirror.js", "js/ejs.js"]; 335 | stylesheets = ["css/book.css", "css/highlight.css", "css/console.css", "css/codemirror.css"]; 336 | 337 | addScripts around = loadScript "js/before.js" : around : map loadScript scripts 338 | contentDiv = divClass "content" 339 | 340 | page title body = tg "html" [tg "head" ((map styleSheet stylesheets) ++ 341 | [uft8Type, tg "title" [Tx $ title ++ " -- Eloquent JavaScript"]]), 342 | tg "body" body] 343 | 344 | sortKeywords cs = splitByLetter (collapse (sortBy compareWord (concatMap extractKeywords cs))) 345 | where extractKeywords ch = map (simplifyKeyword ch) (gatherKeywords ch) 346 | simplifyKeyword ch (Keyword w _ c n) = (w, c, ch, n) 347 | sameWord x y = compareWord x y == EQ 348 | compareWord (w1, c1, _, _) (w2, c2, _, _) = case comparing (map (replaceSpecial . toLower)) w1 w2 349 | of EQ -> compare c1 c2 350 | GT -> GT 351 | LT -> LT 352 | where replaceSpecial '\n' = ' ' 353 | replaceSpecial '|' = '@' -- to get around '|' and '{' being sorted after 'z' 354 | replaceSpecial '{' = '@' 355 | replaceSpecial x = x 356 | end (_, _, x, y) = (x, y) 357 | collapse [] = [] 358 | collapse (k@(w, c, ch, n):ws) = let (same, other) = span (sameWord k) ws 359 | in (w, c, (map end (k:same))) : collapse other 360 | fstLetter ((x:xs), _, _) | isAlphaNum x = toLower x 361 | | otherwise = ' ' 362 | splitByLetter [] = [] 363 | splitByLetter (w:ws) = let (this, rest) = span ((==(fstLetter w)) . fstLetter) ws 364 | in (w:this) : splitByLetter rest 365 | 366 | renderIndex cs now = page "Index" [contentDiv ((header : map letterList (sortKeywords cs) ++ [footer now]))] 367 | where renderKeyword (w, c, ls) = tg "li" (text : (concatMap renderLink ls)) 368 | where text = case c 369 | of True -> tg "code" [Tx w] 370 | False -> Tx w 371 | renderLink (ch, n) = [Tx " ", link ((fileName ch) ++ "#key" ++ (show n)) "\x25CB"] 372 | letterList ws = Tg "ul" [("class", "index")] (map renderKeyword ws) 373 | header = tg "h1" [Tx "Index of Terms"] 374 | 375 | renderContent cs now = page "Contents" [contentDiv [header, listHeader, makeList chapters, appendixHeader, makeList appendices, bottomLinks, footer now]] 376 | where header = tg "h1" [Tx "Eloquent JavaScript"] 377 | appendixHeader = tg "h2" [Tx "Appendices"] 378 | listHeader = tg "h2" [Tx "Contents"] 379 | chapters = filter ((==Normal) . ctype) cs 380 | appendices = filter ((==Appendix) . ctype) cs 381 | makeLink c = link (fileName c) (title c) 382 | makeList cs = Tg "ol" [("class", "contents")] (map (listitem . (:[]) . makeLink) cs) 383 | bottomLinks = tg "p" [link "terms.html" "Alphabetic index of terms", tg "br" [], link "index.html" "Cover page"] 384 | 385 | 386 | renderChapters cs now = do let rcs = map (renderChapter' cs) cs 387 | System.IO.UTF8.writeFile (outputPath ++ "print.html") (showPage (renderPrintable rcs now)) 388 | forEach (zip [0..] (zip cs rcs)) (\c -> System.IO.UTF8.writeFile (outputPath ++ (fileName (fst (snd c)))) 389 | (showPage (renderChapter c cs now))) 390 | 391 | renderChapter' cs c = header : (alternate blocks sep) ++ footnotes' 392 | where blocks = map (renderBlock c cs) (content c) 393 | sep = tg "hr" [] 394 | footnotes' | null (footnotes c) = [] 395 | | otherwise = [Tg "ol" [("class", "footnotes")] (map (renderParagraph cs) (footnotes c))] 396 | header = tg "h1" [spanClass "number" [Tx ((capitalize (typeName c)) ++ " " ++ (show (num c)) ++ ": ")], Tx (title c)] 397 | 398 | renderChapter (pos, (c, rc)) cs now = page (title c) (addScripts content') 399 | where navigation = divClass "navigation" [prevChapter, Tx " | ", link "contents.html" "Contents", Tx " | ", 400 | link "index.html" "Cover", Tx " | ", nextChapter] 401 | prevChapter | pos == 0 = Tx "<< Previous chapter" 402 | | otherwise = link (fileName (cs !! (pos - 1))) "<< Previous chapter" 403 | nextChapter | (pos + 1) == (length cs) = Tx "Next chapter >>" 404 | | otherwise = link (fileName (cs !! (pos + 1))) "Next chapter >>" 405 | tagName = Tg "script" [("type", "text/javascript")] [Tx ("var chapterTag = '" ++ (tag c) ++ "';")] 406 | content' = contentDiv (tagName : navigation : rc ++ [navigation, footer now]) 407 | 408 | renderPrintable rcs now = page "Print Version" [divClass "content" (concat rcs), footer now] 409 | 410 | footer now = divClass "footer" [Tx "\x00A9 ", link "mailto:marijnh@gmail.com" "Marijn Haverbeke", 411 | Tx " (", (link "http://creativecommons.org/licenses/by/3.0/" "license"), Tx ")", 412 | Tx (", written March to July 2007, last modified on " ++ (formatDate now) ++ ".")] 413 | 414 | renderBlock c cs (Block ps) = block (map (renderParagraph cs) ps) 415 | renderBlock c cs (Exercise _ n eps sps) = block [anchor ("exercise" ++ (show n)), 416 | divClass "exercisenum" [Tx ("Ex. " ++ (show (num c)) ++ "." ++ (show n))], 417 | divClass "exercise" (map (renderParagraph cs) eps), 418 | divClass "solution" (map (renderParagraph cs) sps)] 419 | 420 | hashFragment (Plain s) = hash s 421 | hashFragment (CodeFrag s) = hash s 422 | hashFragment (Emp s) = hash s 423 | hashFragment (Keyword s _ _ _) = hash s 424 | hashFragment (ExRef _ s ) = hash s 425 | hashFragment (ChapRef _ s) = hash s 426 | hashFragment (FootRef n) = n 427 | hashFragment Break = 100 428 | hashFragment (Exponent s) = hash s 429 | hashFragment (Link s s2) = hash s + hash s2 430 | 431 | hashFragments fs = hash (map hashFragment fs) 432 | 433 | paragraphMarker n = Tg "a" [("class", "paragraph"), ("href", "#" ++ name), ("name", name)] [Tx " ¶ "] 434 | where name = "p" ++ Numeric.showHex (abs n) "" 435 | 436 | renderParagraph cs (Paragraph fs) = tg "p" (paragraphMarker (hashFragments fs) : renderFragments cs fs) 437 | renderParagraph cs (Quote fs) = tg "blockquote" (renderFragments cs fs) 438 | renderParagraph cs (Code codetype content) = Tg "pre" [("class", classFor codetype)] ((highlighterFor codetype) content) 439 | where classFor Invalid = "code invalid" 440 | classFor Regular = "code" 441 | classFor Expression = "code expression" 442 | highlighterFor Expression = highlightExpression 443 | highlighterFor _ = highlightStatements 444 | renderParagraph cs (Pre content) = Tg "pre" [("class", "preformatted")] [Tx content] 445 | renderParagraph cs (List t is) = tg tagname (map (listitem . (renderFragments cs)) is) 446 | where tagname = case t 447 | of Bullet -> "ul" 448 | Numbered -> "ol" 449 | renderParagraph cs (Footnote n fs) = tg "li" $ anchor ("footnote" ++ (show n)) : (renderFragments cs fs) 450 | renderParagraph cs (Illustration src) = divClass "illustration" [(Tg "img" [("src", imgPath ++ src)] [])] 451 | renderParagraph cs (Picture src) = divClass "picture" [(Tg "img" [("src", imgPath ++ src)] [])] 452 | 453 | renderFragment cs (Plain s) = [Tx s] 454 | renderFragment cs (CodeFrag s) = [tg "code" [Tx s]] 455 | renderFragment cs (Emp s) = [tg "em" [Tx s]] 456 | renderFragment cs (Keyword s v c n) = case (v, c) 457 | of (False, _) -> [a] 458 | (True, False) -> [a, Tx s] 459 | (True, True) -> [a, tg "code" [Tx s]] 460 | where a = anchor ("key" ++ (show n)) 461 | renderFragment cs (ExRef capit tag) = [link href (maybeCapitalise capit text)] 462 | where (href, text) = findExercise cs tag 463 | renderFragment cs (ChapRef capit tag) = [link href (maybeCapitalise capit text)] 464 | where (href, text) = findChapter cs tag 465 | renderFragment cs (FootRef n) = [Tg "a" [("class", "footref"), ("href", "#footnote" ++ (show n))] [Tx (show n)]] 466 | renderFragment cs Break = [tg "br" [], tg "br" []] 467 | renderFragment cs (Exponent n) = [tg "sup" [Tx n]] 468 | renderFragment cs (Link href title) = [link href title] 469 | 470 | renderFragments cs fs = concatMap (renderFragment cs) fs 471 | -------------------------------------------------------------------------------- /web/js/ejs.js: -------------------------------------------------------------------------------- 1 | //- bookutil.js 2 | 3 | function stripPath(url){ 4 | var slash = url.lastIndexOf("/"); 5 | return (slash == -1) ? url : url.slice(slash + 1); 6 | } 7 | 8 | function addPoint(a, b) { 9 | return {x: a.x + b.x, y: a.y + b.y}; 10 | } 11 | 12 | function placeElement(e, p) { 13 | setElementPosition(e, p); 14 | setElementDimensions(e, p); 15 | } 16 | 17 | var dimMode = function(){ 18 | var mode = undefined; 19 | return function(){ 20 | if (mode == undefined) { 21 | var test = DIV(null, "q"); 22 | test.style.width = "100px"; 23 | test.style.borderWidth = "5px"; 24 | test.style.borderStyle = "solid"; 25 | test.style.visibility = "hidden"; 26 | document.body.appendChild(test); 27 | mode = test.offsetWidth == 100 ? "precise" : "standard"; 28 | removeElement(test); 29 | } 30 | return mode; 31 | } 32 | }(); 33 | 34 | function growElement(node) { 35 | setElementDimensions(node, {w: node.parentNode.clientWidth, h: node.parentNode.clientHeight}); 36 | } 37 | 38 | function growEditor(node) { 39 | var cm = node.CodeMirror, parent = node.parentNode; 40 | setElementDimensions(cm.getWrapperElement(), {w: parent.clientWidth, h: parent.clientHeight}); 41 | cm.refresh(); 42 | } 43 | 44 | function centerElement(node, pos) { 45 | setElementPosition(node, {x: pos.x - node.offsetWidth / 2, y: pos.y - node.offsetHeight / 2}); 46 | } 47 | 48 | function attach(event, func) { 49 | return function(element){connect(element, event, func);}; 50 | } 51 | 52 | function scrollToBottom(element) { 53 | element.scrollTop = element.scrollHeight - element.clientHeight; 54 | } 55 | 56 | function disconnectTree(root) { 57 | disconnectAll(root); 58 | if (root.childNodes) 59 | forEach(root.childNodes, disconnectTree); 60 | } 61 | 62 | var browserIsIE = document.all && window.ActiveXObject; 63 | var preNewline = browserIsIE ? "\r" : "\n"; 64 | 65 | function probablyAnArray(value) { 66 | try { 67 | return value && typeof value == "object" && 68 | typeof value.length == "number" && typeof value.splice == "function"; 69 | } catch (e) {return false;} 70 | } 71 | 72 | function probablyARegexp(value) { 73 | try { 74 | return value && typeof value == "object" && 75 | typeof value.ignoreCase == "boolean" && typeof value.compile == "function"; 76 | } catch (e) {return false;} 77 | } 78 | 79 | function probablyADOMNode(value) { 80 | try { 81 | return value && typeof value == "object" && 82 | value.previousSibling !== undefined && 83 | (value.nodeType == 3 || value.nodeType == 1); 84 | } catch (e) {return false;} 85 | } 86 | 87 | function isAccessibleWindow(win) { 88 | try { 89 | return win && typeof win == "object" && win.document && win.document.nodeType == 9; 90 | } catch (e) {return false;} 91 | } 92 | 93 | function trim(string) { 94 | return string.match(/^\s*(.*)\s*$/)[1]; 95 | } 96 | 97 | function getCookie(name, def) { 98 | var cookies = document.cookie.split(";"); 99 | for (var i = 0; i < cookies.length; i++) { 100 | var parts = cookies[i].split("="); 101 | if (trim(parts[0]) == name) 102 | return parts[1]; 103 | } 104 | return def; 105 | } 106 | 107 | function setCookie(name, value) { 108 | document.cookie = name + "=" + value + "; expires=" + (new Date(2030, 0, 1)).toGMTString(); 109 | } 110 | 111 | //- initenv.js 112 | 113 | var htmlTable = []; 114 | 115 | function initEnvironment(win, output, callback) { 116 | var feed = (win.execScript ? function(code) {win.execScript(code);} : function(code) {win.__setTimeout(code, 0);}); 117 | 118 | function wrapCode(before, after, code) { 119 | env.code = code; 120 | return "__setTimeout(__ENV.callback, 0); try{" + before + "__ENV.code" + after +"}catch(e){__ENV.error(e);}"; 121 | } 122 | 123 | function run(code, showResult) { 124 | if (showResult) 125 | feed(wrapCode("$r = eval(", "); if ($r !== undefined) " + showResult + "($r);", code)); 126 | else 127 | feed(wrapCode("eval(", ");", code)); 128 | } 129 | 130 | function error(err) { 131 | env.output(DIV(null, "Exception: ", env.format(err))); 132 | if (err.stack) { 133 | var stack = err.stack.split("\n"); 134 | for (var i = 0; i < env.maxStackTrace && i < stack.length; i++) { 135 | var part = stack[i], at = part.indexOf("@"); 136 | if (at > 1 && part.slice(0, 5) != "eval(") 137 | win.print(" in function " + part.slice(0, at)); 138 | }; 139 | if (stack.length > env.maxStackTrace) 140 | win.print(" [...]") 141 | } 142 | }; 143 | 144 | var env = { 145 | output: method(output, "append"), 146 | format: method(output, "summarize"), 147 | parent: window, 148 | maxStackTrace: 10, 149 | error: error, 150 | run: run, 151 | callback: callback, 152 | code: null 153 | }; 154 | win.__ENV = env; 155 | 156 | win.load = function(file) { 157 | win.document.body.appendChild(withDocument(win.document, function(){ 158 | return createDOM("SCRIPT", {type: "text/javascript", src: /$http:\/\//.test(file) ? file : "js/" + file}); 159 | })); 160 | }; 161 | 162 | win.print = function() { 163 | var accum = []; 164 | for (var i = 0; i != arguments.length; i++) 165 | accum.push(String(arguments[i])); 166 | var joined = accum.join(""); 167 | env.output(DIV(null, preNewline != "\n" ? joined.replace(/\n/g, preNewline) : joined)); 168 | }; 169 | 170 | win.show = function(x) { 171 | env.output(DIV(null, env.format(x))); 172 | }; 173 | 174 | win.viewHTML = function(html) { 175 | htmlTable.push(String(html)); 176 | var result = window.open("view.html?id=" + (htmlTable.length - 1)); 177 | if (!result) 178 | alert("There seems to be a popup-blocker stopping this page from opening new windows. Try turning it off first."); 179 | return result; 180 | }; 181 | 182 | function wrapAction(action) { 183 | if (typeof action == "string") 184 | return "try{" + action + "}catch(e){__ENV.error(e);}"; 185 | else 186 | return function(){try{action();}catch(e){env.error(e);}}; 187 | } 188 | 189 | // Apparantly, the .call and .apply methods of setTimeout and 190 | // setInterval don't quite work on IE, so we rename them and call 191 | // them directly. 192 | win.__setTimeout = win.setTimeout; 193 | win.__setInterval = win.setInterval; 194 | 195 | win.setTimeout = function(action, interval) { 196 | return win.__setTimeout(wrapAction(action), interval); 197 | }; 198 | 199 | win.setInterval = function(action, interval) { 200 | return win.__setInterval(wrapAction(action), interval); 201 | }; 202 | 203 | // For some strange reason, this *has* to be executed from the 204 | // window itself, or creating the Error object fails (in IE). 205 | win.__setTimeout("if (/^\\[object/.test((new Error(\"...\")).toString())) " + 206 | "Error.prototype.toString = function(){return this.name + \": \" + this.message;};", 0); 207 | } 208 | 209 | //- env.js 210 | 211 | var ie = document.selection && window.ActiveXObject && /MSIE \d+/.test(navigator.userAgent); 212 | 213 | function makeFrame(place, init) { 214 | var frame = createDOM("IFRAME", {"style": "border-width: 0; display: none;"}); 215 | (place || document.body).appendChild(frame); 216 | 217 | var fdoc = frame.contentWindow.document; 218 | fdoc.open(); 219 | fdoc.write("Default"); 220 | fdoc.close(); 221 | 222 | if (init) { 223 | if (fdoc.body) 224 | init(frame); 225 | else 226 | connect(frame, "onload", function(){disconnectAll(frame, "onload"); init(frame);}); 227 | } 228 | return frame; 229 | } 230 | 231 | function checkAppVersion(minimum){ 232 | if (typeof navigator.appVersion != "string") return false; 233 | var parts = navigator.appVersion.split(" "); 234 | return parts.length > 0 && Number(parts[0]) >= minimum; 235 | } 236 | var useJSEditor = true; 237 | 238 | function Buffer(name, content, where){ 239 | if (useJSEditor){ 240 | var self = this; 241 | this.editor = CodeMirror(function(node){ 242 | self.node = node; 243 | where.appendChild(node); 244 | }, {value: content, 245 | matchBrackets: true, 246 | lineWrapping: true 247 | }); 248 | } 249 | else { 250 | this.node = TEXTAREA({spellcheck: false}, content); 251 | where.appendChild(this.node); 252 | } 253 | growEditor(this.node); 254 | this.name = name; 255 | } 256 | 257 | Buffer.prototype = { 258 | show: function() { 259 | showElement(this.node); 260 | if (useJSEditor) this.editor.refresh(); 261 | }, 262 | hide: function() { 263 | hideElement(this.node); 264 | }, 265 | remove: function() { 266 | removeElement(this.node); 267 | }, 268 | getCode: function() { 269 | if (useJSEditor) 270 | return this.editor.getValue(); 271 | else 272 | return this.node.value; 273 | } 274 | }; 275 | 276 | function History(){ 277 | this.history = []; 278 | this.pos = 0; 279 | this.current = ""; 280 | } 281 | 282 | History.prototype = { 283 | push: function(line) { 284 | this.history.push(line); 285 | if (this.history.length > 150) 286 | this.history = this.history.slice(0, 100); 287 | this.pos = this.history.length; 288 | }, 289 | move: function(dir, from) { 290 | if (this.pos == this.history.length) 291 | this.current = from; 292 | else 293 | this.history[this.pos] = from; 294 | 295 | this.pos = (this.pos + dir) % (this.history.length + 1); 296 | if (this.pos < 0) // JS' modulo is a bit impractical when dealing with negative numbers 297 | this.pos += (this.history.length + 1); 298 | 299 | if (this.pos == this.history.length) 300 | return this.current; 301 | else 302 | return this.history[this.pos]; 303 | } 304 | }; 305 | 306 | var dotdotdot = "\u2026"; 307 | 308 | function summarize(element, depth) { 309 | depth = depth || 0; 310 | var maxLength = depth == 0 ? 50 : 10; 311 | var self = this; 312 | 313 | function nodeLength(node) { 314 | if (node.nodeType == 3) 315 | return node.nodeValue.length; 316 | else 317 | return sum(map(nodeLength, node.childNodes)); 318 | } 319 | function span(className, content, onclick) { 320 | var result = SPAN({"class": className}, content); 321 | if (onclick) { 322 | connect(result, "onclick", function(event) { 323 | onclick(); 324 | event.stop(); 325 | }); 326 | } 327 | return result; 328 | } 329 | function objectHasProperties(object) { 330 | for (var x in object) 331 | return true; 332 | return false; 333 | } 334 | // Some mystery objects in IE throw an exception when you try to 335 | // enumerate them. 336 | function objectIsEnumerable(object) { 337 | try { 338 | for (var x in object) 339 | return true; 340 | return true; 341 | } 342 | catch (e) { 343 | return false; 344 | } 345 | } 346 | 347 | function formatFunc(value) { 348 | var regexp = /^\s*function ?([^\(]*)?\(([^\(]*)\)/; 349 | var match = String(value).match(regexp); 350 | return span("functionvalue", match ? "" : ""); 351 | } 352 | function formatArray(value) { 353 | var content = ["["], length = 2; 354 | if (depth > 1) { 355 | if (value.length > 0) 356 | content.push(dotdotdot); 357 | } 358 | else { 359 | for (var i = 0; i < value.length; i++) { 360 | var last = i == value.length - 1; 361 | var summary = self.summarize(value[i], depth + 1); 362 | var summaryLength = nodeLength(summary); 363 | if (length + summaryLength + (last ? 0 : 3) <= maxLength) { 364 | content.push(summary); 365 | length += summaryLength; 366 | } 367 | else { 368 | content.push(dotdotdot); 369 | break; 370 | } 371 | if (!last) 372 | content.push(", "); 373 | } 374 | } 375 | content.push("]"); 376 | return span("arrayvalue", content, bind(self.expand, self, value)); 377 | } 378 | function formatDOMNode(value) { 379 | if (value.nodeType == 3) 380 | return value.nodeValue.replace("\n", "\\n"); 381 | var accum = ["<", value.nodeName.toLowerCase()]; 382 | if (value.attributes) { 383 | forEach(value.attributes, function(attr) { 384 | if (attr.specified && typeof attr.nodeValue == "string") { 385 | accum.push(" " + attr.nodeName.toLowerCase() + "=" + serializeJSON(attr.nodeValue)); 386 | } 387 | }); 388 | } 389 | accum.push(">"); 390 | return accum.join(""); 391 | } 392 | function formatObject(value) { 393 | var asString = (probablyADOMNode(value) ? formatDOMNode(value) : value + ""); 394 | if (/^\[object.*\]$/.test(asString)) 395 | return formatPlainObject(value); 396 | else 397 | return span("objectvalue", asString, bind(self.expand, self, value)); 398 | } 399 | function formatPlainObject(value) { 400 | var content = ["{"], length = 2, elements = [], first = true; 401 | if (depth > 1) { 402 | if (objectIsEnumerable(value) && objectHasProperties(value)) 403 | content.push(dotdotdot); 404 | } 405 | else if (objectIsEnumerable(value)) { 406 | for (var name in value) { 407 | var skip = true; 408 | // Firefox has a nasty habit of throwing 'not implemented' or 'security' 409 | // exceptions when accessing certain properties in window and document. 410 | try { 411 | var element = value[name]; 412 | skip = false; 413 | } 414 | catch (e) {} 415 | if (!skip) { 416 | if (first) { 417 | first = false; 418 | } 419 | else { 420 | content.push(", "); 421 | length += 2; 422 | } 423 | var summary = self.summarize(element, depth + 1); 424 | var elementLength = nodeLength(summary) + 2 + name.length; 425 | if (length + elementLength <= maxLength) { 426 | content.push(name + ": "); 427 | content.push(summary); 428 | length += elementLength; 429 | } 430 | else { 431 | content.push(dotdotdot); 432 | break; 433 | } 434 | } 435 | } 436 | } 437 | content.push("}"); 438 | return span("objectvalue", content, bind(self.expand, self, value)); 439 | } 440 | function formatString(value){ 441 | return span("stringvalue", serializeJSON(value)); 442 | } 443 | function formatAtom(value) { 444 | return span("atomvalue", String(value)); 445 | } 446 | 447 | var type = typeof element; 448 | // Regexps report their type as function, but that is a lousy way to 449 | // display them. 450 | if (probablyARegexp(element)) 451 | type = "object"; 452 | if (type == "function") 453 | return formatFunc(element); 454 | else if (type == "object" && element != null) { 455 | // Some IE built-in functions report their type as "object" 456 | // Also, the navigator object in IE can not be passed to the String function. Gah. 457 | if (browserIsIE && ("" + element).match(/function .*/)) 458 | return formatFunc(element); 459 | else if (probablyAnArray(element)) 460 | return formatArray(element); 461 | else 462 | return formatObject(element); 463 | } 464 | else if (type == "string") 465 | return formatString(element); 466 | else 467 | return formatAtom(element); 468 | } 469 | 470 | function inspect(value) { 471 | function cutOff(name) { 472 | if (name.length > 22) 473 | return name.slice(0, 21) + dotdotdot; 474 | else 475 | return name; 476 | } 477 | 478 | var tbody = TBODY(); 479 | if (probablyAnArray(value)) { 480 | for (var i = 0; i < value.length; i++) 481 | tbody.appendChild(TR(null, TH(null, i + ":"), TD(null, this.summarize(value[i], 1)))); 482 | } 483 | else { 484 | var elements = []; 485 | for (var name in value) { 486 | var skip = true; 487 | try { 488 | var element = value[name]; 489 | skip = false; 490 | } 491 | catch(e) {} 492 | if (!skip) 493 | tbody.appendChild(TR(null, TH(null, cutOff(name) + ":"), TD(null, this.summarize(element, 1)))); 494 | } 495 | } 496 | return TABLE({"class": "objecttable"}, tbody); 497 | } 498 | 499 | function Output(place, parent) { 500 | this.place = place; 501 | this.scrollPos = 0; 502 | this.stack = []; 503 | this.parent = parent; 504 | 505 | this.outhead = DIV({"class": "outputhead"}, 506 | DIV({"class": "outputbutton", "title": "Clear output"}, "\u263C", 507 | attach("onclick", method(this, "clear"))), 508 | "Output:"); 509 | this.out = PRE(), this.show = PRE(); 510 | this.scroll = DIV({"class": "outputinner", "id": "outputinner"}, this.out); 511 | this.showhead = DIV({"class": "outputhead"}, 512 | DIV({"class": "outputbutton", "title": "Store this value in $i"}, "$", 513 | attach("onclick", method(this, "copy"))), 514 | DIV({"class": "outputbutton", "title": "Close inspect view"}, "\u00D7", 515 | attach("onclick", method(this, "close"))), 516 | DIV({"class": "outputbutton", "title": "Back"}, "\u2190", 517 | attach("onclick", method(this, "back"))), 518 | "View object"); 519 | replaceChildNodes(this.place, this.outhead, this.scroll); 520 | } 521 | 522 | Output.prototype = { 523 | append: function(node) { 524 | this.out.appendChild(node); 525 | if (this.stack.length == 0) 526 | scrollToBottom(this.scroll); 527 | }, 528 | clear: function() { 529 | disconnectTree(this.out); 530 | replaceChildNodes(this.out); 531 | }, 532 | expand: function(value) { 533 | if (this.stack.length == 0) { 534 | this.scrollPos = this.scroll.scrollTop; 535 | this.place.replaceChild(this.showhead, this.outhead); 536 | this.scroll.replaceChild(this.show, this.out); 537 | } 538 | this.stack.push(value); 539 | this.display(value); 540 | }, 541 | display: function(value) { 542 | disconnectTree(this.show); 543 | this.scroll.scrollTop = 0; 544 | replaceChildNodes(this.show, this.inspect(value)); 545 | }, 546 | close: function() { 547 | this.place.replaceChild(this.outhead, this.showhead); 548 | this.scroll.replaceChild(this.out, this.show); 549 | this.scroll.scrollTop = this.scrollPos; 550 | this.stack = []; 551 | }, 552 | back: function() { 553 | this.stack.pop(); 554 | if (this.stack.length == 0) 555 | this.close(); 556 | else 557 | this.display(this.stack[this.stack.length - 1]); 558 | }, 559 | copy: function() { 560 | if (this.parent.env) 561 | this.parent.env.$i = this.stack[this.stack.length - 1]; 562 | }, 563 | summarize: summarize, 564 | inspect: inspect 565 | }; 566 | 567 | function Console(param) { 568 | var active, self = this, frame = null; 569 | var history = new History(); 570 | var out = new Output(param.output, this); 571 | var baseEnv, codeStream = [], streaming = false; 572 | resetEnvironment(); 573 | 574 | function showBuffer(buffer) { 575 | if (active) 576 | active.hide(); 577 | active = buffer; 578 | active.show(); 579 | return active; 580 | } 581 | 582 | function runCode(code, showResult) { 583 | if (self.env && !self.env.__ENV) { 584 | self.env = baseEnv; 585 | self.env.print("Lost attached window, detaching."); 586 | } 587 | 588 | if (streaming || !self.env) { 589 | codeStream.push({code: code, show: showResult}); 590 | } 591 | else { 592 | streaming = true; 593 | self.env.__ENV.run(code, showResult); 594 | } 595 | } 596 | // Because the window that code must be sent to can change, code 597 | // must only be sent when the code before it has finished running. 598 | // Hence this callback. 599 | function runCallback() { 600 | if (codeStream.length > 0 && self.env) { 601 | var code = codeStream.shift(); 602 | self.env.__ENV.run(code.code, code.show); 603 | } 604 | else { 605 | streaming = false; 606 | } 607 | } 608 | function setEnvironment(win) { 609 | self.env = win; 610 | if (win && !streaming && codeStream.length > 0) 611 | runCallback(); 612 | } 613 | 614 | var buffers = SELECT({"class": "buffers"}); 615 | replaceChildNodes( 616 | param.controls, 617 | BUTTON({title: "Run the code in this buffer", "type": "button"}, 618 | "Run", attach("onclick", function(){runCode(active.getCode(), false);})), 619 | buffers, 620 | BUTTON({title: "New buffer", "type": "button"}, "New", attach("onclick", createBuffer)), 621 | // BUTTON({title: "Load a file as a new buffer", "type": "button"}, "Load", attach("onclick", loadFile)), 622 | BUTTON({title: "Close this buffer", "type": "button"}, "Close", attach("onclick", closeBuffer)), 623 | BUTTON({title: "Reset the console environment", "type": "button"}, "Reset", attach("onclick", resetEnvironment))); 624 | connect(buffers, "onchange", function(){ 625 | showBuffer(buffers.options[buffers.selectedIndex].buffer); 626 | }); 627 | var repl = INPUT({"type": "text"}); 628 | replaceChildNodes(param.repl, repl); 629 | connect(repl, "onkeydown", lineKey); 630 | 631 | function bufferName(name){ 632 | function exists(name) { 633 | return some(buffers.childNodes, function(option){return option.text == name;}); 634 | } 635 | if (!exists(name)) return name; 636 | for (var i = 2; ; i++) { 637 | var newName = name + "(" + i + ")"; 638 | if (!exists(newName)) return newName; 639 | } 640 | } 641 | 642 | function addBuffer(name, content){ 643 | var option = OPTION(null, bufferName(name)); 644 | buffers.appendChild(option); 645 | option.selected = true; 646 | option.buffer = new Buffer(name, content || "", param.editor); 647 | return showBuffer(option.buffer); 648 | } 649 | function createBuffer(){ 650 | var name = prompt("Enter a name for the new buffer", ""); 651 | if (name) 652 | addBuffer(name); 653 | } 654 | function closeBuffer(){ 655 | if (buffers.selectedIndex != -1) { 656 | buffers.removeChild(buffers.childNodes[buffers.selectedIndex]); 657 | active.remove(); 658 | if (buffers.firstChild){ 659 | active = buffers.firstChild.buffer; 660 | buffers.firstChild.selected = true; 661 | active.show(); 662 | } 663 | else { 664 | active = null; 665 | } 666 | } 667 | } 668 | function loadFile(){ 669 | var filename = prompt("Enter a filename or URL", ""); 670 | if (filename) { 671 | var simplename = stripPath(filename); 672 | if (!/^http:\/\//.test(filename)) 673 | filename = "js/" + filename; 674 | var defer = doXHR(filename); 675 | defer.addCallback(function(xhr){addBuffer(simplename, xhr.responseText);}); 676 | defer.addErrback(function(){alert("File '" + simplename + "' could not be loaded.");}); 677 | } 678 | } 679 | 680 | function attachEnvironment(win) { 681 | function detach() { 682 | if (self.env == win) { 683 | var title = self.env.document.title; 684 | setEnvironment(baseEnv); 685 | self.env.print("Detaching from window '", title || "[unnamed]", "'."); 686 | } 687 | } 688 | function attach() { 689 | if (!win.__ENV) 690 | initEnvironment(win, out, runCallback); 691 | win.detach = detach; 692 | var unload = connect(win, "onunload", detach); 693 | connect(window, "onunload", function(){disconnect(unload);}); 694 | setEnvironment(win); 695 | self.env.print("Attaching to window '", win.document.title || "[unnamed]", "'."); 696 | } 697 | 698 | if (isAccessibleWindow(win)) { 699 | connect(win, "onload", attach); 700 | // When immediately attaching to a newly created window, wait 701 | // until onload, or strange things happen. 702 | self.env = null; 703 | if (win.document.body && (win.document.body.childNodes.length > 0 || win.document.title != "")) 704 | attach(); 705 | } 706 | else { 707 | self.env.print("Not an accessible window."); 708 | } 709 | } 710 | 711 | function resetEnvironment(){ 712 | if (frame) 713 | removeElement(frame); 714 | frame = makeFrame(param.framePlace, function(frame){ 715 | baseEnv = frame.contentWindow; 716 | initEnvironment(baseEnv, out, runCallback); 717 | baseEnv.attach = attachEnvironment; 718 | if (param.initEnv) 719 | param.initEnv(baseEnv); 720 | setEnvironment(baseEnv); 721 | }); 722 | } 723 | 724 | function evalLine(){ 725 | var line = repl.value; 726 | repl.value = ""; 727 | history.push(line); 728 | runCode(line, "show"); 729 | } 730 | function getHistory(dir){ 731 | repl.value = history.move(dir, repl.value); 732 | } 733 | 734 | // Opera generates events for the '(' and '&' keys that are pretty 735 | // much the same as those for arrow down and up. So, to 736 | // disambiguate, we disallow shift when those are pressed. 737 | function lineKey(event){ 738 | var keyObj = event.key(), key = keyObj.string; 739 | var shift = event.modifier().shift; 740 | if (key == "KEY_ENTER" || keyObj.code == 10) 741 | evalLine(); 742 | else if (key == "KEY_ARROW_UP" && !shift) 743 | getHistory(-1); 744 | else if (key == "KEY_ARROW_DOWN" && !shift) 745 | getHistory(1); 746 | else 747 | return; 748 | event.stop(); 749 | } 750 | 751 | addBuffer("*scratch*"); 752 | 753 | this.loadCode = addBuffer; 754 | this.runCode = function(code){runCode(code, false);}; 755 | this.evalCode = function(code){runCode(code, "show");}; 756 | this.printCode = function(code){runCode(code, "print");}; 757 | } 758 | 759 | connect(window, "onload", function() { 760 | if (/^\[object/.test((new Error("...")).toString())) 761 | Error.prototype.toString = function(){return this.name + ": " + this.message;}; 762 | }); 763 | 764 | //- book.js 765 | 766 | // This code assumes a 1px border around the console, repl, output, 767 | // and editor elements. 768 | var _console = null; 769 | 770 | function hasClass(node, cls) { 771 | return (new RegExp("\\b" + cls + "\\b")).test(node.className); 772 | } 773 | 774 | var processPage = function(){ 775 | function hideSolutions() { 776 | forEach(getElementsByTagAndClassName("div", "solution"), function(solution) { 777 | solution.style.display = "none"; 778 | 779 | var showToggle = DIV({"class": "toggle"}, "[show solution]"); 780 | connect(showToggle, "onclick", function() { 781 | showToggle.style.display = "none"; 782 | solution.style.display = ""; 783 | }); 784 | solution.parentNode.insertBefore(showToggle, solution); 785 | 786 | var hideToggle = DIV({"class": "solutionarrow", title: "Hide the solution."}, "\u00D7"); 787 | connect(hideToggle, "onclick", function() { 788 | showToggle.style.display = ""; 789 | solution.style.display = "none"; 790 | }); 791 | solution.insertBefore(hideToggle, solution.firstChild); 792 | }); 793 | } 794 | 795 | function positionFloater(element, pos) { 796 | var minWidth = 600; 797 | var winWidth = getViewportDimensions().w; 798 | pos.x = Math.min(pos.x, winWidth - minWidth); 799 | setElementDimensions(element, {w: winWidth - pos.x - 35}); 800 | setElementPosition(element, pos); 801 | } 802 | 803 | function moveFootnotes() { 804 | var notelist = getFirstElementByTagAndClassName("ol", "footnotes"); 805 | if (!notelist) 806 | return; 807 | 808 | function moveNote(note, ref) { 809 | var floater = DIV({"class": "floater footnotefloat"}, note.childNodes); 810 | floater.style.display = "none"; 811 | document.body.appendChild(floater); 812 | var newRef = SPAN({"class": "footref"}, ref.firstChild); 813 | ref.parentNode.replaceChild(newRef, ref); 814 | connect(newRef, "onmouseover", function(event) { 815 | positionFloater(floater, addPoint(event.mouse().page, {x: 5, y: 10})); 816 | floater.style.display = "block"; 817 | }); 818 | connect(newRef, "onmouseout", function() { 819 | floater.style.display = "none"; 820 | }); 821 | } 822 | 823 | var notes = notelist.childNodes; 824 | var refs = getElementsByTagAndClassName("a", "footref"); 825 | for (var i = 0; i < notes.length; i++) 826 | moveNote(notes[i], refs[i]); 827 | removeElement(notelist); 828 | } 829 | 830 | function getCode(code){ 831 | function flattenNode(node){ 832 | if (node.nodeType == 3) 833 | return node.nodeValue; 834 | else if (node.nodeName == "SPAN") 835 | return node.firstChild.nodeValue; 836 | else 837 | return ""; 838 | } 839 | return map(flattenNode, code.childNodes).join(""); 840 | } 841 | 842 | function runCodeUpto(code) { 843 | var fragments = []; 844 | while (code) { 845 | removeElementClass(code, "not-run"); 846 | fragments.push(getCode(code)); 847 | code = code.prev; 848 | } 849 | for (var i = fragments.length - 1; i >= 0; i--) 850 | _console.runCode(fragments[i]); 851 | } 852 | 853 | function addCodeButtons(){ 854 | var prev = null; 855 | forEach(getElementsByTagAndClassName("pre", "code"), function(code) { 856 | addElementClass(code, "not-run"); 857 | var expr = hasClass(code, "expression"); 858 | var runUpto = !(expr || hasClass(code, "invalid")); 859 | if (runUpto) { 860 | code.prev = prev; 861 | prev = code; 862 | } 863 | 864 | if (!expr) { 865 | var load = code.insertBefore(BUTTON({"class": "codebutton load", "type": "button", "title": "Load this code into the console"}), code.firstChild); 866 | connect(load, "onclick", function(){ 867 | setOpen(true); 868 | _console.loadCode("example", getCode(code)); 869 | }); 870 | } 871 | 872 | var run = code.insertBefore(BUTTON({"class": "codebutton run", "title": "Run this code", "type": "button"}), code.firstChild); 873 | connect(run, "onclick", function(event){ 874 | removeElementClass(code, "not-run"); 875 | setOpen(true); 876 | if (runUpto && event.modifier().shift) 877 | runCodeUpto(code); 878 | else 879 | _console[expr ? "printCode" : "runCode"](getCode(code)); 880 | }); 881 | }); 882 | } 883 | 884 | var popup = null; 885 | function react() { 886 | if (popup) { 887 | closeReaction(); 888 | return; 889 | } 890 | 891 | var name = INPUT({value: getCookie("name", ""), type: "text"}), 892 | email = INPUT({value: getCookie("email", ""), type: "text"}), 893 | subject = INPUT({value: "", type: "text"}), 894 | message = TEXTAREA(null, ""); 895 | popup = DIV({"class": "reactpopup"}, 896 | P(null, "Send me a message..."), 897 | P(null, SPAN(null, "Your name:"), name), 898 | P(null, SPAN(null, "Your e-mail:"), email), 899 | P(null, SPAN(null, "Subject:"), subject), 900 | P(null, message), 901 | P(null, 902 | BUTTON({type: "button"}, "Send", attach("onclick", sendReaction)), " ", 903 | BUTTON({type: "button"}, "Cancel", attach("onclick", closeReaction)))); 904 | if (fixedConsole) popup.style.position = "fixed"; 905 | document.body.appendChild(popup); 906 | name.focus(); 907 | 908 | function sendByXHR() { 909 | var data = queryString({name: name.value, email: email.value, message: message.value, 910 | subject: subject.value, chapter: chapterTag}); 911 | request = doXHR("/contact", {method: "POST", sendContent: data, 912 | headers: {"Content-type": "application/x-www-form-urlencoded; charset=utf-8", 913 | "Content-length": data.length, 914 | "Connection": "close"}}); 915 | function fail(reason) { 916 | alert("Could not deliver your message. (" + reason + ")"); 917 | } 918 | request.addErrback(function(xhr){ 919 | fail((err.xhr && err.xhr.statusText) || err.message); 920 | }); 921 | request.addCallback(function(xhr) { 922 | if (xhr.responseText != "ok") 923 | fail(xhr.responseText); 924 | }); 925 | } 926 | function sendByIFrame() { 927 | var frame = createDOM("IFRAME", {style: "border-width: 0; position: absolute; width: 1px; height: 1px; top: 0px;", 928 | src: "js/sendreaction.html"}); 929 | connect(frame, "onload", function() { 930 | var win = frame.contentWindow; 931 | var form = win.document.getElementById("form"); 932 | form.elements.name.value = name.value; 933 | form.elements.email.value = email.value; 934 | form.elements.chapter.value = chapterTag; 935 | form.elements.message.value = message.value; 936 | form.elements.subject.value = subject.value; 937 | form.submit(); 938 | setTimeout(partial(removeElement, frame), 10000); 939 | }); 940 | document.body.appendChild(frame); 941 | } 942 | 943 | function sendReaction() { 944 | if (message.value == "") { 945 | alert("You did not enter a message."); 946 | return; 947 | } 948 | 949 | if (!/\b[\w\.-]+@[\w\.-]+\.\w{2,4}\b/.test(email.value) && !confirm("If you do not enter a valid e-mail address, I will probably ignore you. Send anyway?")) 950 | return; 951 | 952 | setCookie("name", name.value); 953 | setCookie("email", email.value); 954 | 955 | if (/eloquentjavascript\.net/.test(document.domain)) 956 | sendByXHR(); 957 | else 958 | sendByIFrame(); 959 | closeReaction(); 960 | } 961 | function closeReaction() { 962 | removeElement(popup); 963 | popup = null 964 | } 965 | } 966 | 967 | function addReactButton() { 968 | var b = BUTTON({type: "button", "class": "react", 969 | title: "Send me a message"}, 970 | attach("onclick", react)); 971 | document.body.appendChild(b); 972 | if (fixedConsole) b.style.position = "fixed"; 973 | } 974 | 975 | var fixedConsole = !ie; 976 | var fakeFixed = false; 977 | var open = true; 978 | var contentRatio = Number(getCookie("contentRatio", .75)); 979 | var topBar = 13; 980 | var sizeCorrection = dimMode() == "standard" ? -2 : 0; 981 | var minContentWidth = 700; 982 | 983 | function setContentRatio(consoleHeight) { 984 | var winHeight = getViewportDimensions().h; 985 | contentRatio = Math.min(.96, Math.max(.02, (winHeight - consoleHeight) / winHeight)); 986 | setCookie("contentRatio", String(contentRatio)); 987 | } 988 | 989 | function alignConsole() { 990 | if (fakeFixed) { 991 | $("console").style.bottom = "-" + (iphone ? window.pageYOffset : document.body.scrollTop) + "px"; 992 | } 993 | else { 994 | $("console").style.bottom = "0px"; 995 | } 996 | } 997 | 998 | function resizeFrames() { 999 | var winSize = getViewportDimensions(); 1000 | if (open) { 1001 | var consoleSize = Math.max(minSize, Math.round(winSize.h * (1.0 - contentRatio))); 1002 | if (fixedConsole) 1003 | $("consoleCompensation").style.height = consoleSize + "px"; 1004 | else 1005 | setElementDimensions($("content"), {h: Math.max(0, winSize.h - 1 - consoleSize)}); 1006 | var width = fixedConsole ? document.body.clientWidth : winSize.w + sizeCorrection; 1007 | if (fixedConsole && fakeFixed) width += sizeCorrection; 1008 | setElementDimensions($("console"), {h: consoleSize + sizeCorrection, w: width}); 1009 | resizeConsole(); 1010 | } 1011 | else { 1012 | if (fixedConsole) { 1013 | $("consoleCompensation").style.height = "0px"; 1014 | $("console").style.height = topBar + "px"; 1015 | } 1016 | else { 1017 | setElementDimensions($("content"), {h: winSize.h - topBar + sizeCorrection}); 1018 | } 1019 | var width = fixedConsole ? document.body.clientWidth : winSize.w + sizeCorrection; 1020 | if (fixedConsole && fakeFixed) width += sizeCorrection; 1021 | setElementDimensions($("console"), {h: topBar, w: width}); 1022 | } 1023 | if (fixedConsole) alignConsole(); 1024 | } 1025 | 1026 | function addConsole() { 1027 | if (fixedConsole) { 1028 | document.body.appendChild(DIV({"class": "frame console" + (open ? " open" : ""), "id": "console"})); 1029 | document.getElementsByClassName("content")[0].id = "content"; 1030 | $("console").style.position = fakeFixed ? "absolute" : "fixed"; 1031 | alignConsole(); 1032 | document.body.appendChild(DIV({id: "consoleCompensation"})); 1033 | } 1034 | else { 1035 | document.body.appendChild(DIV({"class": "frame", "id": "content"}, document.body.childNodes[1])); 1036 | document.body.appendChild(DIV({"class": "frame console" + (open ? " open" : ""), "id": "console"})); 1037 | document.body.style.overflow = "hidden"; 1038 | } 1039 | initConsole($("console")); 1040 | setOpen(false); 1041 | connect(window, "onresize", resizeFrames); 1042 | 1043 | if (!fixedConsole) { 1044 | window.onscroll = function() { 1045 | if (document.body.scrollTop > 0) { 1046 | $("content").scrollTop += document.body.scrollTop; 1047 | document.body.scrollTop = 0; 1048 | $("content").focus(); 1049 | } 1050 | }; 1051 | } 1052 | else if (fakeFixed) { 1053 | var timeout = null; 1054 | window.onscroll = function() { 1055 | $("console").style.display = "none"; 1056 | clearTimeout(timeout); 1057 | timeout = setTimeout(function() { 1058 | $("console").style.display = ""; 1059 | alignConsole(); 1060 | }, 100); 1061 | }; 1062 | } 1063 | } 1064 | 1065 | var minSize = 120; 1066 | 1067 | function resizeConsole() { 1068 | var margin = 6; 1069 | var leftRatio = .4; 1070 | var width = fixedConsole ? document.body.clientWidth : $("console").clientWidth; 1071 | var height = $("console").clientHeight; 1072 | 1073 | var bottomHeight = Math.max($("repl").offsetHeight, $("controls").offsetHeight); 1074 | var topHeight = height - topBar - 2 * margin - bottomHeight; 1075 | 1076 | var innerWidth = width - 3 * margin; 1077 | var leftWidth = Math.round(leftRatio * innerWidth); 1078 | var rightWidth = innerWidth - leftWidth; 1079 | 1080 | var output = $("output"), editor = $("editor"); 1081 | 1082 | placeElement(output, {x: margin, y: topBar, w: leftWidth + sizeCorrection, h: topHeight + sizeCorrection}); 1083 | placeElement($("repl"), {x: margin, y: topBar + margin + topHeight, w: leftWidth + sizeCorrection}); 1084 | setElementDimensions($("repl").firstChild, {w: $("repl").clientWidth}); 1085 | 1086 | placeElement(editor, {x: 2 * margin + leftWidth, y: topBar, w: rightWidth + sizeCorrection, h: topHeight + sizeCorrection}); 1087 | placeElement($("controls"), {x: 2 * margin + leftWidth, y: topBar + margin + topHeight, w: rightWidth}); 1088 | 1089 | forEach(editor.childNodes, growEditor); 1090 | setElementDimensions($("outputinner"), {w: output.clientWidth, h: output.clientHeight - output.firstChild.offsetHeight}); 1091 | } 1092 | 1093 | function setOpen(nowOpen){ 1094 | if (open == nowOpen) 1095 | return; 1096 | open = nowOpen; 1097 | $("editor").style.display = $("repl").style.display = $("output").style.display = 1098 | $("controls").style.display = $("resize").style.display = (open ? "" : "none"); 1099 | resizeFrames(); 1100 | if (open) 1101 | addElementClass($("console"), "open"); 1102 | else 1103 | removeElementClass($("console"), "open"); 1104 | } 1105 | 1106 | function dragResize(event) { 1107 | var size = $("console").offsetHeight, startSize = size; 1108 | var startY = event.mouse().page.y; 1109 | 1110 | document.body.style.cursor = "n-resize"; 1111 | 1112 | var marker = DIV({"class": "resizemarker"}); 1113 | marker.style.bottom = size + "px"; 1114 | $("console").appendChild(marker); 1115 | 1116 | var tracker = connect(document.body, "onmousemove", function(event) { 1117 | size = Math.max(minSize, startSize - (event.mouse().page.y - startY)); 1118 | marker.style.bottom = size + "px"; 1119 | }); 1120 | var finish = connect(document.body, "onmouseup", function(event) { 1121 | disconnect(tracker); 1122 | disconnect(finish); 1123 | document.body.style.cursor = ""; 1124 | setContentRatio(size); 1125 | resizeFrames(); 1126 | removeElement(marker); 1127 | }); 1128 | } 1129 | 1130 | function initConsole(where) { 1131 | function toggle(event){ 1132 | setOpen(!open); 1133 | } 1134 | 1135 | var showHide = BUTTON({"class": "showhide", "type": "button", "title": "Open or close the console"}, 1136 | attach("onclick", toggle)); 1137 | var resize = BUTTON({"class": "resize", "type": "button", "title": "Resize the console", "id": "resize"}, 1138 | attach("onmousedown", dragResize)); 1139 | 1140 | var output = DIV({"class": "output", "id": "output"}), 1141 | controls = DIV({"id": "controls"}), 1142 | editor = DIV({"class": "editor", "id": "editor"}), 1143 | repl = DIV({"class": "editor", "id": "repl"}), 1144 | header = DIV({"class": "header"}, SPAN(null, "CONSOLE", attach("onclick", toggle)), resize, showHide); 1145 | replaceChildNodes(where, header, output, controls, editor, repl); 1146 | 1147 | function initFrame(env) { 1148 | if (window.chapterTag) 1149 | env.load("chapter/" + chapterTag + ".js"); 1150 | } 1151 | _console = new Console({output: output, controls: controls, editor: editor, repl: repl, 1152 | initEnv: initFrame}); 1153 | } 1154 | 1155 | function restoreBookmark(){ 1156 | if (/#/.test(location.href)) 1157 | location.href = location.href; 1158 | } 1159 | 1160 | setTimeout(function(){document.body.style.visibility = "";}, 0); 1161 | if (/Safari\//.test(navigator.userAgent) && /Mobile\//.test(navigator.userAgent)) { 1162 | // Doesn't work on mobile safari 1163 | } else if (/Version\/2/.test(navigator.userAgent) && /Safari\//.test(navigator.userAgent)) { 1164 | if (!getCookie("safariwarning", false)) { 1165 | setCookie("safariwarning", "1"); 1166 | alert("Safari 2 unfortunately does not support the JavaScript used by this book. Extra functionality will be disabled. Upgrade to version 3 (still beta), or use Firefox to read the book with full functionality."); 1167 | } 1168 | } else if (window.opera && Number(window.opera.version()) < 9.52) { 1169 | if (!getCookie("operawarning", false)) { 1170 | setCookie("operawarning", "1"); 1171 | alert("Your version of Opera is not supported by this site. The 'active' components of this book will be disabled. Use version 9.52+, Firefox, or a recent Safari if you want full functionality."); 1172 | } 1173 | } else { 1174 | hideSolutions(); 1175 | moveFootnotes(); 1176 | addCodeButtons(); 1177 | // addReactButton(); 1178 | addConsole(); 1179 | restoreBookmark(); 1180 | } 1181 | }; 1182 | 1183 | connect(window, "onload", processPage); 1184 | --------------------------------------------------------------------------------