├── .gitignore ├── 00_intro.md ├── 01_values.md ├── 02_program_structure.md ├── 03_functions.md ├── 04_data.md ├── 05_higher_order.md ├── 06_object.md ├── 07_robot.md ├── 08_error.md ├── 09_regexp.md ├── 10_modules.md ├── 11_async.md ├── 12_language.md ├── 13_browser.md ├── 14_dom.md ├── 15_event.md ├── 16_game.md ├── 17_canvas.md ├── 18_http.md ├── 19_paint.md ├── 20_node.md ├── 21_skillsharing.md ├── Makefile ├── README.md ├── code ├── LICENSE ├── _stop_keys.js ├── animatevillage.js ├── chapter │ ├── .keep │ └── 22_fast.js ├── draw_layout.js ├── hangar2.js ├── hello.js ├── index.html ├── intro.js ├── journal.js ├── levels.js ├── load.js ├── packages_chapter_10.js ├── scripts.js ├── skillsharing │ ├── .keep │ ├── package.json │ └── public │ │ └── skillsharing.css ├── solutions │ ├── 02_1_looping_a_triangle.js │ ├── 02_2_fizzbuzz.js │ ├── 02_3_chessboard.js │ ├── 03_1_minimum.js │ ├── 03_2_recursion.js │ ├── 03_3_bean_counting.js │ ├── 04_1_the_sum_of_a_range.js │ ├── 04_2_reversing_an_array.js │ ├── 04_3_a_list.js │ ├── 04_4_deep_comparison.js │ ├── 05_1_flattening.js │ ├── 05_2_your_own_loop.js │ ├── 05_3_everything.js │ ├── 05_4_dominant_writing_direction.js │ ├── 06_1_a_vector_type.js │ ├── 06_2_groups.js │ ├── 06_3_iterable_groups.js │ ├── 06_4_borrowing_a_method.js │ ├── 07_1_measuring_a_robot.js │ ├── 07_2_robot_efficiency.js │ ├── 07_3_persistent_group.js │ ├── 08_1_retry.js │ ├── 08_2_the_locked_box.js │ ├── 09_1_regexp_golf.js │ ├── 09_2_quoting_style.js │ ├── 09_3_numbers_again.js │ ├── 10_2_roads_module.js │ ├── 11_1_quiet_times.js │ ├── 11_1_tracking_the_scalpel.js │ ├── 11_2_real_promises.js │ ├── 11_3_building_promiseall.js │ ├── 12_1_arrays.js │ ├── 12_3_comments.js │ ├── 12_4_fixing_scope.js │ ├── 14_1_build_a_table.html │ ├── 14_2_elements_by_tag_name.html │ ├── 14_3_the_cats_hat.html │ ├── 15_1_balloon.html │ ├── 15_2_mouse_trail.html │ ├── 15_3_tabs.html │ ├── 16_1_game_over.html │ ├── 16_2_pausing_the_game.html │ ├── 16_3_a_monster.html │ ├── 17_1_shapes.html │ ├── 17_2_the_pie_chart.html │ ├── 17_3_a_bouncing_ball.html │ ├── 18_1_content_negotiation.js │ ├── 18_2_a_javascript_workbench.html │ ├── 18_3_conways_game_of_life.html │ ├── 19_1_keyboard_bindings.html │ ├── 19_2_efficient_drawing.html │ ├── 19_3_circles.html │ ├── 19_4_proper_lines.html │ ├── 20_1_search_tool.mjs │ ├── 20_2_directory_creation.mjs │ ├── 20_3_a_public_space_on_the_web │ │ ├── index.html │ │ ├── other.html │ │ └── public_space.js │ ├── 21_1_disk_persistence.mjs │ ├── 21_2_comment_field_resets.mjs │ ├── 22_1_pathfinding.js │ ├── 22_1_prime_numbers.js │ ├── 22_2_faster_prime_numbers.js │ ├── 22_2_timing.js │ └── 22_3_optimizing.js └── squareworker.js ├── epub ├── META-INF │ └── container.xml ├── content.opf.src ├── font │ ├── cinzel_bold.otf │ └── pt_mono.otf ├── frontmatter.xhtml ├── mimetype ├── style.css ├── titlepage.xhtml └── toc.xhtml.src ├── html ├── .keep ├── Eloquent_JavaScript.epub ├── Eloquent_JavaScript.mobi ├── Eloquent_JavaScript.pdf ├── Eloquent_JavaScript_small.pdf ├── author.html ├── author.json ├── author.txt ├── backers3.html ├── code ├── css │ ├── ejs.css │ ├── game.css │ └── paint.css ├── empty.html ├── errata.html ├── example │ ├── bert.json │ ├── data.txt │ ├── fruit.json │ ├── fruit.xml │ ├── message.html │ ├── muriel.json │ ├── submit.html │ └── suzie.json ├── favicon.ico ├── font │ ├── cinzel_bold.woff │ └── pt_mono.woff ├── img └── index.html ├── img ├── Hieres-sur-Amby.png ├── blockquote.png ├── boxed-in.png ├── button_disabled.png ├── canvas_beziercurve.png ├── canvas_circle.png ├── canvas_fill.png ├── canvas_game.png ├── canvas_path.png ├── canvas_pie_chart.png ├── canvas_quadraticcurve.png ├── canvas_scale.png ├── canvas_stroke.png ├── canvas_tree.png ├── canvas_triangle.png ├── cat-animation.png ├── cat.png ├── chapter_picture_00.jpg ├── chapter_picture_1.jpg ├── chapter_picture_10.jpg ├── chapter_picture_11.jpg ├── chapter_picture_12.jpg ├── chapter_picture_13.jpg ├── chapter_picture_14.jpg ├── chapter_picture_15.jpg ├── chapter_picture_16.jpg ├── chapter_picture_17.jpg ├── chapter_picture_18.jpg ├── chapter_picture_19.jpg ├── chapter_picture_2.jpg ├── chapter_picture_20.jpg ├── chapter_picture_21.jpg ├── chapter_picture_3.jpg ├── chapter_picture_4.jpg ├── chapter_picture_5.jpg ├── chapter_picture_6.jpg ├── chapter_picture_7.jpg ├── chapter_picture_8.jpg ├── chapter_picture_9.jpg ├── color-field.png ├── colored-links.png ├── control-io.svg ├── controlflow-if.svg ├── controlflow-loop.svg ├── controlflow-nested-if.svg ├── controlflow-straight.svg ├── cos_sin.svg ├── cover.jpg ├── darkblue.png ├── display.png ├── drag-bar.png ├── exercise_shapes.png ├── flood-grid.svg ├── form_fields.png ├── form_select.png ├── game-grid.svg ├── game_simpleLevel.png ├── generated │ └── .keep ├── hat.png ├── help-field.png ├── highlighted.png ├── holberton.png ├── home-page.png ├── html-boxes.svg ├── html-links.svg ├── html-tree.svg ├── line-grid.svg ├── linked-list.svg ├── middle_east_graph.png ├── middle_east_graph_random.png ├── mirror.svg ├── nextjournal.png ├── object.jpg ├── object_full.jpg ├── ostrich.png ├── parcel2x.png ├── pixel_editor.png ├── pizza-squirrel.svg ├── player.png ├── player_big.png ├── prompt.png ├── rabbits.svg ├── re_number.svg ├── re_pigchickens.svg ├── re_slow.svg ├── robot_idle.png ├── robot_idle2x.png ├── robot_moving.gif ├── robot_moving2x.gif ├── skillsharing.png ├── sprites.png ├── sprites_big.png ├── svg-demo.png ├── syntax_tree.svg ├── tamil.png ├── transform.svg ├── tree_graph.png ├── unicycle.svg ├── village.png ├── village2x.png ├── weresquirrel.png └── weresquirrel.svg ├── package.json ├── pdf ├── book.tex └── build.sh └── src ├── add_images_to_epub.mjs ├── build_code.mjs ├── chapter.html ├── chapter_info.mjs ├── check_links.mjs ├── client ├── code.mjs ├── editor.mjs ├── ejs.mjs ├── index.mjs ├── rollup.config.mjs └── sandbox.mjs ├── epub_chapter.html ├── extract_hints.mjs ├── generate_epub_toc.mjs ├── markdown.mjs ├── pseudo_json.mjs ├── render_html.mjs ├── render_latex.mjs ├── require.js ├── run_tests.mjs ├── transform.mjs └── varify.mjs /.gitignore: -------------------------------------------------------------------------------- 1 | /nostarch/[012]*.tex 2 | /nostarch/hints.tex 3 | /nostarch/book.* 4 | /nostarch.pdf 5 | /pdf/[012]*.tex 6 | /pdf/hints.tex 7 | /pdf/book.* 8 | /pdf/book_mobile.* 9 | /pdf/*.log 10 | /book.pdf 11 | /book_mobile.pdf 12 | /html/[012]*.html 13 | /html/ejs.js 14 | /code/chapter/* 15 | /code/chapter_info.js 16 | /code/file_server.mjs 17 | /code/skillsharing.zip 18 | /code/solutions/20_3_a_public_space_on_the_web.zip 19 | /code/skillsharing/* 20 | /node_modules 21 | .tern-port 22 | /toc.txt 23 | /img/cover.xcf 24 | /img/generated/* 25 | /epub/[012]*.xhtml 26 | /epub/hints.xhtml 27 | /epub/img/* 28 | /epub/content.opf 29 | /epub/toc.xhtml 30 | /book.epub 31 | /book.mobi 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CHAPTERS := $(basename $(shell ls [0-9][0-9]_*.md) .md) 2 | 3 | SVGS := $(wildcard img/*.svg) 4 | 5 | all: html book.pdf book_mobile.pdf book.epub book.mobi 6 | 7 | html: $(foreach CHAP,$(CHAPTERS),html/$(CHAP).html) html/ejs.js \ 8 | code/skillsharing.zip code/solutions/20_3_a_public_space_on_the_web.zip html/code/chapter_info.js 9 | 10 | html/%.html: %.md src/render_html.mjs src/chapter.html 11 | node src/render_html.mjs $< > $@ 12 | node src/build_code.mjs $< 13 | 14 | html/code/chapter_info.js: $(foreach CHAP,$(CHAPTERS),$(CHAP).md) code/solutions/* src/chapter_info.mjs 15 | node src/chapter_info.mjs > html/code/chapter_info.js 16 | 17 | html/ejs.js: node_modules/codemirror/dist/index.js \ 18 | node_modules/@codemirror/view/dist/index.js \ 19 | node_modules/@codemirror/state/dist/index.js \ 20 | node_modules/@codemirror/language/dist/index.js \ 21 | node_modules/@codemirror/lang-html/dist/index.js \ 22 | node_modules/@codemirror/lang-javascript/dist/index.js \ 23 | node_modules/acorn/dist/acorn.js \ 24 | node_modules/acorn-walk/dist/walk.js \ 25 | src/client/*.mjs 26 | node_modules/.bin/rollup -c src/client/rollup.config.mjs 27 | 28 | code/skillsharing.zip: html/21_skillsharing.html code/skillsharing/package.json 29 | rm -f $@ 30 | cd code; zip skillsharing.zip skillsharing/*.mjs skillsharing/package.json skillsharing/public/*.* 31 | 32 | code/solutions/20_3_a_public_space_on_the_web.zip: $(wildcard code/solutions/20_3_a_public_space_on_the_web/*) 33 | rm -f $@ 34 | cd code/solutions; zip 20_3_a_public_space_on_the_web.zip 20_3_a_public_space_on_the_web/* 35 | 36 | test: html 37 | @for F in $(CHAPTERS); do echo Testing $$F:; node src/run_tests.mjs $$F.md; done 38 | @node src/check_links.mjs 39 | @echo Done. 40 | 41 | tex: $(foreach CHAP,$(CHAPTERS),pdf/$(CHAP).tex) pdf/hints.tex $(patsubst img/%.svg,img/generated/%.pdf,$(SVGS)) 42 | 43 | book.pdf: tex pdf/book.tex 44 | cd pdf && sh build.sh book > /dev/null 45 | mv pdf/book.pdf . 46 | 47 | pdf/book_mobile.tex: pdf/book.tex 48 | cat pdf/book.tex | sed -e 's/natbib}/natbib}\n\\usepackage[a5paper, left=5mm, right=5mm]{geometry}/' | sed -e 's/setmonofont.Scale=0.8./setmonofont[Scale=0.75]/' > pdf/book_mobile.tex 49 | 50 | book_mobile.pdf: pdf/book_mobile.tex tex 51 | cd pdf && sh build.sh book_mobile > /dev/null 52 | mv pdf/book_mobile.pdf . 53 | 54 | pdf/hints.tex: $(foreach CHAP,$(CHAPTERS),$(CHAP).md) src/extract_hints.mjs 55 | node src/extract_hints.mjs | node src/render_latex.mjs - > $@ 56 | 57 | img/generated/%.pdf: img/%.svg 58 | inkscape --export-pdf=$@ $< 59 | 60 | pdf/%.tex: %.md 61 | node src/render_latex.mjs $< > $@ 62 | 63 | book.epub: epub/titlepage.xhtml epub/toc.xhtml epub/hints.xhtml $(foreach CHAP,$(CHAPTERS),epub/$(CHAP).xhtml) \ 64 | epub/content.opf.src epub/style.css src/add_images_to_epub.mjs 65 | rm -f $@ 66 | grep ' $@ 73 | 74 | epub/%.xhtml: %.md src/render_html.mjs 75 | node src/render_html.mjs --epub $< > $@ 76 | 77 | epub/hints.xhtml: $(foreach CHAP,$(CHAPTERS),$(CHAP).md) src/extract_hints.mjs src/render_html.mjs 78 | node src/extract_hints.mjs | node src/render_html.mjs --epub - > $@ 79 | 80 | epubcheck: book.epub 81 | epubcheck book.epub 2>&1 | grep -v 'img/.*\.svg' 82 | 83 | book.mobi: book.epub img/cover.jpg 84 | ebook-convert book.epub book.mobi --output-profile=kindle --cover=img/cover.jpg --remove-first-image 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Eloquent JavaScript 2 | 3 | These are the sources used to build the fourth edition of Eloquent 4 | JavaScript (https://eloquentjavascript.net). 5 | 6 | Feedback welcome, in the form of issues and pull requests. 7 | 8 | ## Building 9 | 10 | This builds the HTML output in `html/`, where `make` is GNU make: 11 | 12 | npm install 13 | make html 14 | 15 | To build the PDF file (don't bother trying this unless you really need 16 | it, since this list has probably bitrotted again and getting all this 17 | set up is a pain): 18 | 19 | apt-get install texlive texlive-xetex fonts-inconsolata fonts-symbola texlive-lang-chinese inkscape 20 | make book.pdf 21 | 22 | ## Translating 23 | 24 | Translations are very much welcome. The license this book is published 25 | under allows non-commercial derivations, which includes open 26 | translations. If you do one, let me know, and I'll add a link to the 27 | website. 28 | 29 | A note of caution though: This text consists of about 130 000 words, 30 | the paper book is 400 pages. That's a lot of text, which will take a 31 | lot of time to translate. 32 | 33 | If that doesn't scare you off, the recommended way to go about a 34 | translation is: 35 | 36 | - Fork this repository on GitHub. 37 | 38 | - Create an issue on the repository describing your plan to translate. 39 | 40 | - Translate the `.md` files in your fork. These are 41 | [CommonMark](https://commonmark.org/) formatted, with a few 42 | extensions. You may consider omitting the index terms (indicated 43 | with double parentheses and `{{index ...}}` syntax) from your 44 | translation, since that's mostly relevant for print output. 45 | 46 | - Publish somewhere online or ask me to host the result. 47 | 48 | Doing this in public, and creating an issue that links to your work, 49 | helps avoid wasted effort, where multiple people start a translation 50 | to the same language (and possibly never finish it). (Since 51 | translations have to retain the license, it is okay to pick up someone 52 | else's translation and continue it, even when they have vanished from 53 | the internet.) 54 | 55 | I am not interested in machine translations. Please only ask me to 56 | link your translation when it was done by actual people. 57 | -------------------------------------------------------------------------------- /code/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2008-2024 by Marijn Haverbeke 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /code/_stop_keys.js: -------------------------------------------------------------------------------- 1 | window.addEventListener("keydown", e => { 2 | if (/Arrow|Home|End|Page/.test(e.key)) e.preventDefault() 3 | }) 4 | -------------------------------------------------------------------------------- /code/animatevillage.js: -------------------------------------------------------------------------------- 1 | // test: no 2 | 3 | (function() { 4 | "use strict" 5 | 6 | let active = null 7 | 8 | const places = { 9 | "Alice's House": {x: 279, y: 100}, 10 | "Bob's House": {x: 295, y: 203}, 11 | "Cabin": {x: 372, y: 67}, 12 | "Daria's House": {x: 183, y: 285}, 13 | "Ernie's House": {x: 50, y: 283}, 14 | "Farm": {x: 36, y: 118}, 15 | "Grete's House": {x: 35, y: 187}, 16 | "Marketplace": {x: 162, y: 110}, 17 | "Post Office": {x: 205, y: 57}, 18 | "Shop": {x: 137, y: 212}, 19 | "Town Hall": {x: 202, y: 213} 20 | } 21 | const placeKeys = Object.keys(places) 22 | 23 | const speed = 2 24 | 25 | class Animation { 26 | constructor(worldState, robot, robotState) { 27 | this.worldState = worldState 28 | this.robot = robot 29 | this.robotState = robotState 30 | this.turn = 0 31 | 32 | let outer = (window.__sandbox ? window.__sandbox.output.div : document.body), doc = outer.ownerDocument 33 | this.node = outer.appendChild(doc.createElement("div")) 34 | this.node.style.cssText = "position: relative; line-height: 0.1; margin-left: 10px" 35 | this.map = this.node.appendChild(doc.createElement("img")) 36 | this.imgPath = "img/" 37 | if (/\/code($|\/)/.test(outer.ownerDocument.defaultView.location)) this.imgPath = "../" + this.imgPath 38 | console.log(outer.ownerDocument.defaultView.location.toString(), /\/code($|\/)/.test(outer.ownerDocument.defaultView.localation), this.imgPath) 39 | this.map.src = this.imgPath + "village2x.png" 40 | this.map.style.cssText = "vertical-align: -8px" 41 | this.robotElt = this.node.appendChild(doc.createElement("div")) 42 | this.robotElt.style.cssText = `position: absolute; transition: left ${0.8 / speed}s, top ${0.8 / speed}s;` 43 | let robotPic = this.robotElt.appendChild(doc.createElement("img")) 44 | robotPic.src = this.imgPath + "robot_moving2x.gif" 45 | this.parcels = [] 46 | 47 | this.text = this.node.appendChild(doc.createElement("span")) 48 | this.button = this.node.appendChild(doc.createElement("button")) 49 | this.button.style.cssText = "color: white; background: #28b; border: none; border-radius: 2px; padding: 2px 5px; line-height: 1.1; font-family: sans-serif; font-size: 80%" 50 | this.button.textContent = "Stop" 51 | 52 | this.button.addEventListener("click", () => this.clicked()) 53 | this.schedule() 54 | 55 | this.updateView() 56 | this.updateParcels() 57 | 58 | this.robotElt.addEventListener("transitionend", () => this.updateParcels()) 59 | } 60 | 61 | 62 | updateView() { 63 | let pos = places[this.worldState.place] 64 | this.robotElt.style.top = (pos.y - 38) + "px" 65 | this.robotElt.style.left = (pos.x - 16) + "px" 66 | 67 | this.text.textContent = ` Turn ${this.turn} ` 68 | } 69 | 70 | updateParcels() { 71 | while (this.parcels.length) this.parcels.pop().remove() 72 | let heights = {} 73 | for (let {place, address} of this.worldState.parcels) { 74 | let height = heights[place] || (heights[place] = 0) 75 | heights[place] += 14 76 | let node = document.createElement("div") 77 | let offset = placeKeys.indexOf(address) * 16 78 | node.style.cssText = `position: absolute; height: 16px; width: 16px; background-image: url(${this.imgPath}parcel2x.png); background-position: 0 -${offset}px`; 79 | if (place == this.worldState.place) { 80 | node.style.left = "25px" 81 | node.style.bottom = (20 + height) + "px" 82 | this.robotElt.appendChild(node) 83 | } else { 84 | let pos = places[place] 85 | node.style.left = (pos.x - 5) + "px" 86 | node.style.top = (pos.y - 10 - height) + "px" 87 | this.node.appendChild(node) 88 | } 89 | this.parcels.push(node) 90 | } 91 | } 92 | 93 | tick() { 94 | let {direction, memory} = this.robot(this.worldState, this.robotState) 95 | this.worldState = this.worldState.move(direction) 96 | this.robotState = memory 97 | this.turn++ 98 | this.updateView() 99 | if (this.worldState.parcels.length == 0) { 100 | this.button.remove() 101 | this.text.textContent = ` Finished after ${this.turn} turns` 102 | this.robotElt.firstChild.src = this.imgPath + "robot_idle2x.png" 103 | } else { 104 | this.schedule() 105 | } 106 | } 107 | 108 | schedule() { 109 | this.timeout = setTimeout(() => this.tick(), 1000 / speed) 110 | } 111 | 112 | clicked() { 113 | if (this.timeout == null) { 114 | this.schedule() 115 | this.button.textContent = "Stop" 116 | this.robotElt.firstChild.src = this.imgPath + "robot_moving2x.gif" 117 | } else { 118 | clearTimeout(this.timeout) 119 | this.timeout = null 120 | this.button.textContent = "Start" 121 | this.robotElt.firstChild.src = this.imgPath + "robot_idle2x.png" 122 | } 123 | } 124 | } 125 | 126 | window.runRobotAnimation = function(worldState, robot, robotState) { 127 | if (active && active.timeout != null) 128 | clearTimeout(active.timeout) 129 | active = new Animation(worldState, robot, robotState) 130 | } 131 | })() 132 | -------------------------------------------------------------------------------- /code/chapter/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/code/chapter/.keep -------------------------------------------------------------------------------- /code/chapter/22_fast.js: -------------------------------------------------------------------------------- 1 | var Graph = class Graph { 2 | #nodes = []; 3 | 4 | get size() { 5 | return this.#nodes.length; 6 | } 7 | 8 | addNode() { 9 | let id = this.#nodes.length; 10 | this.#nodes.push(new Set()); 11 | return id; 12 | } 13 | 14 | addEdge(nodeA, nodeB) { 15 | this.#nodes[nodeA].add(nodeB); 16 | this.#nodes[nodeB].add(nodeA); 17 | } 18 | 19 | neighbors(node) { 20 | return this.#nodes[node]; 21 | } 22 | } 23 | 24 | function randomLayout(graph) { 25 | let layout = []; 26 | for (let i = 0; i < graph.size; i++) { 27 | layout.push(new Vec(Math.random() * 1000, 28 | Math.random() * 1000)); 29 | } 30 | return layout; 31 | } 32 | 33 | function gridGraph(size) { 34 | let grid = new Graph(); 35 | for (let y = 0; y < size; y++) { 36 | for (let x = 0; x < size; x++) { 37 | let id = grid.addNode(); 38 | if (x > 0) grid.addEdge(id, id - 1); 39 | if (y > 0) grid.addEdge(id, id - size); 40 | } 41 | } 42 | return grid; 43 | } 44 | 45 | var springLength = 20; 46 | var springStrength = 0.1; 47 | var repulsionStrength = 1500; 48 | 49 | function forceSize(distance, connected) { 50 | let repulse = -repulsionStrength / (distance * distance); 51 | let spring = 0; 52 | if (connected) { 53 | spring = (distance - springLength) * springStrength; 54 | } 55 | return spring + repulse; 56 | } 57 | 58 | function forceDirected_simple(layout, graph) { 59 | for (let a = 0; a < graph.size; a++) { 60 | for (let b = 0; b < graph.size; b++) { 61 | if (a == b) continue; 62 | let apart = layout[b].minus(layout[a]); 63 | let distance = Math.max(1, apart.length); 64 | let connected = graph.neighbors(a).has(b); 65 | let size = forceSize(distance, connected); 66 | let force = apart.times(1 / distance).times(size); 67 | layout[a] = layout[a].plus(force); 68 | } 69 | } 70 | } 71 | 72 | function pause() { 73 | return new Promise(done => setTimeout(done, 0)) 74 | } 75 | 76 | async function runLayout(implementation, graph) { 77 | let time = 0, iterations = 0; 78 | let layout = randomLayout(graph); 79 | while (time < 3000) { 80 | let start = Date.now(); 81 | for (let i = 0; i < 100; i++) { 82 | implementation(layout, graph); 83 | iterations++; 84 | } 85 | time += Date.now() - start; 86 | drawGraph(graph, layout); 87 | await pause(); 88 | } 89 | let perSecond = Math.round(iterations / (time / 1000)); 90 | console.log(`${perSecond} iterations per second`); 91 | } 92 | 93 | function forceDirected_noRepeat(layout, graph) { 94 | for (let a = 0; a < graph.size; a++) { 95 | for (let b = a + 1; b < graph.size; b++) { 96 | let apart = layout[b].minus(layout[a]); 97 | let distance = Math.max(1, apart.length); 98 | let connected = graph.neighbors(a).has(b); 99 | let size = forceSize(distance, connected); 100 | let force = apart.times(1 / distance).times(size); 101 | layout[a] = layout[a].plus(force); 102 | layout[b] = layout[b].minus(force); 103 | } 104 | } 105 | } 106 | 107 | var skipDistance = 175; 108 | 109 | function forceDirected_skip(layout, graph) { 110 | for (let a = 0; a < graph.size; a++) { 111 | for (let b = a + 1; b < graph.size; b++) { 112 | let apart = layout[b].minus(layout[a]); 113 | let distance = Math.max(1, apart.length); 114 | let connected = graph.neighbors(a).has(b); 115 | if (distance > skipDistance && !connected) continue; 116 | let size = forceSize(distance, connected); 117 | let force = apart.times(1 / distance).times(size); 118 | layout[a] = layout[a].plus(force); 119 | layout[b] = layout[b].minus(force); 120 | } 121 | } 122 | } 123 | 124 | function forceDirected_noVector(layout, graph) { 125 | for (let a = 0; a < graph.size; a++) { 126 | let posA = layout[a]; 127 | for (let b = a + 1; b < graph.size; b++) { 128 | let posB = layout[b]; 129 | let apartX = posB.x - posA.x 130 | let apartY = posB.y - posA.y; 131 | let distance = Math.sqrt(apartX * apartX + 132 | apartY * apartY); 133 | let connected = graph.neighbors(a).has(b); 134 | if (distance > skipDistance && !connected) continue; 135 | let size = forceSize(distance, connected); 136 | let forceX = (apartX / distance) * size; 137 | let forceY = (apartY / distance) * size; 138 | posA.x += forceX; 139 | posA.y += forceY; 140 | posB.x -= forceX; 141 | posB.y -= forceY; 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /code/draw_layout.js: -------------------------------------------------------------------------------- 1 | // The familiar Vec type. 2 | 3 | class Vec { 4 | constructor(x, y) { 5 | this.x = x; this.y = y; 6 | } 7 | plus(other) { 8 | return new Vec(this.x + other.x, this.y + other.y); 9 | } 10 | minus(other) { 11 | return new Vec(this.x - other.x, this.y - other.y); 12 | } 13 | times(factor) { 14 | return new Vec(this.x * factor, this.y * factor); 15 | } 16 | get length() { 17 | return Math.sqrt(this.x * this.x + this.y * this.y); 18 | } 19 | } 20 | 21 | // Since we will want to inspect the layouts our code produces, let's 22 | // first write code to draw a graph onto a canvas. Since we don't know 23 | // in advance how big the graph is, the `Scale` object computes a 24 | // scale and offset so that all nodes fit onto the given canvas. 25 | 26 | const nodeSize = 6; 27 | 28 | function drawGraph(graph, layout) { 29 | let parent = (window.__sandbox ? window.__sandbox.output.div : document.body); 30 | let canvas = parent.querySelector("canvas"); 31 | if (!canvas) { 32 | canvas = parent.appendChild(document.createElement("canvas")); 33 | canvas.width = canvas.height = 400; 34 | } 35 | let cx = canvas.getContext("2d"); 36 | 37 | cx.clearRect(0, 0, canvas.width, canvas.height); 38 | let scale = new Scale(layout, canvas.width, canvas.height); 39 | 40 | // Draw the edges. 41 | cx.strokeStyle = "orange"; 42 | cx.lineWidth = 3; 43 | for (let i = 0; i < layout.length; i++) { 44 | let conn = graph.neighbors(i); 45 | for (let target of conn) { 46 | if (conn <= i) continue; 47 | cx.beginPath(); 48 | cx.moveTo(scale.x(layout[i].x), scale.y(layout[i].y)); 49 | cx.lineTo(scale.x(layout[target].x), scale.y(layout[target].y)); 50 | cx.stroke(); 51 | } 52 | } 53 | 54 | // Draw the nodes. 55 | cx.fillStyle = "purple"; 56 | for (let pos of layout) { 57 | cx.beginPath(); 58 | cx.arc(scale.x(pos.x), scale.y(pos.y), nodeSize, 0, 7); 59 | cx.fill(); 60 | } 61 | } 62 | 63 | // The function starts by drawing the edges, so that they appear 64 | // behind the nodes. Since the nodes on _both_ side of an edge refer 65 | // to each other, and we don't want to draw every edge twice, edges 66 | // are only drawn then the target comes _after_ the current node in 67 | // the `graph` array. 68 | 69 | // When the edges have been drawn, the nodes are drawn on top of them 70 | // as purple discs. Remember that the last argument to `arc` gives the 71 | // rotation, and we have to pass something bigger than 2π to get a 72 | // full circle. 73 | 74 | // Finding a scale at which to draw the graph is done by finding the 75 | // top left and bottom right corners of the area taken up by the 76 | // nodes. The offset at which nodes are drawn is based on the top left 77 | // corner, and the scale is based on the size of the canvas divided by 78 | // the distance between those corners. The function reserves space 79 | // along the sides of the canvas based on the `nodeSize` variable, so 80 | // that the circles drawn around nodes’ center points don't get cut off. 81 | 82 | class Scale { 83 | constructor(layout, width, height) { 84 | let xs = layout.map(node => node.x); 85 | let ys = layout.map(node => node.y); 86 | let minX = Math.min(...xs); 87 | let minY = Math.min(...ys); 88 | let maxX = Math.max(...xs); 89 | let maxY = Math.max(...ys); 90 | 91 | this.offsetX = minX; this.offsetY = minY; 92 | this.scaleX = (width - 2 * nodeSize) / (maxX - minX); 93 | this.scaleY = (height - 2 * nodeSize) / (maxY - minY); 94 | } 95 | 96 | // The `x` and `y` methods convert from graph coordinates into 97 | // canvas coordinates. 98 | x(x) { 99 | return this.scaleX * (x - this.offsetX) + nodeSize; 100 | } 101 | y(y) { 102 | return this.scaleY * (y - this.offsetY) + nodeSize; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /code/hello.js: -------------------------------------------------------------------------------- 1 | alert("hello!"); 2 | -------------------------------------------------------------------------------- /code/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Eloquent JavaScript :: Code Sandbox 6 | 7 | 8 | 9 | 12 | 13 | 16 | 17 | 18 |
19 |

Code Sandbox
Eloquent JavaScript

20 | 21 |

You can use this page to download source code and solutions to 22 | exercises for the book Eloquent JavaScript, and to directly run code 23 | in the context of chapters from that book, either to solve exercises 24 | to simply play around.

25 | 26 |

27 | Chapter: 28 | 29 | 30 |

31 | 32 |
33 |
34 |
35 |
36 | 37 |
38 | To run this chapter's code locally, use these files: 39 |
    40 |
    41 | 42 |
    43 | These files contain this chapter’s project code: 44 |
      45 |
      46 | 47 |

      If you've solved the exercise and want to compare your code with 48 | mine, or you really tried, but can't get your code to work, 49 | you can (or download it).

      51 | 52 |

      53 | The base environment for this chapter (if any) is available in the 54 | sandbox above, allowing you to run the chapter's examples by 55 | simply pasting them into the editor. 56 |

      57 |
      58 | 59 | 60 | -------------------------------------------------------------------------------- /code/intro.js: -------------------------------------------------------------------------------- 1 | function range(start, end, step) { 2 | if (step == null) step = 1; 3 | var array = []; 4 | 5 | if (step > 0) { 6 | for (var i = start; i <= end; i += step) 7 | array.push(i); 8 | } else { 9 | for (var i = start; i >= end; i += step) 10 | array.push(i); 11 | } 12 | return array; 13 | } 14 | 15 | function sum(array) { 16 | var total = 0; 17 | for (var i = 0; i < array.length; i++) 18 | total += array[i]; 19 | return total; 20 | } 21 | 22 | function factorial(n) { 23 | if (n == 0) { 24 | return 1; 25 | } else { 26 | return factorial(n - 1) * n; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /code/load.js: -------------------------------------------------------------------------------- 1 | // Since the code for most chapter in Eloquent JavaScript isn't 2 | // written with node's module system in mind, this kludge is used to 3 | // load dependency files into the global namespace, so that the 4 | // examples can run on node. 5 | 6 | module.exports = function(...args) { 7 | for (let arg of args) 8 | (1,eval)(require("fs").readFileSync(__dirname + "/../" + arg, "utf8")) 9 | } 10 | -------------------------------------------------------------------------------- /code/skillsharing/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/code/skillsharing/.keep -------------------------------------------------------------------------------- /code/skillsharing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ejs-skillsharing", 3 | "version": "1.0.0", 4 | "main": "skillsharing_server.js", 5 | "description": "Skill-sharing website example from Eloquent JavaScript", 6 | "dependencies": { 7 | "serve-static": "^1.15.0" 8 | }, 9 | "license": "MIT", 10 | "bugs": "https://github.com/marijnh/Eloquent-JavaScript/issues", 11 | "homepage": "https://eloquentjavascript.net/21_skillsharing.html", 12 | "maintainers": [ 13 | { 14 | "name": "Marijn Haverbeke", 15 | "email": "marijn@haverbeke.berlin", 16 | "web": "https://marijnhaverbeke.nl/" 17 | } 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/marijnh/Eloquent-JavaScript.git" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /code/skillsharing/public/skillsharing.css: -------------------------------------------------------------------------------- 1 | .talk { margin: 40px 0; } 2 | 3 | .comment { font-style: italic; margin: 0; } 4 | .comment strong { font-style: normal; } 5 | 6 | .talk h2 { font-size: 130%; margin-bottom: 0; } 7 | .talk h2 button { vertical-align: bottom; } 8 | 9 | h1, h3 { margin-bottom: 0.33em; } 10 | 11 | label input { display: block; width: 30em; } 12 | -------------------------------------------------------------------------------- /code/solutions/02_1_looping_a_triangle.js: -------------------------------------------------------------------------------- 1 | for (let line = "#"; line.length < 8; line += "#") 2 | console.log(line); 3 | -------------------------------------------------------------------------------- /code/solutions/02_2_fizzbuzz.js: -------------------------------------------------------------------------------- 1 | for (let n = 1; n <= 100; n++) { 2 | let output = ""; 3 | if (n % 3 == 0) output += "Fizz"; 4 | if (n % 5 == 0) output += "Buzz"; 5 | console.log(output || n); 6 | } 7 | -------------------------------------------------------------------------------- /code/solutions/02_3_chessboard.js: -------------------------------------------------------------------------------- 1 | let size = 8; 2 | 3 | let board = ""; 4 | 5 | for (let y = 0; y < size; y++) { 6 | for (let x = 0; x < size; x++) { 7 | if ((x + y) % 2 == 0) { 8 | board += " "; 9 | } else { 10 | board += "#"; 11 | } 12 | } 13 | board += "\n"; 14 | } 15 | 16 | console.log(board); 17 | -------------------------------------------------------------------------------- /code/solutions/03_1_minimum.js: -------------------------------------------------------------------------------- 1 | function min(a, b) { 2 | if (a < b) return a; 3 | else return b; 4 | } 5 | 6 | console.log(min(0, 10)); 7 | // → 0 8 | console.log(min(0, -10)); 9 | // → -10 10 | -------------------------------------------------------------------------------- /code/solutions/03_2_recursion.js: -------------------------------------------------------------------------------- 1 | function isEven(n) { 2 | if (n == 0) return true; 3 | else if (n == 1) return false; 4 | else if (n < 0) return isEven(-n); 5 | else return isEven(n - 2); 6 | } 7 | 8 | console.log(isEven(50)); 9 | // → true 10 | console.log(isEven(75)); 11 | // → false 12 | console.log(isEven(-1)); 13 | // → false 14 | -------------------------------------------------------------------------------- /code/solutions/03_3_bean_counting.js: -------------------------------------------------------------------------------- 1 | function countChar(string, ch) { 2 | let counted = 0; 3 | for (let i = 0; i < string.length; i++) { 4 | if (string[i] == ch) { 5 | counted += 1; 6 | } 7 | } 8 | return counted; 9 | } 10 | 11 | function countBs(string) { 12 | return countChar(string, "B"); 13 | } 14 | 15 | console.log(countBs("BBC")); 16 | // → 2 17 | console.log(countChar("kakkerlak", "k")); 18 | // → 4 19 | -------------------------------------------------------------------------------- /code/solutions/04_1_the_sum_of_a_range.js: -------------------------------------------------------------------------------- 1 | function range(start, end, step = start < end ? 1 : -1) { 2 | let array = []; 3 | 4 | if (step > 0) { 5 | for (let i = start; i <= end; i += step) array.push(i); 6 | } else { 7 | for (let i = start; i >= end; i += step) array.push(i); 8 | } 9 | return array; 10 | } 11 | 12 | function sum(array) { 13 | let total = 0; 14 | for (let value of array) { 15 | total += value; 16 | } 17 | return total; 18 | } 19 | 20 | console.log(range(1, 10)) 21 | // → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 22 | console.log(range(5, 2, -1)); 23 | // → [5, 4, 3, 2] 24 | console.log(sum(range(1, 10))); 25 | // → 55 26 | -------------------------------------------------------------------------------- /code/solutions/04_2_reversing_an_array.js: -------------------------------------------------------------------------------- 1 | function reverseArray(array) { 2 | let output = []; 3 | for (let i = array.length - 1; i >= 0; i--) { 4 | output.push(array[i]); 5 | } 6 | return output; 7 | } 8 | 9 | function reverseArrayInPlace(array) { 10 | for (let i = 0; i < Math.floor(array.length / 2); i++) { 11 | let old = array[i]; 12 | array[i] = array[array.length - 1 - i]; 13 | array[array.length - 1 - i] = old; 14 | } 15 | return array; 16 | } 17 | 18 | console.log(reverseArray(["A", "B", "C"])); 19 | // → ["C", "B", "A"]; 20 | let arrayValue = [1, 2, 3, 4, 5]; 21 | reverseArrayInPlace(arrayValue); 22 | console.log(arrayValue); 23 | // → [5, 4, 3, 2, 1] 24 | -------------------------------------------------------------------------------- /code/solutions/04_3_a_list.js: -------------------------------------------------------------------------------- 1 | function arrayToList(array) { 2 | let list = null; 3 | for (let i = array.length - 1; i >= 0; i--) { 4 | list = {value: array[i], rest: list}; 5 | } 6 | return list; 7 | } 8 | 9 | function listToArray(list) { 10 | let array = []; 11 | for (let node = list; node; node = node.rest) { 12 | array.push(node.value); 13 | } 14 | return array; 15 | } 16 | 17 | function prepend(value, list) { 18 | return {value, rest: list}; 19 | } 20 | 21 | function nth(list, n) { 22 | if (!list) return undefined; 23 | else if (n == 0) return list.value; 24 | else return nth(list.rest, n - 1); 25 | } 26 | 27 | console.log(arrayToList([10, 20])); 28 | // → {value: 10, rest: {value: 20, rest: null}} 29 | console.log(listToArray(arrayToList([10, 20, 30]))); 30 | // → [10, 20, 30] 31 | console.log(prepend(10, prepend(20, null))); 32 | // → {value: 10, rest: {value: 20, rest: null}} 33 | console.log(nth(arrayToList([10, 20, 30]), 1)); 34 | // → 20 35 | -------------------------------------------------------------------------------- /code/solutions/04_4_deep_comparison.js: -------------------------------------------------------------------------------- 1 | function deepEqual(a, b) { 2 | if (a === b) return true; 3 | 4 | if (a == null || typeof a != "object" || 5 | b == null || typeof b != "object") return false; 6 | 7 | let keysA = Object.keys(a), keysB = Object.keys(b); 8 | 9 | if (keysA.length != keysB.length) return false; 10 | 11 | for (let key of keysA) { 12 | if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false; 13 | } 14 | 15 | return true; 16 | } 17 | 18 | let obj = {here: {is: "an"}, object: 2}; 19 | console.log(deepEqual(obj, obj)); 20 | // → true 21 | console.log(deepEqual(obj, {here: 1, object: 2})); 22 | // → false 23 | console.log(deepEqual(obj, {here: {is: "an"}, object: 2})); 24 | // → true 25 | -------------------------------------------------------------------------------- /code/solutions/05_1_flattening.js: -------------------------------------------------------------------------------- 1 | let arrays = [[1, 2, 3], [4, 5], [6]]; 2 | 3 | console.log(arrays.reduce((flat, current) => flat.concat(current), [])); 4 | // → [1, 2, 3, 4, 5, 6] 5 | -------------------------------------------------------------------------------- /code/solutions/05_2_your_own_loop.js: -------------------------------------------------------------------------------- 1 | function loop(start, test, update, body) { 2 | for (let value = start; test(value); value = update(value)) { 3 | body(value); 4 | } 5 | } 6 | 7 | loop(3, n => n > 0, n => n - 1, console.log); 8 | // → 3 9 | // → 2 10 | // → 1 11 | -------------------------------------------------------------------------------- /code/solutions/05_3_everything.js: -------------------------------------------------------------------------------- 1 | function every(array, predicate) { 2 | for (let element of array) { 3 | if (!predicate(element)) return false; 4 | } 5 | return true; 6 | } 7 | 8 | function every2(array, predicate) { 9 | return !array.some(element => !predicate(element)); 10 | } 11 | 12 | console.log(every([1, 3, 5], n => n < 10)); 13 | // → true 14 | console.log(every([2, 4, 16], n => n < 10)); 15 | // → false 16 | console.log(every([], n => n < 10)); 17 | // → true 18 | -------------------------------------------------------------------------------- /code/solutions/05_4_dominant_writing_direction.js: -------------------------------------------------------------------------------- 1 | function dominantDirection(text) { 2 | let counted = countBy(text, char => { 3 | let script = characterScript(char.codePointAt(0)); 4 | return script ? script.direction : "none"; 5 | }).filter(({name}) => name != "none"); 6 | 7 | if (counted.length == 0) return "ltr"; 8 | 9 | return counted.reduce((a, b) => a.count > b.count ? a : b).name; 10 | } 11 | 12 | console.log(dominantDirection("Hello!")); 13 | // → ltr 14 | console.log(dominantDirection("Hey, مساء الخير")); 15 | // → rtl 16 | -------------------------------------------------------------------------------- /code/solutions/06_1_a_vector_type.js: -------------------------------------------------------------------------------- 1 | class Vec { 2 | constructor(x, y) { 3 | this.x = x; 4 | this.y = y; 5 | } 6 | 7 | plus(other) { 8 | return new Vec(this.x + other.x, this.y + other.y); 9 | } 10 | 11 | minus(other) { 12 | return new Vec(this.x - other.x, this.y - other.y); 13 | } 14 | 15 | get length() { 16 | return Math.sqrt(this.x * this.x + this.y * this.y); 17 | } 18 | } 19 | 20 | console.log(new Vec(1, 2).plus(new Vec(2, 3))); 21 | // → Vec{x: 3, y: 5} 22 | console.log(new Vec(1, 2).minus(new Vec(2, 3))); 23 | // → Vec{x: -1, y: -1} 24 | console.log(new Vec(3, 4).length); 25 | // → 5 26 | -------------------------------------------------------------------------------- /code/solutions/06_2_groups.js: -------------------------------------------------------------------------------- 1 | class Group { 2 | #members = []; 3 | 4 | add(value) { 5 | if (!this.has(value)) { 6 | this.#members.push(value); 7 | } 8 | } 9 | 10 | delete(value) { 11 | this.#members = this.#members.filter(v => v !== value); 12 | } 13 | 14 | has(value) { 15 | return this.#members.includes(value); 16 | } 17 | 18 | static from(collection) { 19 | let group = new Group; 20 | for (let value of collection) { 21 | group.add(value); 22 | } 23 | return group; 24 | } 25 | } 26 | 27 | let group = Group.from([10, 20]); 28 | console.log(group.has(10)); 29 | // → true 30 | console.log(group.has(30)); 31 | // → false 32 | group.add(10); 33 | group.delete(10); 34 | console.log(group.has(10)); 35 | // → false 36 | -------------------------------------------------------------------------------- /code/solutions/06_3_iterable_groups.js: -------------------------------------------------------------------------------- 1 | class Group { 2 | #members = []; 3 | 4 | add(value) { 5 | if (!this.has(value)) { 6 | this.#members.push(value); 7 | } 8 | } 9 | 10 | delete(value) { 11 | this.#members = this.#members.filter(v => v !== value); 12 | } 13 | 14 | has(value) { 15 | return this.#members.includes(value); 16 | } 17 | 18 | static from(collection) { 19 | let group = new Group; 20 | for (let value of collection) { 21 | group.add(value); 22 | } 23 | return group; 24 | } 25 | 26 | [Symbol.iterator]() { 27 | return new GroupIterator(this.#members); 28 | } 29 | } 30 | 31 | class GroupIterator { 32 | #members; 33 | #position; 34 | 35 | constructor(members) { 36 | this.#members = members; 37 | this.#position = 0; 38 | } 39 | 40 | next() { 41 | if (this.#position >= this.#members.length) { 42 | return {done: true}; 43 | } else { 44 | let result = {value: this.#members[this.#position], 45 | done: false}; 46 | this.#position++; 47 | return result; 48 | } 49 | } 50 | } 51 | 52 | for (let value of Group.from(["a", "b", "c"])) { 53 | console.log(value); 54 | } 55 | // → a 56 | // → b 57 | // → c 58 | -------------------------------------------------------------------------------- /code/solutions/06_4_borrowing_a_method.js: -------------------------------------------------------------------------------- 1 | let map = {one: true, two: true, hasOwnProperty: true}; 2 | 3 | console.log(Object.prototype.hasOwnProperty.call(map, "one")); 4 | // → true 5 | -------------------------------------------------------------------------------- /code/solutions/07_1_measuring_a_robot.js: -------------------------------------------------------------------------------- 1 | function countSteps(state, robot, memory) { 2 | for (let steps = 0;; steps++) { 3 | if (state.parcels.length == 0) return steps; 4 | let action = robot(state, memory); 5 | state = state.move(action.direction); 6 | memory = action.memory; 7 | } 8 | } 9 | 10 | function compareRobots(robot1, memory1, robot2, memory2) { 11 | let total1 = 0, total2 = 0; 12 | for (let i = 0; i < 100; i++) { 13 | let state = VillageState.random(); 14 | total1 += countSteps(state, robot1, memory1); 15 | total2 += countSteps(state, robot2, memory2); 16 | } 17 | console.log(`Robot 1 needed ${total1 / 100} steps per task`) 18 | console.log(`Robot 2 needed ${total2 / 100}`) 19 | } 20 | 21 | compareRobots(routeRobot, [], goalOrientedRobot, []); 22 | -------------------------------------------------------------------------------- /code/solutions/07_2_robot_efficiency.js: -------------------------------------------------------------------------------- 1 | function lazyRobot({place, parcels}, route) { 2 | if (route.length == 0) { 3 | // Describe a route for every parcel 4 | let routes = parcels.map(parcel => { 5 | if (parcel.place != place) { 6 | return {route: findRoute(roadGraph, place, parcel.place), 7 | pickUp: true}; 8 | } else { 9 | return {route: findRoute(roadGraph, place, parcel.address), 10 | pickUp: false}; 11 | } 12 | }); 13 | 14 | // This determines the precedence a route gets when choosing. 15 | // Route length counts negatively, routes that pick up a package 16 | // get a small bonus. 17 | function score({route, pickUp}) { 18 | return (pickUp ? 0.5 : 0) - route.length; 19 | } 20 | route = routes.reduce((a, b) => score(a) > score(b) ? a : b).route; 21 | } 22 | 23 | return {direction: route[0], memory: route.slice(1)}; 24 | } 25 | 26 | runRobotAnimation(VillageState.random(), lazyRobot, []); 27 | -------------------------------------------------------------------------------- /code/solutions/07_3_persistent_group.js: -------------------------------------------------------------------------------- 1 | class PGroup { 2 | #members; 3 | constructor(members) { 4 | this.#members = members; 5 | } 6 | 7 | add(value) { 8 | if (this.has(value)) return this; 9 | return new PGroup(this.#members.concat([value])); 10 | } 11 | 12 | delete(value) { 13 | if (!this.has(value)) return this; 14 | return new PGroup(this.#members.filter(m => m !== value)); 15 | } 16 | 17 | has(value) { 18 | return this.#members.includes(value); 19 | } 20 | 21 | static empty = new PGroup([]); 22 | } 23 | 24 | let a = PGroup.empty.add("a"); 25 | let ab = a.add("b"); 26 | let b = ab.delete("a"); 27 | 28 | console.log(b.has("b")); 29 | // → true 30 | console.log(a.has("b")); 31 | // → false 32 | console.log(b.has("a")); 33 | // → false 34 | -------------------------------------------------------------------------------- /code/solutions/08_1_retry.js: -------------------------------------------------------------------------------- 1 | class MultiplicatorUnitFailure extends Error {} 2 | 3 | function primitiveMultiply(a, b) { 4 | if (Math.random() < 0.2) { 5 | return a * b; 6 | } else { 7 | throw new MultiplicatorUnitFailure("Klunk"); 8 | } 9 | } 10 | 11 | function reliableMultiply(a, b) { 12 | for (;;) { 13 | try { 14 | return primitiveMultiply(a, b); 15 | } catch (e) { 16 | if (!(e instanceof MultiplicatorUnitFailure)) 17 | throw e; 18 | } 19 | } 20 | } 21 | 22 | console.log(reliableMultiply(8, 8)); 23 | // → 64 24 | -------------------------------------------------------------------------------- /code/solutions/08_2_the_locked_box.js: -------------------------------------------------------------------------------- 1 | const box = new class { 2 | locked = true; 3 | #content = []; 4 | 5 | unlock() { this.locked = false; } 6 | lock() { this.locked = true; } 7 | get content() { 8 | if (this.locked) throw new Error("Locked!"); 9 | return this.#content; 10 | } 11 | }; 12 | 13 | function withBoxUnlocked(body) { 14 | let locked = box.locked; 15 | if (locked) box.unlock(); 16 | try { 17 | return body(); 18 | } finally { 19 | if (locked) box.lock(); 20 | } 21 | } 22 | 23 | withBoxUnlocked(() => { 24 | box.content.push("gold piece"); 25 | }); 26 | 27 | try { 28 | withBoxUnlocked(() => { 29 | throw new Error("Pirates on the horizon! Abort!"); 30 | }); 31 | } catch (e) { 32 | console.log("Error raised:", e); 33 | } 34 | 35 | console.log(box.locked); 36 | // → true 37 | -------------------------------------------------------------------------------- /code/solutions/09_1_regexp_golf.js: -------------------------------------------------------------------------------- 1 | // Fill in the regular expressions 2 | 3 | verify(/ca[rt]/, 4 | ["my car", "bad cats"], 5 | ["camper", "high art"]); 6 | 7 | verify(/pr?op/, 8 | ["pop culture", "mad props"], 9 | ["plop", "prrrop"]); 10 | 11 | verify(/ferr(et|y|ari)/, 12 | ["ferret", "ferry", "ferrari"], 13 | ["ferrum", "transfer A"]); 14 | 15 | verify(/ious($|\P{L})/u, 16 | ["how delicious", "spacious room"], 17 | ["ruinous", "consciousness"]); 18 | 19 | verify(/\s[.,:;]/, 20 | ["bad punctuation ."], 21 | ["escape the dot"]); 22 | 23 | verify(/\p{L}{7}/u, 24 | ["Siebentausenddreihundertzweiundzwanzig"], 25 | ["no", "three small words"]); 26 | 27 | verify(/(^|\P{L})[^\P{L}e]+($|\P{L})/ui, 28 | ["red platypus", "wobbling nest"], 29 | ["earth bed", "bedrøvet abe", "BEET"]); 30 | 31 | 32 | function verify(regexp, yes, no) { 33 | // Ignore unfinished exercises 34 | if (regexp.source == "...") return; 35 | for (let str of yes) if (!regexp.test(str)) { 36 | console.log(`Failure to match '${str}'`); 37 | } 38 | for (let str of no) if (regexp.test(str)) { 39 | console.log(`Unexpected match for '${str}'`); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /code/solutions/09_2_quoting_style.js: -------------------------------------------------------------------------------- 1 | let text = "'I'm the cook,' he said, 'it's my job.'"; 2 | 3 | console.log(text.replace(/(^|\P{L})'|'(\P{L}|$)/gu, '$1"$2')); 4 | // → "I'm the cook," he said, "it's my job." 5 | 6 | -------------------------------------------------------------------------------- /code/solutions/09_3_numbers_again.js: -------------------------------------------------------------------------------- 1 | // Fill in this regular expression. 2 | let number = /^[+\-]?(\d+(\.\d*)?|\.\d+)([eE][+\-]?\d+)?$/; 3 | 4 | // Tests: 5 | for (let str of ["1", "-1", "+15", "1.55", ".5", "5.", 6 | "1.3e2", "1E-4", "1e+12"]) { 7 | if (!number.test(str)) { 8 | console.log(`Failed to match '${str}'`); 9 | } 10 | } 11 | for (let str of ["1a", "+-1", "1.2.3", "1+1", "1e4.5", 12 | ".5.", "1f5", "."]) { 13 | if (number.test(str)) { 14 | console.log(`Incorrectly accepted '${str}'`); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /code/solutions/10_2_roads_module.js: -------------------------------------------------------------------------------- 1 | import {buildGraph} from "./graph"; 2 | 3 | const roads = [ 4 | "Alice's House-Bob's House", "Alice's House-Cabin", 5 | "Alice's House-Post Office", "Bob's House-Town Hall", 6 | "Daria's House-Ernie's House", "Daria's House-Town Hall", 7 | "Ernie's House-Grete's House", "Grete's House-Farm", 8 | "Grete's House-Shop", "Marketplace-Farm", 9 | "Marketplace-Post Office", "Marketplace-Shop", 10 | "Marketplace-Town Hall", "Shop-Town Hall" 11 | ]; 12 | 13 | export const roadGraph = buildGraph(roads.map(r => r.split("-"))); 14 | -------------------------------------------------------------------------------- /code/solutions/11_1_quiet_times.js: -------------------------------------------------------------------------------- 1 | async function activityTable(day) { 2 | let table = []; 3 | for (let i = 0; i < 24; i++) table[i] = 0; 4 | 5 | let logFileList = await textFile("camera_logs.txt"); 6 | for (let filename of logFileList.split("\n")) { 7 | let log = await textFile(filename); 8 | for (let timestamp of log.split("\n")) { 9 | let date = new Date(Number(timestamp)); 10 | if (date.getDay() == day) { 11 | table[date.getHours()]++; 12 | } 13 | } 14 | } 15 | 16 | return table; 17 | } 18 | 19 | activityTable(1) 20 | .then(table => console.log(activityGraph(table))); 21 | -------------------------------------------------------------------------------- /code/solutions/11_1_tracking_the_scalpel.js: -------------------------------------------------------------------------------- 1 | async function locateScalpel(nest) { 2 | let current = nest.name; 3 | for (;;) { 4 | let next = await anyStorage(nest, current, "scalpel"); 5 | if (next == current) return current; 6 | current = next; 7 | } 8 | } 9 | 10 | function locateScalpel2(nest) { 11 | function loop(current) { 12 | return anyStorage(nest, current, "scalpel").then(next => { 13 | if (next == current) return current; 14 | else return loop(next); 15 | }); 16 | } 17 | return loop(nest.name); 18 | } 19 | 20 | locateScalpel(bigOak).then(console.log); 21 | // → Butcher's Shop 22 | locateScalpel2(bigOak).then(console.log); 23 | // → Butcher's Shop 24 | -------------------------------------------------------------------------------- /code/solutions/11_2_real_promises.js: -------------------------------------------------------------------------------- 1 | function activityTable(day) { 2 | let table = []; 3 | for (let i = 0; i < 24; i++) table[i] = 0; 4 | 5 | return textFile("camera_logs.txt").then(files => { 6 | return Promise.all(files.split("\n").map(name => { 7 | return textFile(name).then(log => { 8 | for (let timestamp of log.split("\n")) { 9 | let date = new Date(Number(timestamp)); 10 | if (date.getDay() == day) { 11 | table[date.getHours()]++; 12 | } 13 | } 14 | }); 15 | })); 16 | }).then(() => table); 17 | } 18 | 19 | activityTable(6) 20 | .then(table => console.log(activityGraph(table))); 21 | -------------------------------------------------------------------------------- /code/solutions/11_3_building_promiseall.js: -------------------------------------------------------------------------------- 1 | function Promise_all(promises) { 2 | return new Promise((resolve, reject) => { 3 | let results = []; 4 | let pending = promises.length; 5 | for (let i = 0; i < promises.length; i++) { 6 | promises[i].then(result => { 7 | results[i] = result; 8 | pending--; 9 | if (pending == 0) resolve(results); 10 | }).catch(reject); 11 | } 12 | if (promises.length == 0) resolve(results); 13 | }); 14 | } 15 | 16 | // Test code. 17 | Promise_all([]).then(array => { 18 | console.log("This should be []:", array); 19 | }); 20 | function soon(val) { 21 | return new Promise(resolve => { 22 | setTimeout(() => resolve(val), Math.random() * 500); 23 | }); 24 | } 25 | Promise_all([soon(1), soon(2), soon(3)]).then(array => { 26 | console.log("This should be [1, 2, 3]:", array); 27 | }); 28 | Promise_all([soon(1), Promise.reject("X"), soon(3)]).then(array => { 29 | console.log("We should not get here"); 30 | }).catch(error => { 31 | if (error != "X") { 32 | console.log("Unexpected failure:", error); 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /code/solutions/12_1_arrays.js: -------------------------------------------------------------------------------- 1 | topScope.array = (...values) => values; 2 | 3 | topScope.length = array => array.length; 4 | 5 | topScope.element = (array, i) => array[i]; 6 | 7 | run(` 8 | do(define(sum, fun(array, 9 | do(define(i, 0), 10 | define(sum, 0), 11 | while(<(i, length(array)), 12 | do(define(sum, +(sum, element(array, i))), 13 | define(i, +(i, 1)))), 14 | sum))), 15 | print(sum(array(1, 2, 3)))) 16 | `); 17 | // → 6 18 | -------------------------------------------------------------------------------- /code/solutions/12_3_comments.js: -------------------------------------------------------------------------------- 1 | function skipSpace(string) { 2 | let skippable = string.match(/^(\s|#.*)*/); 3 | return string.slice(skippable[0].length); 4 | } 5 | 6 | console.log(parse("# hello\nx")); 7 | // → {type: "word", name: "x"} 8 | 9 | console.log(parse("a # one\n # two\n()")); 10 | // → {type: "apply", 11 | // operator: {type: "word", name: "a"}, 12 | // args: []} 13 | -------------------------------------------------------------------------------- /code/solutions/12_4_fixing_scope.js: -------------------------------------------------------------------------------- 1 | specialForms.set = (args, env) => { 2 | if (args.length != 2 || args[0].type != "word") { 3 | throw new SyntaxError("Bad use of set"); 4 | } 5 | let varName = args[0].name; 6 | let value = evaluate(args[1], env); 7 | 8 | for (let scope = env; scope; scope = Object.getPrototypeOf(scope)) { 9 | if (Object.hasOwn(scope, varName)) { 10 | scope[varName] = value; 11 | return value; 12 | } 13 | } 14 | throw new ReferenceError(`Setting undefined variable ${varName}`); 15 | }; 16 | 17 | run(` 18 | do(define(x, 4), 19 | define(setx, fun(val, set(x, val))), 20 | setx(50), 21 | print(x)) 22 | `); 23 | // → 50 24 | run(`set(quux, true)`); 25 | // → Some kind of ReferenceError 26 | -------------------------------------------------------------------------------- /code/solutions/14_1_build_a_table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

      Mountains

      5 | 6 |
      7 | 8 | 50 | -------------------------------------------------------------------------------- /code/solutions/14_2_elements_by_tag_name.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

      Heading with a span element.

      4 |

      A paragraph with one, two 5 | spans.

      6 | 7 | 34 | -------------------------------------------------------------------------------- /code/solutions/14_3_the_cats_hat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 28 | -------------------------------------------------------------------------------- /code/solutions/15_1_balloon.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

      🎈

      4 | 5 | 30 | -------------------------------------------------------------------------------- /code/solutions/15_2_mouse_trail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | 33 | 34 | -------------------------------------------------------------------------------- /code/solutions/15_3_tabs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
      Tab one
      5 |
      Tab two
      6 |
      Tab three
      7 |
      8 | 34 | -------------------------------------------------------------------------------- /code/solutions/16_1_game_over.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 29 | 30 | -------------------------------------------------------------------------------- /code/solutions/16_2_pausing_the_game.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 93 | 94 | -------------------------------------------------------------------------------- /code/solutions/16_3_a_monster.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 62 | 63 | -------------------------------------------------------------------------------- /code/solutions/17_1_shapes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 67 | -------------------------------------------------------------------------------- /code/solutions/17_2_the_pie_chart.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 41 | -------------------------------------------------------------------------------- /code/solutions/17_3_a_bouncing_ball.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 37 | -------------------------------------------------------------------------------- /code/solutions/18_1_content_negotiation.js: -------------------------------------------------------------------------------- 1 | const url = "https://eloquentjavascript.net/author"; 2 | const types = ["text/plain", 3 | "text/html", 4 | "application/json", 5 | "application/rainbows+unicorns"]; 6 | 7 | async function showTypes() { 8 | for (let type of types) { 9 | let resp = await fetch(url, {headers: {accept: type}}); 10 | console.log(`${type}: ${await resp.text()}\n`); 11 | } 12 | } 13 | 14 | showTypes(); 15 | -------------------------------------------------------------------------------- /code/solutions/18_2_a_javascript_workbench.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
      
       6 | 
       7 | 
      19 | 
      
      
      --------------------------------------------------------------------------------
      /code/solutions/18_3_conways_game_of_life.html:
      --------------------------------------------------------------------------------
       1 | 
       2 | 
       3 | 
      4 | 5 | 6 | 7 | 90 | -------------------------------------------------------------------------------- /code/solutions/19_1_keyboard_bindings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
      7 | 53 | -------------------------------------------------------------------------------- /code/solutions/19_2_efficient_drawing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
      7 | 38 | -------------------------------------------------------------------------------- /code/solutions/19_3_circles.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
      7 | 35 | -------------------------------------------------------------------------------- /code/solutions/19_4_proper_lines.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
      7 | 50 | -------------------------------------------------------------------------------- /code/solutions/20_1_search_tool.mjs: -------------------------------------------------------------------------------- 1 | import {statSync, readdirSync, readFileSync} from "node:fs"; 2 | 3 | let searchTerm = new RegExp(process.argv[2]); 4 | 5 | for (let arg of process.argv.slice(3)) { 6 | search(arg); 7 | } 8 | 9 | function search(file) { 10 | let stats = statSync(file); 11 | if (stats.isDirectory()) { 12 | for (let f of readdirSync(file)) { 13 | search(file + "/" + f); 14 | } 15 | } else if (searchTerm.test(readFileSync(file, "utf8"))) { 16 | console.log(file); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /code/solutions/20_2_directory_creation.mjs: -------------------------------------------------------------------------------- 1 | // This code won't work on its own, but is also included in the 2 | // code/file_server.js file, which defines the whole system. 3 | 4 | import {mkdir} from "node:fs/promises"; 5 | 6 | methods.MKCOL = async function(request) { 7 | let path = urlPath(request.url); 8 | let stats; 9 | try { 10 | stats = await stat(path); 11 | } catch (error) { 12 | if (error.code != "ENOENT") throw error; 13 | await mkdir(path); 14 | return {status: 204}; 15 | } 16 | if (stats.isDirectory()) return {status: 204}; 17 | else return {status: 400, body: "Not a directory"}; 18 | }; 19 | -------------------------------------------------------------------------------- /code/solutions/20_3_a_public_space_on_the_web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

      A Public Space on the Web

      5 | 6 |

      This is a self-editing website. Select a file, edit it, and save to 7 | update the website.

      8 | 9 |

      Files:

      10 | 11 |

      12 | 13 |
      14 | 15 |

      16 | 17 | 18 | -------------------------------------------------------------------------------- /code/solutions/20_3_a_public_space_on_the_web/other.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

      This is another file

      5 | -------------------------------------------------------------------------------- /code/solutions/20_3_a_public_space_on_the_web/public_space.js: -------------------------------------------------------------------------------- 1 | // Get a reference to the DOM nodes we need 2 | let filelist = document.querySelector("#filelist"); 3 | let textarea = document.querySelector("#file"); 4 | 5 | // This loads the initial file list from the server 6 | fetch("/").then(resp => resp.text()).then(files => { 7 | for (let file of files.split("\n")) { 8 | let option = document.createElement("option"); 9 | option.textContent = file; 10 | filelist.appendChild(option); 11 | } 12 | // Now that we have a list of files, make sure the textarea contains 13 | // the currently selected one. 14 | loadCurrentFile(); 15 | }); 16 | 17 | // Fetch a file from the server and put it in the textarea. 18 | function loadCurrentFile() { 19 | fetch(filelist.value).then(resp => resp.text()).then(file => { 20 | textarea.value = file; 21 | }); 22 | } 23 | 24 | filelist.addEventListener("change", loadCurrentFile); 25 | 26 | // Called by the button on the page. Makes a request to save the 27 | // currently selected file. 28 | function saveFile() { 29 | fetch(filelist.value, {method: "PUT", 30 | body: textarea.value}); 31 | } 32 | -------------------------------------------------------------------------------- /code/solutions/21_1_disk_persistence.mjs: -------------------------------------------------------------------------------- 1 | // This isn't a stand-alone file, only a redefinition of a few 2 | // fragments from skillsharing/skillsharing_server.js 3 | 4 | import {readFileSync, writeFile} from "node:fs"; 5 | 6 | const fileName = "./talks.json"; 7 | 8 | SkillShareServer.prototype.updated = function() { 9 | this.version++; 10 | let response = this.talkResponse(); 11 | this.waiting.forEach(resolve => resolve(response)); 12 | this.waiting = []; 13 | 14 | writeFile(fileName, JSON.stringify(this.talks), e => { 15 | if (e) throw e; 16 | }); 17 | }; 18 | 19 | function loadTalks() { 20 | try { 21 | return JSON.parse(readFileSync(fileName, "utf8")); 22 | } catch (e) { 23 | return {}; 24 | } 25 | } 26 | 27 | // The line that starts the server must be changed to 28 | new SkillShareServer(loadTalks()).start(8000); 29 | -------------------------------------------------------------------------------- /code/solutions/21_2_comment_field_resets.mjs: -------------------------------------------------------------------------------- 1 | // This isn't a stand-alone file, only a redefinition of the main 2 | // component from skillsharing/public/skillsharing_client.js 3 | 4 | class Talk { 5 | constructor(talk, dispatch) { 6 | this.comments = elt("div"); 7 | this.dom = elt( 8 | "section", {className: "talk"}, 9 | elt("h2", null, talk.title, " ", elt("button", { 10 | type: "button", 11 | onclick: () => dispatch({type: "deleteTalk", 12 | talk: talk.title}) 13 | }, "Delete")), 14 | elt("div", null, "by ", 15 | elt("strong", null, talk.presenter)), 16 | elt("p", null, talk.summary), 17 | this.comments, 18 | elt("form", { 19 | onsubmit(event) { 20 | event.preventDefault(); 21 | let form = event.target; 22 | dispatch({type: "newComment", 23 | talk: talk.title, 24 | message: form.elements.comment.value}); 25 | form.reset(); 26 | } 27 | }, elt("input", {type: "text", name: "comment"}), " ", 28 | elt("button", {type: "submit"}, "Add comment"))); 29 | this.syncState(talk); 30 | } 31 | 32 | syncState(talk) { 33 | this.talk = talk; 34 | this.comments.textContent = ""; 35 | for (let comment of talk.comments) { 36 | this.comments.appendChild(renderComment(comment)); 37 | } 38 | } 39 | } 40 | 41 | class SkillShareApp { 42 | constructor(state, dispatch) { 43 | this.dispatch = dispatch; 44 | this.talkDOM = elt("div", {className: "talks"}); 45 | this.talkMap = Object.create(null); 46 | this.dom = elt("div", null, 47 | renderUserField(state.user, dispatch), 48 | this.talkDOM, 49 | renderTalkForm(dispatch)); 50 | this.syncState(state); 51 | } 52 | 53 | syncState(state) { 54 | if (state.talks == this.talks) return; 55 | this.talks = state.talks; 56 | 57 | for (let talk of state.talks) { 58 | let found = this.talkMap[talk.title]; 59 | if (found && found.talk.presenter == talk.presenter && 60 | found.talk.summary == talk.summary) { 61 | found.syncState(talk); 62 | } else { 63 | if (found) found.dom.remove(); 64 | found = new Talk(talk, this.dispatch); 65 | this.talkMap[talk.title] = found; 66 | this.talkDOM.appendChild(found.dom); 67 | } 68 | } 69 | for (let title of Object.keys(this.talkMap)) { 70 | if (!state.talks.some(talk => talk.title == title)) { 71 | this.talkMap[title].dom.remove(); 72 | delete this.talkMap[title]; 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /code/solutions/22_1_pathfinding.js: -------------------------------------------------------------------------------- 1 | function findPath(a, b) { 2 | let work = [[a]]; 3 | for (let path of work) { 4 | let end = path[path.length - 1]; 5 | if (end == b) return path; 6 | for (let next of end.edges) { 7 | if (!work.some(path => path[path.length - 1] == next)) { 8 | work.push(path.concat([next])); 9 | } 10 | } 11 | } 12 | } 13 | 14 | let graph = treeGraph(4, 4); 15 | let root = graph[0], leaf = graph[graph.length - 1]; 16 | console.log(findPath(root, leaf).length); 17 | // → 4 18 | 19 | leaf.connect(root); 20 | console.log(findPath(root, leaf).length); 21 | // → 2 22 | -------------------------------------------------------------------------------- /code/solutions/22_1_prime_numbers.js: -------------------------------------------------------------------------------- 1 | function* primes() { 2 | for (let n = 2;; n++) { 3 | let skip = false; 4 | for (let d = 2; d < n; d++) { 5 | if (n % d == 0) { 6 | skip = true; 7 | break; 8 | } 9 | } 10 | if (!skip) yield n; 11 | } 12 | } 13 | 14 | function measurePrimes() { 15 | let iter = primes(), t0 = Date.now(); 16 | for (let i = 0; i < 10000; i++) { 17 | iter.next(); 18 | } 19 | console.log(`Took ${Date.now() - t0}ms`); 20 | } 21 | 22 | measurePrimes(); 23 | -------------------------------------------------------------------------------- /code/solutions/22_2_faster_prime_numbers.js: -------------------------------------------------------------------------------- 1 | function* primes() { 2 | let found = []; 3 | for (let n = 2;; n++) { 4 | let skip = false, root = Math.sqrt(n); 5 | for (let prev of found) { 6 | if (prev > root) { 7 | break; 8 | } else if (n % prev == 0) { 9 | skip = true; 10 | break; 11 | } 12 | } 13 | if (!skip) { 14 | found.push(n); 15 | yield n; 16 | } 17 | } 18 | } 19 | 20 | function measurePrimes() { 21 | let iter = primes(), t0 = Date.now(); 22 | for (let i = 0; i < 10000; i++) { 23 | iter.next(); 24 | } 25 | console.log(`Took ${Date.now() - t0}ms`); 26 | } 27 | 28 | measurePrimes(); 29 | -------------------------------------------------------------------------------- /code/solutions/22_2_timing.js: -------------------------------------------------------------------------------- 1 | function findPath(a, b) { 2 | let work = [[a]]; 3 | for (let path of work) { 4 | let end = path[path.length - 1]; 5 | if (end == b) return path; 6 | for (let next of end.edges) { 7 | if (!work.some(path => path[path.length - 1] == next)) { 8 | work.push(path.concat([next])); 9 | } 10 | } 11 | } 12 | } 13 | 14 | function time(findPath) { 15 | let graph = treeGraph(6, 6); 16 | let startTime = Date.now(); 17 | let result = findPath(graph[0], graph[graph.length - 1]); 18 | console.log(`Path with length ${result.length} found in ${Date.now() - startTime}ms`); 19 | } 20 | time(findPath); 21 | -------------------------------------------------------------------------------- /code/solutions/22_3_optimizing.js: -------------------------------------------------------------------------------- 1 | function time(findPath) { 2 | let graph = treeGraph(6, 6); 3 | let startTime = Date.now(); 4 | let result = findPath(graph[0], graph[graph.length - 1]); 5 | console.log(`Path with length ${result.length} found in ${Date.now() - startTime}ms`); 6 | } 7 | 8 | function findPath_set(a, b) { 9 | let work = [[a]]; 10 | let reached = new Set([a]); 11 | for (let path of work) { 12 | let end = path[path.length - 1]; 13 | if (end == b) return path; 14 | for (let next of end.edges) { 15 | if (!reached.has(next)) { 16 | reached.add(next); 17 | work.push(path.concat([next])); 18 | } 19 | } 20 | } 21 | } 22 | 23 | time(findPath_set); 24 | 25 | function pathToArray(path) { 26 | let result = []; 27 | for (; path; path = path.via) result.unshift(path.at); 28 | return result; 29 | } 30 | 31 | function findPath_list(a, b) { 32 | let work = [{at: a, via: null}]; 33 | let reached = new Set([a]); 34 | for (let path of work) { 35 | if (path.at == b) return pathToArray(path); 36 | for (let next of path.at.edges) { 37 | if (!reached.has(next)) { 38 | reached.add(next); 39 | work.push({at: next, via: path}); 40 | } 41 | } 42 | } 43 | } 44 | 45 | time(findPath_list); 46 | -------------------------------------------------------------------------------- /code/squareworker.js: -------------------------------------------------------------------------------- 1 | addEventListener("message", event => { 2 | postMessage(event.data * event.data); 3 | }); 4 | -------------------------------------------------------------------------------- /epub/META-INF/container.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /epub/content.opf.src: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Eloquent JavaScript 5 | main 6 | Marijn Haverbeke 7 | HAVERBEKE, MARIJN 8 | aut 9 | net.eloquentjavascript 10 | en-US 11 | This work is shared with the public using the Attribution-NonCommercial 3.0 Unported (CC BY-NC 3.0) license. 12 | 13 | https://eloquentjavascript.net/ 14 | 2018-02-25T22:07:00Z 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | {{images}} 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /epub/font/cinzel_bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/epub/font/cinzel_bold.otf -------------------------------------------------------------------------------- /epub/font/pt_mono.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/epub/font/pt_mono.otf -------------------------------------------------------------------------------- /epub/frontmatter.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Eloquent JavaScript 6 | 9 | 10 | 11 |

      Eloquent JavaScript

      12 |

      4th edition

      13 |

      Written by Marijn Haverbeke.

      14 | 15 |

      Licensed under 16 | a Creative 17 | Commons attribution-noncommercial license. All code in this book 18 | may also be considered licensed under 19 | an MIT license.

      20 | 21 |

      Illustrations by various artists: Cover 22 | by Péchane Sumi-e. Chapter illustrations by Madalina Tantareanu. 23 | Pixel art in Chapters 7 and 16 by Antonio Perdomo Pastor. Regular 24 | expression diagrams in Chapter 9 generated 25 | with regexper.com by Jeff 26 | Avallone. Game concept for Chapter 16 27 | by Thomas Palef.

      28 | 29 |

      A paper version of Eloquent JavaScript, including a bonus 30 | chapter, is being brought out 31 | by No Starch Press. They also 32 | sell a more polished EPUB version that includes the bonus 33 | chapter.

      34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /epub/mimetype: -------------------------------------------------------------------------------- 1 | application/epub+zip -------------------------------------------------------------------------------- /epub/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Cinzel'; 3 | font-style: normal; 4 | font-weight: 700; 5 | src: url(font/cinzel_bold.otf); 6 | } 7 | 8 | @font-face { 9 | font-family: 'PT Mono'; 10 | font-style: normal; 11 | font-weight: 400; 12 | src: url(font/pt_mono.otf); 13 | } 14 | 15 | body { 16 | font-family: Georgia, 'Nimbus Roman No9 L', 'Century Schoolbook L', serif; 17 | font-size: 100%; 18 | line-height: 1.45; 19 | color: black; 20 | background: white; 21 | text-align: left; 22 | } 23 | 24 | article { 25 | padding: 2em 0 5em 0; 26 | } 27 | 28 | pre { 29 | padding: 5px 0 5px 15px; 30 | line-height: 1.35; 31 | margin: 1rem 0; 32 | position: relative; 33 | font-size: 90%; 34 | white-space: pre-wrap; 35 | overflow-wrap: break-word; 36 | } 37 | 38 | code, pre { 39 | font-family: 'PT Mono', monospace; 40 | } 41 | 42 | code { 43 | padding: 0 2px; 44 | } 45 | 46 | h1, h2, h3 { 47 | font-family: 'Cinzel', Georgia, serif; 48 | font-weight: 700; 49 | margin: 1rem 0; 50 | letter-spacing: 2px; 51 | } 52 | 53 | h1 { 54 | font-size: 130%; 55 | } 56 | h2 { 57 | font-size: 115%; 58 | } 59 | h3 { 60 | font-size: 100%; 61 | } 62 | 63 | div.chap_num { 64 | font-family: 'Cinzel', Georgia, serif; 65 | margin-bottom: -0.8rem; 66 | } 67 | 68 | blockquote { 69 | margin: 0 0 0 3em; 70 | padding: 0; 71 | font-size: 85%; 72 | } 73 | 74 | blockquote p { 75 | color: #333; 76 | } 77 | 78 | blockquote p:first-of-type:before { 79 | content: '“'; 80 | } 81 | 82 | blockquote p:last-of-type:after { 83 | content: '”'; 84 | } 85 | 86 | p + footer { 87 | margin-top: -.5em; 88 | } 89 | 90 | blockquote footer cite { 91 | font-style: italic; 92 | } 93 | 94 | blockquote footer:before { 95 | content: '—'; 96 | } 97 | 98 | figure img { 99 | max-width: 80%; 100 | margin-left: 30px; 101 | } 102 | 103 | figure.chapter { 104 | text-align: center; 105 | margin: 3em 0 2em; 106 | } 107 | 108 | figure.chapter img { 109 | max-width: 75%; 110 | } 111 | 112 | figure.framed img { 113 | border-radius: 50%; 114 | border: 2px solid black; 115 | } 116 | 117 | span.keyname { font-variant: small-caps } 118 | 119 | td { 120 | vertical-align: top; 121 | } 122 | 123 | td + td { 124 | padding-left: 1em; 125 | } 126 | 127 | table { 128 | margin-left: 15px; 129 | } 130 | 131 | sub, sup { 132 | line-height: 1; 133 | } 134 | 135 | sub { 136 | font-size: 60%; 137 | } 138 | sup { 139 | font-size: 70%; 140 | } 141 | 142 | ol li p { 143 | margin: 0; 144 | } 145 | 146 | /* Syntax highlighting */ 147 | .cm-keyword {color: #506;} 148 | .cm-atom {color: #106;} 149 | .cm-number {color: #042;} 150 | .cm-def {color: #009;} 151 | .cm-variable-2, .cm-attribute {color: #027;} 152 | .cm-variable-3 {color: #072;} 153 | .cm-comment {color: #740;} 154 | .cm-string {color: #700;} 155 | .cm-string-2 {color: #740;} 156 | .cm-tag {color: #170;} 157 | .cm-keyword {color: #708;} 158 | .cm-atom {color: #219;} 159 | .cm-number {color: #164;} 160 | .cm-def {color: #00f;} 161 | -------------------------------------------------------------------------------- /epub/titlepage.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Cover 5 | 6 | 10 | 11 | 12 |
      13 | Eloquent JavaScript 3rd edition 14 |
      15 | 16 | 17 | -------------------------------------------------------------------------------- /epub/toc.xhtml.src: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Table of Contents 5 | 6 | 37 | 38 | 39 | 40 |

      Table of Contents

      41 | 42 | 43 |
        44 |
      1. Introduction
      2. 45 |
      3. 46 | (Part 1: Language)Values, Types, and Operators 47 |
      4. 48 |
      5. Program Structure
      6. 49 |
      7. Functions
      8. 50 |
      9. Data Structures: Objects and Arrays
      10. 51 |
      11. Higher-order Functions
      12. 52 |
      13. The Secret Life of Objects
      14. 53 |
      15. Project: A Robot
      16. 54 |
      17. Bugs and Errors
      18. 55 |
      19. Regular Expressions
      20. 56 |
      21. Modules
      22. 57 |
      23. Asynchronous Programming
      24. 58 |
      25. Project: A Programming Language
      26. 59 |
      27. 60 | (Part 2: Browser)JavaScript and the Browser 61 |
      28. 62 |
      29. The Document Object Model
      30. 63 |
      31. Handling Events
      32. 64 |
      33. Project: A Platform Game
      34. 65 |
      35. Drawing on Canvas
      36. 66 |
      37. HTTP and Forms
      38. 67 |
      39. Project: A Pixel Art Editor
      40. 68 |
      41. 69 | (Part 3: Node)Node.js 70 |
      42. 71 |
      43. Project: Skill-Sharing Website
      44. 72 |
      45. Hints to the exercises
      46. 73 |
      74 | 75 | 76 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /html/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/html/.keep -------------------------------------------------------------------------------- /html/Eloquent_JavaScript.epub: -------------------------------------------------------------------------------- 1 | ../book.epub -------------------------------------------------------------------------------- /html/Eloquent_JavaScript.mobi: -------------------------------------------------------------------------------- 1 | ../book.mobi -------------------------------------------------------------------------------- /html/Eloquent_JavaScript.pdf: -------------------------------------------------------------------------------- 1 | ../book.pdf -------------------------------------------------------------------------------- /html/Eloquent_JavaScript_small.pdf: -------------------------------------------------------------------------------- 1 | ../book_mobile.pdf -------------------------------------------------------------------------------- /html/author.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
      4 |

      Marijn Haverbeke, Programmer

      6 | 7 |

      You can reach me at marijn@haverbeke.nl, or visit my web page, marijnhaverbeke.nl.

      10 |
      11 | -------------------------------------------------------------------------------- /html/author.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Marijn Haverbeke", 3 | "email": "marijn@haverbeke.nl", 4 | "website": "https://marijnhaverbeke.nl/" 5 | } 6 | -------------------------------------------------------------------------------- /html/author.txt: -------------------------------------------------------------------------------- 1 | My name is Marijn Haverbeke. You can email me at marijn@haverbeke.nl, or visit my website, https://marijnhaverbeke.nl/ . 2 | -------------------------------------------------------------------------------- /html/code: -------------------------------------------------------------------------------- 1 | ../code -------------------------------------------------------------------------------- /html/css/game.css: -------------------------------------------------------------------------------- 1 | .background { background: rgb(52, 166, 251); 2 | table-layout: fixed; 3 | border-spacing: 0; } 4 | .background td { padding: 0; } 5 | .lava { background: rgb(255, 100, 100); } 6 | .wall { background: white; } 7 | 8 | .actor { position: absolute; } 9 | .coin { background: rgb(241, 229, 89); } 10 | .player { background: rgb(64, 64, 64); } 11 | 12 | .game { 13 | overflow: hidden; 14 | max-width: 600px; 15 | max-height: 450px; 16 | position: relative; 17 | } 18 | 19 | .lost .player { 20 | background: rgb(160, 64, 64); 21 | } 22 | .won .player { 23 | box-shadow: -4px -7px 8px white, 4px -7px 8px white; 24 | } 25 | -------------------------------------------------------------------------------- /html/css/paint.css: -------------------------------------------------------------------------------- 1 | .picturepanel { 2 | width: -webkit-fit-content; 3 | width: -moz-fit-content; 4 | width: -ms-fit-content; 5 | width: fit-content; 6 | max-width: 500px; 7 | max-height: 300px; 8 | border: 2px solid silver; 9 | overflow: auto; 10 | position: relative; 11 | } 12 | .picturepanel canvas { display: block; } 13 | .toolbar > * { margin-right: 5px; } 14 | -------------------------------------------------------------------------------- /html/empty.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /html/errata.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Eloquent JavaScript :: Errata 6 | 7 | 8 | 10 | 11 | 12 |
      13 | 14 |

      Errata
      Eloquent JavaScript, 4th Edition

      15 | 16 |

      These are the known mistakes in the fourth edition 17 | of the book. For errata in other editions, see these pages: first, second, third. To report a problem that is not listed 18 | here, send me an email.

      19 | 20 |

      No issues found yet.

      21 | 22 | 34 | 35 |
      36 | -------------------------------------------------------------------------------- /html/example/bert.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bert", 3 | "spouse": "example/suzie.json" 4 | } 5 | -------------------------------------------------------------------------------- /html/example/data.txt: -------------------------------------------------------------------------------- 1 | This is the content of data.txt 2 | -------------------------------------------------------------------------------- /html/example/fruit.json: -------------------------------------------------------------------------------- 1 | {"banana": "yellow", 2 | "lemon": "yellow", 3 | "cherry": "red"} 4 | -------------------------------------------------------------------------------- /html/example/fruit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /html/example/message.html: -------------------------------------------------------------------------------- 1 | 2 | Example form target 3 | 4 | 5 | 6 | 10 | 13 | 14 |

      (Note that this page is just an illustration. No actual message was delivered anywhere.)

      15 | 16 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /html/example/muriel.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Muriel" 3 | } 4 | -------------------------------------------------------------------------------- /html/example/submit.html: -------------------------------------------------------------------------------- 1 | 2 | Example form target 3 | 4 | 5 | 6 |

      You submitted...

      7 | 8 |
      
       9 | 
      10 | 
      21 | 
      22 | 
      23 | 
      
      
      --------------------------------------------------------------------------------
      /html/example/suzie.json:
      --------------------------------------------------------------------------------
      1 | {
      2 |   "name": "Suzie",
      3 |   "spouse": "example/bert.json",
      4 |   "mother": "example/muriel.json"
      5 | }
      6 | 
      
      
      --------------------------------------------------------------------------------
      /html/favicon.ico:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/html/favicon.ico
      
      
      --------------------------------------------------------------------------------
      /html/font/cinzel_bold.woff:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/html/font/cinzel_bold.woff
      
      
      --------------------------------------------------------------------------------
      /html/font/pt_mono.woff:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/html/font/pt_mono.woff
      
      
      --------------------------------------------------------------------------------
      /html/img:
      --------------------------------------------------------------------------------
      1 | ../img/
      
      
      --------------------------------------------------------------------------------
      /img/Hieres-sur-Amby.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/Hieres-sur-Amby.png
      
      
      --------------------------------------------------------------------------------
      /img/blockquote.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/blockquote.png
      
      
      --------------------------------------------------------------------------------
      /img/boxed-in.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/boxed-in.png
      
      
      --------------------------------------------------------------------------------
      /img/button_disabled.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/button_disabled.png
      
      
      --------------------------------------------------------------------------------
      /img/canvas_beziercurve.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_beziercurve.png
      
      
      --------------------------------------------------------------------------------
      /img/canvas_circle.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_circle.png
      
      
      --------------------------------------------------------------------------------
      /img/canvas_fill.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_fill.png
      
      
      --------------------------------------------------------------------------------
      /img/canvas_game.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_game.png
      
      
      --------------------------------------------------------------------------------
      /img/canvas_path.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_path.png
      
      
      --------------------------------------------------------------------------------
      /img/canvas_pie_chart.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_pie_chart.png
      
      
      --------------------------------------------------------------------------------
      /img/canvas_quadraticcurve.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_quadraticcurve.png
      
      
      --------------------------------------------------------------------------------
      /img/canvas_scale.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_scale.png
      
      
      --------------------------------------------------------------------------------
      /img/canvas_stroke.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_stroke.png
      
      
      --------------------------------------------------------------------------------
      /img/canvas_tree.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_tree.png
      
      
      --------------------------------------------------------------------------------
      /img/canvas_triangle.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/canvas_triangle.png
      
      
      --------------------------------------------------------------------------------
      /img/cat-animation.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/cat-animation.png
      
      
      --------------------------------------------------------------------------------
      /img/cat.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/cat.png
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_00.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_00.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_1.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_1.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_10.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_10.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_11.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_11.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_12.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_12.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_13.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_13.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_14.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_14.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_15.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_15.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_16.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_16.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_17.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_17.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_18.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_18.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_19.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_19.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_2.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_2.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_20.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_20.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_21.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_21.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_3.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_3.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_4.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_4.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_5.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_5.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_6.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_6.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_7.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_7.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_8.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_8.jpg
      
      
      --------------------------------------------------------------------------------
      /img/chapter_picture_9.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/chapter_picture_9.jpg
      
      
      --------------------------------------------------------------------------------
      /img/color-field.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/color-field.png
      
      
      --------------------------------------------------------------------------------
      /img/colored-links.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/colored-links.png
      
      
      --------------------------------------------------------------------------------
      /img/control-io.svg:
      --------------------------------------------------------------------------------
       1 | 
       2 | 
      10 | synchronous, single thread of controlsynchronous, two threads of controlasynchronous
      11 | 
      
      
      --------------------------------------------------------------------------------
      /img/controlflow-if.svg:
      --------------------------------------------------------------------------------
       1 | 
       2 |    
       3 |       
       4 |          
       5 |       
       6 |    
       7 |    
       9 |    
      11 |    
      13 |    
      15 |    
      16 | 
      
      
      --------------------------------------------------------------------------------
      /img/controlflow-loop.svg:
      --------------------------------------------------------------------------------
       1 | 
       2 |    
       3 |       
       4 |          
       5 |       
       6 |    
       7 |    
      10 |    
      12 |    
      14 |    
      15 | 
      
      
      --------------------------------------------------------------------------------
      /img/controlflow-nested-if.svg:
      --------------------------------------------------------------------------------
       1 | 
       2 |    
       3 |       
       4 |          
       5 |       
       6 |    
       7 |    
       9 |    
      11 |    
      13 |    
      15 |    
      17 |    
      19 |    
      21 | 
      
      
      --------------------------------------------------------------------------------
      /img/controlflow-straight.svg:
      --------------------------------------------------------------------------------
      1 | 
      2 |    
      3 |       
      4 |          
      5 |       
      6 |    
      7 |    
      9 | 
      
      
      --------------------------------------------------------------------------------
      /img/cos_sin.svg:
      --------------------------------------------------------------------------------
       1 | 
       2 | 
       3 |   
      12 |   
      13 |   
      14 |   
      15 |   
      16 |   
      17 |   
      18 |   
      19 |   
      20 |   
      21 |   
      22 |   
      23 |   cos(¼π)
      24 |   sin(¼π)
      25 |   
      26 |   
      27 |   
      28 |   
      29 |   
      30 |   cos(-⅔π)
      31 |   sin(-⅔π)
      32 |   sin(-⅔π)
      33 | 
      34 | 
      
      
      --------------------------------------------------------------------------------
      /img/cover.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/cover.jpg
      
      
      --------------------------------------------------------------------------------
      /img/darkblue.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/darkblue.png
      
      
      --------------------------------------------------------------------------------
      /img/display.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/display.png
      
      
      --------------------------------------------------------------------------------
      /img/drag-bar.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/drag-bar.png
      
      
      --------------------------------------------------------------------------------
      /img/exercise_shapes.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/exercise_shapes.png
      
      
      --------------------------------------------------------------------------------
      /img/flood-grid.svg:
      --------------------------------------------------------------------------------
      1 | 
      2 | 
      
      
      --------------------------------------------------------------------------------
      /img/form_fields.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/form_fields.png
      
      
      --------------------------------------------------------------------------------
      /img/form_select.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/form_select.png
      
      
      --------------------------------------------------------------------------------
      /img/game-grid.svg:
      --------------------------------------------------------------------------------
      1 | 
      2 | 
      
      
      --------------------------------------------------------------------------------
      /img/game_simpleLevel.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/game_simpleLevel.png
      
      
      --------------------------------------------------------------------------------
      /img/generated/.keep:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/generated/.keep
      
      
      --------------------------------------------------------------------------------
      /img/hat.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/hat.png
      
      
      --------------------------------------------------------------------------------
      /img/help-field.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/help-field.png
      
      
      --------------------------------------------------------------------------------
      /img/highlighted.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/highlighted.png
      
      
      --------------------------------------------------------------------------------
      /img/holberton.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/holberton.png
      
      
      --------------------------------------------------------------------------------
      /img/home-page.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/home-page.png
      
      
      --------------------------------------------------------------------------------
      /img/html-boxes.svg:
      --------------------------------------------------------------------------------
       1 | 
       2 | 
      23 | herea.I also wrote a book! Read itpHello, I am Marijn and this is...pMy home pageh1bodyMy home pagetitleheadhtml
      24 | 
      
      
      --------------------------------------------------------------------------------
      /img/html-links.svg:
      --------------------------------------------------------------------------------
       1 | 
       2 | 
      25 |   I also wrote a book! ...
      26 |   p
      27 |   
      28 |   Hello, I am Marijn...
      29 |   p
      30 |   
      31 |   My home page
      32 |   h1
      33 |   
      34 |   body
      35 |   
      36 |   0
      37 |   1
      38 |   2
      39 |   
      40 |   
      41 |   
      42 |   
      43 |   
      44 |   
      45 |   
      46 |   
      47 |   
      48 |   childNodes
      49 |   
      50 |   
      51 |   firstChild
      52 |   
      53 |   
      54 |   lastChild
      55 |   
      56 |   
      57 |   previousSibling
      58 |   
      59 |   
      60 |   nextSibling
      61 |   
      62 |   
      63 |   parentNode
      64 | 
      65 | 
      
      
      --------------------------------------------------------------------------------
      /img/html-tree.svg:
      --------------------------------------------------------------------------------
       1 | 
       2 | 
      26 | htmlheadtitleMy home pagebodyh1My home pagepHello! I am...pI also wrote...herea.
      27 | 
      
      
      --------------------------------------------------------------------------------
      /img/line-grid.svg:
      --------------------------------------------------------------------------------
      1 | 
      2 | 
      
      
      --------------------------------------------------------------------------------
      /img/linked-list.svg:
      --------------------------------------------------------------------------------
       1 | 
       2 | 
      13 | value: 1rest:value: 2rest:value: 3rest: null
      14 | 
      
      
      --------------------------------------------------------------------------------
      /img/middle_east_graph.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/middle_east_graph.png
      
      
      --------------------------------------------------------------------------------
      /img/middle_east_graph_random.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/middle_east_graph_random.png
      
      
      --------------------------------------------------------------------------------
      /img/mirror.svg:
      --------------------------------------------------------------------------------
       1 | 
       2 | 
      11 | mirror1234
      12 | 
      
      
      --------------------------------------------------------------------------------
      /img/nextjournal.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/nextjournal.png
      
      
      --------------------------------------------------------------------------------
      /img/object.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/object.jpg
      
      
      --------------------------------------------------------------------------------
      /img/object_full.jpg:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/object_full.jpg
      
      
      --------------------------------------------------------------------------------
      /img/ostrich.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/ostrich.png
      
      
      --------------------------------------------------------------------------------
      /img/parcel2x.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/parcel2x.png
      
      
      --------------------------------------------------------------------------------
      /img/pixel_editor.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/pixel_editor.png
      
      
      --------------------------------------------------------------------------------
      /img/pizza-squirrel.svg:
      --------------------------------------------------------------------------------
       1 | 
       2 | 
       3 |   
       4 |     
       5 |     
       6 |     
       7 |   
       8 |   
       9 |     
      10 |     
      11 |     
      12 |     
      13 |     
      14 |     
      15 |     
      16 |   
      17 | 
      18 | No squirrel, no pizza76Squirrel, no pizza4No squirrel, pizza9Squirrel, pizza1
      
      
      --------------------------------------------------------------------------------
      /img/player.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/player.png
      
      
      --------------------------------------------------------------------------------
      /img/player_big.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/player_big.png
      
      
      --------------------------------------------------------------------------------
      /img/prompt.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/prompt.png
      
      
      --------------------------------------------------------------------------------
      /img/rabbits.svg:
      --------------------------------------------------------------------------------
       1 | 
       2 | 
      13 | toString: <function>...teeth: "small"speak: <function>killerRabbitteeth: "long, sharp, ..."type: "killer"RabbitprototypeObjectcreate: <function>prototype...
      14 | 
      
      
      --------------------------------------------------------------------------------
      /img/re_number.svg:
      --------------------------------------------------------------------------------
       1 | Start of linegroup #1One of:01bOne of:digit-afhdigitEnd of line
      16 | 
      
      
      --------------------------------------------------------------------------------
      /img/re_pigchickens.svg:
      --------------------------------------------------------------------------------
       1 | digit group #1pigcowchickens
      18 | 
      
      
      --------------------------------------------------------------------------------
      /img/robot_idle.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/robot_idle.png
      
      
      --------------------------------------------------------------------------------
      /img/robot_idle2x.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/robot_idle2x.png
      
      
      --------------------------------------------------------------------------------
      /img/robot_moving.gif:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/robot_moving.gif
      
      
      --------------------------------------------------------------------------------
      /img/robot_moving2x.gif:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/robot_moving2x.gif
      
      
      --------------------------------------------------------------------------------
      /img/skillsharing.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/skillsharing.png
      
      
      --------------------------------------------------------------------------------
      /img/sprites.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/sprites.png
      
      
      --------------------------------------------------------------------------------
      /img/sprites_big.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/sprites_big.png
      
      
      --------------------------------------------------------------------------------
      /img/svg-demo.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/svg-demo.png
      
      
      --------------------------------------------------------------------------------
      /img/syntax_tree.svg:
      --------------------------------------------------------------------------------
       1 | 
       2 | 
      11 | dodefinex10if>x5print"large"print"small"
      
      
      --------------------------------------------------------------------------------
      /img/tamil.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/tamil.png
      
      
      --------------------------------------------------------------------------------
      /img/transform.svg:
      --------------------------------------------------------------------------------
       1 | 
       2 | 
      11 | translate(50, 50)rotate(0.1*Math.PI)rotate(0.1*Math.PI)translate(50, 50)
      12 | 
      
      
      --------------------------------------------------------------------------------
      /img/tree_graph.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/tree_graph.png
      
      
      --------------------------------------------------------------------------------
      /img/village.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/village.png
      
      
      --------------------------------------------------------------------------------
      /img/village2x.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/village2x.png
      
      
      --------------------------------------------------------------------------------
      /img/weresquirrel.png:
      --------------------------------------------------------------------------------
      https://raw.githubusercontent.com/marijnh/Eloquent-JavaScript/63141ec8295797cb89fbb2571c434f04b2ee4360/img/weresquirrel.png
      
      
      --------------------------------------------------------------------------------
      /package.json:
      --------------------------------------------------------------------------------
       1 | {
       2 |   "name": "Eloquent-JavaScript",
       3 |   "license": "CC BY-NC 3.0",
       4 |   "version": "0.1.0",
       5 |   "author": "Marijn Haverbeke ",
       6 |   "description": "Sources for the book Eloquent JavaScript",
       7 |   "repository": {
       8 |     "type": "git",
       9 |     "url": "git://github.com/marijnh/Eloquent-JavaScript.git"
      10 |   },
      11 |   "dependencies": {
      12 |     "rollup": "^3.28.0",
      13 |     "@rollup/plugin-node-resolve": "^15.0.0",
      14 |     "@rollup/plugin-terser": "^0.4.4",
      15 |     "acorn": "^8.0.0",
      16 |     "acorn-walk": "^8.0.0",
      17 |     "@codemirror/view": "^6.20.0",
      18 |     "@codemirror/state": "^6.2.0",
      19 |     "@codemirror/language": "^6.9.0",
      20 |     "@codemirror/lang-css": "^6.2.0",
      21 |     "@codemirror/lang-html": "^6.4.0",
      22 |     "@codemirror/lang-javascript": "^6.2.0",
      23 |     "@codemirror/legacy-modes": "^6.3.0",
      24 |     "codemirror": "^6.0.0",
      25 |     "@lezer/common": "^1.1.0",
      26 |     "@lezer/highlight": "^1.1.0",
      27 |     "jszip": "^3.10.0",
      28 |     "markdown-it": "^14.0.0",
      29 |     "markdown-it-sub": "^2.0.0",
      30 |     "markdown-it-sup": "^2.0.0",
      31 |     "mime": "^2.3.1",
      32 |     "mold-template": "^2.0.1"
      33 |   },
      34 |   "devDependencies": {
      35 |     "jsdom": "^20.0.0",
      36 |     "promise": "^8.0.1"
      37 |   },
      38 |   "scripts": {
      39 |     "test": "make test"
      40 |   }
      41 | }
      42 | 
      
      
      --------------------------------------------------------------------------------
      /pdf/book.tex:
      --------------------------------------------------------------------------------
        1 | \documentclass[fontsize=13pt,oneside,headings=small,chapterprefix]{scrbook}
        2 | \usepackage{listings}
        3 | \usepackage{graphicx}
        4 | \PassOptionsToPackage{hyphens}{url}\usepackage{hyperref}
        5 | \usepackage{natbib}
        6 | \usepackage{scrhack}
        7 | \usepackage{charter}
        8 | \usepackage{bookmark}
        9 | \usepackage{ucharclasses}
       10 | \usepackage{fontspec}
       11 | \usepackage{xcolor}
       12 | \usepackage{pdfpages}
       13 | \usepackage{arabxetex}
       14 | \usepackage{makeidx}
       15 | 
       16 | % epigraph is used to style chapter quotes
       17 | \usepackage{epigraph}
       18 | \setlength{\epigraphwidth}{.8\textwidth}
       19 | \setlength{\epigraphrule}{0pt}
       20 | 
       21 | \lstset{basicstyle=\ttfamily,xleftmargin=0.8em,breaklines=true,lineskip=-0.2em,aboveskip=0.8em,belowskip=0.8em}
       22 | \renewcommand*{\chapterheadstartvskip}{\vspace*{3cm}}
       23 | \date{}
       24 | 
       25 | \lstset{escapeinside={$<}{>$}}
       26 | 
       27 | \makeatletter
       28 | \lst@InputCatcodes
       29 | \def\lst@DefEC{%
       30 |  \lst@CCECUse \lst@ProcessLetter
       31 |   ^^80^^81^^82^^83^^84^^85^^86^^87^^88^^89^^8a^^8b^^8c^^8d^^8e^^8f%
       32 |   ^^90^^91^^92^^93^^94^^95^^96^^97^^98^^99^^9a^^9b^^9c^^9d^^9e^^9f%
       33 |   ^^a0^^a1^^a2^^a3^^a4^^a5^^a6^^a7^^a8^^a9^^aa^^ab^^ac^^ad^^ae^^af%
       34 |   ^^b0^^b1^^b2^^b3^^b4^^b5^^b6^^b7^^b8^^b9^^ba^^bb^^bc^^bd^^be^^bf%
       35 |   ^^c0^^c1^^c2^^c3^^c4^^c5^^c6^^c7^^c8^^c9^^ca^^cb^^cc^^cd^^ce^^cf%
       36 |   ^^d0^^d1^^d2^^d3^^d4^^d5^^d6^^d7^^d8^^d9^^da^^db^^dc^^dd^^de^^df%
       37 |   ^^e0^^e1^^e2^^e3^^e4^^e5^^e6^^e7^^e8^^e9^^ea^^eb^^ec^^ed^^ee^^ef%
       38 |   ^^f0^^f1^^f2^^f3^^f4^^f5^^f6^^f7^^f8^^f9^^fa^^fb^^fc^^fd^^fe^^ff%
       39 |   тявΧαίρετΑΊΡΤΕ“”
       40 |   ^^00}
       41 | \lst@RestoreCatcodes
       42 | \makeatother
       43 | 
       44 | \setcounter{secnumdepth}{0}
       45 | \setcounter{tocdepth}{1}
       46 | \setmonofont[Scale=0.8]{Inconsolata LGC}
       47 | \defaultfontfeatures[\emojifont]{Scale=1.15}
       48 | \newfontface{\emojifont}{Symbola_hint.ttf}
       49 | \newfontfamily{\cjkfont}{TW-Sung}
       50 | \setTransitionsFor{MiscellaneousSymbolsAndPictographs}{\emojifont}{\normalfont}{}
       51 | 
       52 | \newfontfamily{\cinzel}{Cinzel}
       53 | \setkomafont{disposition}{\bfseries\cinzel}
       54 | \definecolor{silver-chalice}{HTML}{AAAAAA}
       55 | \setkomafont{chapterprefix}{\small\color{silver-chalice}}
       56 | \RedeclareSectionCommand[innerskip=0pt]{chapter}
       57 | 
       58 | \pagestyle{plain}
       59 | 
       60 | \usepackage{newunicodechar}
       61 | \newunicodechar{π}{$\pi$}
       62 | \newunicodechar{ϕ}{$\varphi$}
       63 | \newunicodechar{≈}{$\approx$}
       64 | \newunicodechar{β}{\ss}
       65 | \newunicodechar{⮪}{\emojifont{⮪}}
       66 | 
       67 | \graphicspath{{../}}
       68 | \definecolor{coveryellow}{rgb}{0.997,0.840,0.122}
       69 | \definecolor{blue-bayoux}{rgb}{0.267,0.4,0.467}
       70 | \hypersetup{colorlinks,linkcolor=blue-bayoux,urlcolor=black}
       71 | 
       72 | \makeindex
       73 | 
       74 | \begin{document}
       75 | 
       76 | \pagecolor{coveryellow}
       77 | \includepdf{../img/cover.jpg}
       78 | \pagecolor{white}
       79 | 
       80 | \author{Marijn Haverbeke}
       81 | 
       82 | \title{Eloquent JavaScript}
       83 | 
       84 | \subtitle{4th edition}
       85 | 
       86 | \maketitle
       87 | 
       88 | \frontmatter
       89 | 
       90 |   \noindent Copyright \textcopyright{} 2024 by Marijn Haverbeke
       91 | 
       92 |   \vskip 1em
       93 | 
       94 |   \noindent This work is licensed under a Creative Commons
       95 |   attribution-noncommercial license
       96 |   (\url{http://creativecommons.org/licenses/by-nc/3.0/}). All code in
       97 |   the book may also be considered licensed under an MIT license
       98 |   (\url{https://eloquentjavascript.net/code/LICENSE}).
       99 | 
      100 |   The illustrations are contributed by various artists: Cover by
      101 |   Péchane Sumi-e. Chapter illustrations by Madalina Tantareanu. Pixel
      102 |   art in Chapters 7 and 16 by Antonio Perdomo Pastor. Regular
      103 |   expression diagrams in Chapter 9 generated with
      104 |   \href{http://regexper.com}{regexper.com} by Jeff Avallone. Game
      105 |   concept for Chapter 16 by \href{http://lessmilk.com}{Thomas Palef}.
      106 | 
      107 |   \vskip 1em
      108 | 
      109 |   \noindent You can buy a print version of this book, with an extra
      110 |   bonus chapter included, printed by No Starch Press at
      111 |   \url{http://a-fwd.com/com=marijhaver-20&asin-com=1593279507}.
      112 | 
      113 | {
      114 |   \hypersetup{hidelinks}
      115 |   \tableofcontents
      116 | }
      117 | 
      118 | \mainmatter
      119 | 
      120 | \input{00_intro.tex}
      121 | 
      122 | \input{01_values.tex}
      123 | 
      124 | \input{02_program_structure.tex}
      125 | 
      126 | \input{03_functions.tex}
      127 | 
      128 | \input{04_data.tex}
      129 | 
      130 | \input{05_higher_order.tex}
      131 | 
      132 | \input{06_object.tex}
      133 | 
      134 | \input{07_robot.tex}
      135 | 
      136 | \input{08_error.tex}
      137 | 
      138 | \input{09_regexp.tex}
      139 | 
      140 | \input{10_modules.tex}
      141 | 
      142 | \input{11_async.tex}
      143 | 
      144 | \input{12_language.tex}
      145 | 
      146 | \input{13_browser.tex}
      147 | 
      148 | \input{14_dom.tex}
      149 | 
      150 | \input{15_event.tex}
      151 | 
      152 | \input{16_game.tex}
      153 | 
      154 | \input{17_canvas.tex}
      155 | 
      156 | \input{18_http.tex}
      157 | 
      158 | \input{19_paint.tex}
      159 | 
      160 | \input{20_node.tex}
      161 | 
      162 | \input{21_skillsharing.tex}
      163 | 
      164 | \input{hints.tex}
      165 | 
      166 | \backmatter
      167 | 
      168 | {
      169 |   \hypersetup{hidelinks}
      170 |   \printindex
      171 | }
      172 | 
      173 | \end{document}
      174 | 
      
      
      --------------------------------------------------------------------------------
      /pdf/build.sh:
      --------------------------------------------------------------------------------
      1 | xelatex $1.tex
      2 | xelatex $1.tex
      3 | makeindex -o $1.ind $1.idx
      4 | makeindex -o $1.ind $1.idx
      5 | xelatex $1.tex
      6 | while ( grep -q '^LaTeX Warning: Label(s) may have changed' $1.log) \
      7 | do xelatex $1.tex; done
      8 | 
      
      
      --------------------------------------------------------------------------------
      /src/add_images_to_epub.mjs:
      --------------------------------------------------------------------------------
       1 | import {readdirSync, lstatSync, readFileSync, writeFileSync} from "fs"
       2 | import * as path from "path"
       3 | 
       4 | let images = []
       5 | function scanDir(dir) {
       6 |   for (let file of readdirSync(dir)) {
       7 |     let full = path.resolve(dir, file), type
       8 |     if (lstatSync(full).isDirectory())
       9 |       return scanDir(full)
      10 |     if (/\.svg$/.test(file))
      11 |       type = "image/svg+xml"
      12 |     else if (/\.png$/.test(file))
      13 |       type = "image/png"
      14 |     else if (/\.jpg$/.test(file))
      15 |       type = "image/jpeg"
      16 |     else
      17 |       throw new Error("Unknown image type: " + full)
      18 |     let local = full.slice(full.indexOf("/img/") + 1)
      19 |     images.push(`    `)
      20 |   }
      21 | }
      22 | scanDir("epub/img")
      23 | 
      24 | let out = readFileSync("epub/content.opf.src", "utf8").replace("{{images}}", images.join("\n"))
      25 | writeFileSync("epub/content.opf", out)
      26 | 
      
      
      --------------------------------------------------------------------------------
      /src/build_code.mjs:
      --------------------------------------------------------------------------------
       1 | import * as fs from "fs"
       2 | import * as PJSON from "./pseudo_json.mjs"
       3 | import varify from "./varify.mjs"
       4 | 
       5 | let file = process.argv[2]
       6 | let input = fs.readFileSync(file, "utf8")
       7 | 
       8 | let included = /\n```(.*?\bincludeCode:.*)\n([^]*?\n)```/g, m
       9 | let files = Object.create(null)
      10 | let defaultFile = "code/chapter/" + file.replace(".md", ".js")
      11 | 
      12 | while (m = included.exec(input)) {
      13 |   let [_, params, snippet] = m, directive = String(PJSON.parse(params).includeCode)
      14 |   let file = defaultFile
      15 |   if (m = directive.match(/(?:\s|^)>(\S+)/))
      16 |     file = m[1]
      17 |   snippet = snippet.replace(/(\n|^)\s*\/\/ →.*\n/g, "$1")
      18 |   if (!/\.mjs$/.test(file)) snippet = varify(snippet)
      19 |   if (directive.indexOf("strip_log") > -1)
      20 |     snippet = snippet.replace(/(\n|^)\s*console\.log\(.*\);\n/g, "$1")
      21 |   if (m = directive.match(/top_lines:\s*(\d+)/))
      22 |     snippet = snippet.split("\n").slice(0, Number(m[1])).join("\n") + "\n"
      23 |   if (file in files)
      24 |     files[file].push(snippet)
      25 |   else
      26 |     files[file] = [snippet]
      27 | }
      28 | 
      29 | for (let file in files)
      30 |   fs.writeFileSync(file, files[file].join("\n"), "utf8")
      31 | 
      
      
      --------------------------------------------------------------------------------
      /src/chapter.html:
      --------------------------------------------------------------------------------
       1 | 
       2 | 
       3 |   
       4 |   
       5 |   <<t $in.title>> :: Eloquent JavaScript
       6 |   
       7 |   <>
       8 |     
      11 |   <>
      12 | 
      13 | 
      14 | 
      15 | 21 | 22 | > id="<>"<>><>Chapter <><><> 23 | 24 | <> 25 | 26 | 32 |
      33 | 34 | 35 | -------------------------------------------------------------------------------- /src/check_links.mjs: -------------------------------------------------------------------------------- 1 | import {readdirSync, readFileSync} from "fs" 2 | 3 | let files = Object.create(null) 4 | for (let name of readdirSync(".")) { 5 | let m = /^\d\d_(.*?)\.md$/.exec(name) 6 | if (m) files[m[1]] = readFileSync(name, "utf8") 7 | } 8 | files.fast = files.hints = "" 9 | 10 | let fail = 0 11 | function error(file, msg) { 12 | console.error(file + ": " + msg) 13 | fail = 1 14 | } 15 | 16 | let link = /\]\(([\w_]+)(?:#([\w_]+))?\)/g, m 17 | for (let file in files) { 18 | while (m = link.exec(files[file])) { 19 | let [_, file, anchor] = m 20 | let target = files[file] 21 | if (target == null) 22 | error(file, "Unknown target file: " + file) 23 | else if (anchor && target.indexOf("{{id " + anchor + "}}") == -1) 24 | error(file, "Non-existing anchor: " + file + "#" + anchor) 25 | } 26 | } 27 | 28 | process.exit(fail) 29 | -------------------------------------------------------------------------------- /src/client/editor.mjs: -------------------------------------------------------------------------------- 1 | import {EditorView, keymap, lineNumbers} from "@codemirror/view" 2 | import {EditorState, Compartment} from "@codemirror/state" 3 | import {minimalSetup} from "codemirror" 4 | import {html} from "@codemirror/lang-html" 5 | import {javascript} from "@codemirror/lang-javascript" 6 | import {bracketMatching, syntaxHighlighting} from "@codemirror/language" 7 | import {classHighlighter} from "@lezer/highlight" 8 | 9 | let modeCompartment = new Compartment 10 | 11 | export function createState(code, mode, extensions = []) { 12 | return EditorState.create({ 13 | doc: code, 14 | extensions: [ 15 | extensions, 16 | modeCompartment.of(mode == "html" ? html() : javascript()), 17 | minimalSetup, 18 | syntaxHighlighting(classHighlighter), 19 | bracketMatching(), 20 | lineNumbers(), 21 | EditorView.contentAttributes.of({"aria-label": "Code editor"}) 22 | ] 23 | }) 24 | } 25 | 26 | export function updateLanguage(mode) { 27 | return modeCompartment.reconfigure(mode == "html" ? html() : javascript()) 28 | } 29 | -------------------------------------------------------------------------------- /src/client/index.mjs: -------------------------------------------------------------------------------- 1 | import "./ejs.mjs" 2 | import "./code.mjs" 3 | -------------------------------------------------------------------------------- /src/client/rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import {nodeResolve} from "@rollup/plugin-node-resolve" 2 | import terser from "@rollup/plugin-terser" 3 | 4 | export default { 5 | input: "src/client/index.mjs", 6 | output: { 7 | file: "html/ejs.js", 8 | format: "umd" 9 | }, 10 | plugins: [nodeResolve(), terser()] 11 | } 12 | -------------------------------------------------------------------------------- /src/epub_chapter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <<t $in.title>> 5 | 6 | 7 | 8 |
      9 | <>
      Chapter <>
      <
      > 10 |

      <> id="<>"<><>

      11 | <> 12 |
      13 | 14 | 15 | -------------------------------------------------------------------------------- /src/extract_hints.mjs: -------------------------------------------------------------------------------- 1 | import {readdirSync, readFileSync} from "fs" 2 | 3 | process.stdout.write("# Exercise Hints\n\nThe hints below might help when you are stuck with one of the exercises in this book. They don't give away the entire solution, but rather try to help you find it yourself.\n\n"); 4 | 5 | for (let name of readdirSync(".")) { 6 | if (!/^\d\d.*\.md$/.test(name)) continue 7 | 8 | let file = readFileSync(name, "utf8") 9 | let title = file.match(/(?:\n|^)# (.*?)\n/)[1], titleWritten = false 10 | 11 | let curSubsection 12 | let re = /\n### (.*?)\n|\{\{hint\n([^]+?)\nhint\}\}/g, m 13 | while (m = re.exec(file)) { 14 | if (m[1]) { 15 | curSubsection = m[1] 16 | } else { 17 | if (!titleWritten) { 18 | process.stdout.write(`## ${title}\n\n`) 19 | titleWritten = true 20 | } 21 | process.stdout.write(`### ${curSubsection}\n\n${m[2].trim()}\n\n`) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/generate_epub_toc.mjs: -------------------------------------------------------------------------------- 1 | import {readFileSync} from "fs" 2 | import {basename} from "path" 3 | 4 | let [template, ...chapters] = process.argv.slice(2) 5 | 6 | function esc(str) { 7 | return str.replace(/[<>&"]/g, ch => ch == "<" ? "<" : ch == ">" ? ">" : ch == "&" ? "&" : """) 8 | } 9 | 10 | let toc = "" 11 | const section = /]*>]*?id="(h_.*?)".*?><\/a>(.*?)<\/h2>/g 12 | 13 | for (let chapter of chapters) { 14 | let text = readFileSync(chapter, "utf8") 15 | let base = basename(chapter) 16 | let title = /(.*?)<\/h1>/.exec(text)[1] 17 | toc += `
    • ${esc(title)} 18 |
        \n` 19 | for (let match; match = section.exec(text);) 20 | toc += `
      1. ${esc(match[2])}
      2. \n` 21 | toc += `
      22 |
    • \n` 23 | } 24 | 25 | console.log(readFileSync(template, "utf8").replace("{{full_toc}}", toc)) 26 | -------------------------------------------------------------------------------- /src/pseudo_json.mjs: -------------------------------------------------------------------------------- 1 | class Stream { 2 | constructor(str) { 3 | this.str = str 4 | this.pos = 0 5 | } 6 | 7 | err(msg) { 8 | throw new SyntaxError(msg + " at " + this.pos + " in " + JSON.stringify(this.str)) 9 | } 10 | 11 | space() { 12 | for (;;) { 13 | let next = this.next 14 | if (next == 32 || next == 9 || next == 10 || next == 13) this.pos++ 15 | else break 16 | } 17 | } 18 | 19 | get next() { 20 | return this.str.charCodeAt(this.pos) 21 | } 22 | 23 | ahead(n) { 24 | this.pos += n 25 | this.space() 26 | } 27 | } 28 | 29 | export function parse(str) { 30 | let stream = new Stream(str) 31 | stream.space() 32 | let value = parseValue(stream) 33 | if (stream.pos != stream.str.length) stream.err("Extra characters at end of input") 34 | return value 35 | } 36 | 37 | function parseValue(stream) { 38 | let next = stream.next 39 | if (next == 123) return parseObj(stream) 40 | if (next == 91) return parseArr(stream) 41 | if (next == 34) return parseStr(stream) 42 | return parseWord(stream) 43 | } 44 | 45 | function parseObj(stream) { 46 | stream.ahead(1) 47 | let obj = {} 48 | for (;;) { 49 | if (stream.next == 125) break 50 | let prop = parseWord(stream, true) 51 | if (stream.next != 58) stream.err("Expected ':'") 52 | stream.ahead(1) 53 | obj[prop] = parseValue(stream) 54 | if (stream.next == 44) stream.ahead(1) 55 | } 56 | stream.ahead(1) 57 | return obj 58 | } 59 | 60 | function parseArr(stream) { 61 | stream.ahead(1) 62 | let arr = [] 63 | for (;;) { 64 | if (stream.next == 93) break 65 | arr.push(parseValue(stream)) 66 | if (stream.next == 44) stream.ahead(1) 67 | } 68 | stream.ahead(1) 69 | return arr 70 | } 71 | 72 | function parseStr(stream) { 73 | let start = stream.pos 74 | stream.pos++ 75 | for (let escaped = false;;) { 76 | let next = stream.next 77 | stream.pos++ 78 | if (next == 34 && !escaped) break 79 | else if (isNaN(next)) stream.err("Unterminated string") 80 | escaped = next == 92 81 | } 82 | stream.space() 83 | return JSON.parse(stream.str.slice(start, stream.pos)) 84 | } 85 | 86 | function parseWord(stream, prop) { 87 | let start = stream.pos 88 | for (;;) { 89 | let next = stream.next 90 | if ((next >= 97 && next <= 122) || (next >= 65 && next <= 90) || next == 95 || (next >= 48 && next <= 57)) stream.pos++ 91 | else break 92 | } 93 | let word = stream.str.slice(start, stream.pos) 94 | if (!word) stream.err("Expected word") 95 | stream.space() 96 | if (/^(?:0x[\da-f]+|\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?)$/i.test(word)) return JSON.parse(word) 97 | if (!prop) { 98 | if (word == "true") return true 99 | if (word == "false") return false 100 | } 101 | return word 102 | } 103 | -------------------------------------------------------------------------------- /src/require.js: -------------------------------------------------------------------------------- 1 | module.exports = require 2 | -------------------------------------------------------------------------------- /src/transform.mjs: -------------------------------------------------------------------------------- 1 | function childrenText(token) { 2 | let text = "" 3 | for (let i = 0; i < token.children.length; i++) { 4 | let child = token.children[i]; 5 | if (child.type == "text" || child.type == "code_inlin") text += child.content 6 | } 7 | return text 8 | } 9 | 10 | import {createHash} from "crypto" 11 | 12 | function hash(text) { 13 | let sum = createHash("sha1") 14 | sum.update(text) 15 | return sum.digest("base64").slice(0, 10) 16 | } 17 | 18 | function startAndEnd(text) { 19 | var words = text.split(/\W+/); 20 | if (!words[0]) words.shift(); 21 | if (!words[words.length - 1]) words.pop(); 22 | if (words.length <= 6) return words.join(" "); 23 | return words.slice(0, 3).concat(words.slice(words.length - 3)).join(" "); 24 | } 25 | 26 | function tokenText(token) { 27 | if (token.type == "text" || token.type == "code_inline") return token.content 28 | else if (token.type == "softbreak") return " " 29 | else return "" 30 | } 31 | 32 | function smartQuotes(tokens, i, tex, moveQuotes) { 33 | let text = tokens[i].content, from = 0 34 | for (let j = i - 1, tt; j >= 0; j--) if (tt = tokenText(tokens[j])) { 35 | text = tt + text 36 | from = tt.length 37 | break 38 | } 39 | let to = text.length 40 | for (let j = i + 1, tt; j < tokens.length; j++) if (tt = tokenText(tokens[j])) { 41 | text += tt 42 | break 43 | } 44 | 45 | let quoted = text 46 | .replace(/([\w\.,!?\)])'/g, "$1’") 47 | .replace(/'(\w|\(\()/g, "‘$1") 48 | .replace(/([\w\.,!?\)])"/g, "$1”") 49 | .replace(/"(\w|\(\()/g, "“$1") 50 | .slice(from, to) 51 | if (moveQuotes) quoted = quoted.replace(/”([.!?:;,]+)/g, "$1”") 52 | return tex ? quoted.replace(/‘/g, "`").replace(/’/g, "'").replace(/“/g, "``").replace(/”/g, "''") : quoted 53 | } 54 | 55 | function handleIf(tokens, i, options) { 56 | let tag = tokens[i].args[0] 57 | if (options.defined.indexOf(tag) > -1) return i 58 | for (let j = i + 1; j < tokens.length; j++) if (tokens[j].type == "meta_if_close" && tokens[j].args[0] == tag) 59 | return j 60 | } 61 | 62 | const titleCaseSmallWords = "a an the at by for in of on to up and as but with or nor if console.log".split(" "); 63 | 64 | function capitalizeTitle(text) { 65 | return text.split(" ") 66 | .map(word => titleCaseSmallWords.includes(word) ? word : word[0].toUpperCase() + word.slice(1)) 67 | .join(" ") 68 | } 69 | 70 | function transformInline(tokens, options, prevType) { 71 | let capitalize = options.capitalizeTitles && prevType == "heading_open" 72 | let result = [] 73 | for (let i = 0; i < tokens.length; i++) { 74 | let tok = tokens[i], type = tok.type 75 | if (type == "meta_if_close" || (options.index === false && type == "meta_index")) { 76 | // Drop 77 | } else if (type == "meta_if_open") { 78 | i = handleIf(tokens, i, options) 79 | } else { 80 | if (type == "text" && /[\'\"]/.test(tok.content)) tok.content = smartQuotes(tokens, i, options.texQuotes, options.moveQuotes) 81 | if (capitalize) tok.content = capitalizeTitle(tok.content) 82 | result.push(tok) 83 | } 84 | } 85 | return result 86 | } 87 | 88 | function nextTag(tokens, i) { 89 | for (let j = i + 1; j < tokens.length; j++) if (tokens[j].tag) return tokens[j]; 90 | } 91 | 92 | export function transformTokens(tokens, options) { 93 | let meta = {}, result = [] 94 | for (let i = 0; i < tokens.length; i++) { 95 | let tok = tokens[i], type = tok.type 96 | if (type == "meta_meta") { 97 | for (let prop in tok.args[0]) meta[prop] = tok.args[0][prop] 98 | } else if (type == "meta_id") { 99 | let next = nextTag(tokens, i) 100 | ;(next.attrs || (next.attrs = [])).push(["id", tok.args[0]]) 101 | } else if (type == "meta_table") { 102 | nextTag(tokens, i).tableData = tok.args[0] 103 | } else if (type == "meta_if_open") { 104 | i = handleIf(tokens, i, options) 105 | } else if (type == "meta_hint_open" && options.strip === "hints") { 106 | do { i++ } while (tokens[i].type != "meta_hint_close") 107 | } else if (type == "meta_if_close" || (options.index === false && (type == "meta_indexsee" || type == "meta_index"))) { 108 | // Drop 109 | } else if (tok.tag == "h1" && options.takeTitle) { 110 | if (tokens[i + 1].children.length != 1) throw new Error("Complex H1 not supported") 111 | meta.title = tokens[i + 1].children[0].content 112 | i += 2 113 | } else { 114 | if (type == "paragraph_open") 115 | tok.hashID = "p-" + hash(startAndEnd(childrenText(tokens[i + 1]))) 116 | else if (type == "heading_open") 117 | tok.hashID = (tok.tag == "h2" ? "h-" : "i-") + hash(childrenText(tokens[i + 1])) 118 | else if (type == "fence") 119 | tok.hashID = "c-" + hash(tok.content) 120 | 121 | if (tok.children) tok.children = transformInline(tok.children, options, tokens[i - 1].type) 122 | 123 | result.push(tok) 124 | } 125 | } 126 | return {tokens: result, metadata: meta} 127 | } 128 | -------------------------------------------------------------------------------- /src/varify.mjs: -------------------------------------------------------------------------------- 1 | import {parse} from "acorn" 2 | 3 | export default function(code) { 4 | let ast 5 | try { ast = parse(code, {sourceType: "module", ecmaVersion: 2022}) } 6 | catch(_) { return code } 7 | 8 | let patches = [] 9 | ast.body.forEach(node => { 10 | if (node.type == "VariableDeclaration" && node.kind != "var") { 11 | patches.push({from: node.start, to: node.start + node.kind.length, text: "var"}) 12 | } else if (node.type == "ClassDeclaration") { 13 | patches.push({from: node.start, to: node.start, text: "var " + node.id.name + " = "}) 14 | } else if (node.type == "ImportDeclaration") { 15 | let req = "require(" + node.source.raw + ")", text 16 | if (node.specifiers.length == 0) { 17 | text = req 18 | } else if (node.specifiers.length > 1 || node.specifiers[0].type == "ImportDefaultSpecifier") { 19 | let name = "m_" + node.source.value.replace(/\W+/g, "_") + "__" 20 | text = "var " + name + " = " + req 21 | node.specifiers.forEach(spec => { 22 | if (spec.type == "ImportDefaultSpecifier") 23 | text += ", " + spec.local.name + " = " + name + ".default || " + name 24 | else if (name != null) 25 | text += ", " + spec.local.name + " = " + name + "." + spec.imported.name 26 | }) 27 | } else { 28 | text = "var " 29 | node.specifiers.forEach(spec => { 30 | if (spec.type == "ImportNamespaceSpecifier") 31 | text += spec.local.name + " = " + req 32 | else 33 | text += spec.local.name + " = " + req + "." + spec.imported.name 34 | }) 35 | } 36 | patches.push({from: node.start, to: node.end, text: text + ";"}) 37 | } else if (node.type == "ExportNamedDeclaration") { 38 | if (node.source || !node.declaration) 39 | patches.push({from: node.start, to: node.end, text: ""}) 40 | else 41 | patches.push({from: node.start, to: node.declaration.start, text: ""}) 42 | } else if (node.type == "ExportDefaultDeclaration") { 43 | if (/Declaration/.test(node.declaration.type)) { 44 | patches.push({from: node.start, to: node.declaration.start, text: ""}) 45 | } else { 46 | patches.push({from: node.start, to: node.declaration.start, text: ";("}, 47 | {from: node.declaration.end, text: ")"}) 48 | } 49 | } else if (node.type == "ExportAllDeclaration") { 50 | patches.push({from: node.start, to: node.end, text: ""}) 51 | } 52 | }) 53 | 54 | patches.sort((a, b) => a.from - b.from || (a.to || a.from) - (b.to || b.from)) 55 | let out = "", pos = 0 56 | patches.forEach(({from, to, text}) => { 57 | out += code.slice(pos, from) + text 58 | pos = to 59 | }) 60 | out += code.slice(pos) 61 | return out 62 | } 63 | --------------------------------------------------------------------------------