├── .keep
├── 00_intro.html
├── 01_values.html
├── 02_program_structure.html
├── 03_functions.html
├── 04_data.html
├── 05_higher_order.html
├── 06_object.html
├── 07_robot.html
├── 08_error.html
├── 09_regexp.html
├── 10_modules.html
├── 11_async.html
├── 12_language.html
├── 13_browser.html
├── 14_dom.html
├── 15_event.html
├── 16_game.html
├── 17_canvas.html
├── 18_http.html
├── 19_paint.html
├── 20_node.html
├── 21_skillsharing.html
├── CNAME
├── author.html
├── author.json
├── author.txt
├── backers.html
├── backers3.html
├── code
├── animatevillage.js
├── chapter
│ ├── .keep
│ ├── 04_data.js
│ ├── 05_higher_order.js
│ ├── 06_object.js
│ ├── 07_robot.js
│ ├── 08_error.js
│ ├── 11_async.js
│ ├── 12_language.js
│ ├── 16_game.js
│ ├── 17_canvas.js
│ ├── 19_paint.js
│ └── 22_fast.js
├── crow-tech.js
├── draw_layout.js
├── file_server.js
├── hello.js
├── index.html
├── intro.js
├── journal.js
├── levels.js
├── load.js
├── packages_chapter_10.js
├── scripts.js
├── skillsharing.zip
├── skillsharing
│ ├── .keep
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ ├── skillsharing.css
│ │ └── skillsharing_client.js
│ ├── router.js
│ └── skillsharing_server.js
├── 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_tracking_the_scalpel.js
│ ├── 11_2_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.js
│ ├── 20_2_directory_creation.js
│ ├── 20_3_a_public_space_on_the_web.zip
│ ├── 20_3_a_public_space_on_the_web
│ │ ├── index.html
│ │ ├── other.html
│ │ └── public_space.js
│ ├── 21_1_disk_persistence.js
│ ├── 21_2_comment_field_resets.js
│ ├── 22_1_pathfinding.js
│ ├── 22_2_timing.js
│ └── 22_3_optimizing.js
└── squareworker.js
├── 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
├── Shabnam-Bold.eot
├── Shabnam-Bold.ttf
├── Shabnam-Bold.woff
├── Shabnam-Bold.woff2
├── Shabnam-Light.eot
├── Shabnam-Light.ttf
├── Shabnam-Light.woff
├── Shabnam-Light.woff2
├── Shabnam.eot
├── Shabnam.ttf
├── Shabnam.woff
├── Shabnam.woff2
├── cinzel_bold.woff
└── pt_mono.woff
├── 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-else.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
├── ghostery.png
├── ghostery_mini.png
├── hack_reactor.png
├── hack_reactor_mini.png
├── 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
├── mozilla.png
├── mozilla_mini.png
├── 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
├── index.html
└── js
├── .tern-project
├── acorn_codemirror.js
├── chapter_info.js
├── code.js
├── ejs.js
├── node_modules
└── codemirror
│ └── lib
│ ├── codemirror.css
│ └── codemirror.js
└── sandbox.js
/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/.keep
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | eloquentjs.ir
--------------------------------------------------------------------------------
/author.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/author.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Marijn Haverbeke",
3 | "email": "marijn@haverbeke.nl",
4 | "website": "https://marijnhaverbeke.nl/"
5 | }
6 |
--------------------------------------------------------------------------------
/author.txt:
--------------------------------------------------------------------------------
1 | My name is Marijn Haverbeke. You can email me at marijn@haverbeke.nl, or visit my website, https://marijnhaverbeke.nl/ .
2 |
--------------------------------------------------------------------------------
/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.map.src = "img/village2x.png"
37 | this.map.style.cssText = "vertical-align: -8px"
38 | this.robotElt = this.node.appendChild(doc.createElement("div"))
39 | this.robotElt.style.cssText = `position: absolute; transition: left ${0.8 / speed}s, top ${0.8 / speed}s;`
40 | let robotPic = this.robotElt.appendChild(doc.createElement("img"))
41 | robotPic.src = "img/robot_moving2x.gif"
42 | this.parcels = []
43 |
44 | this.text = this.node.appendChild(doc.createElement("span"))
45 | this.button = this.node.appendChild(doc.createElement("button"))
46 | 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%"
47 | this.button.textContent = "Stop"
48 |
49 | this.button.addEventListener("click", () => this.clicked())
50 | this.schedule()
51 |
52 | this.updateView()
53 | this.updateParcels()
54 |
55 | this.robotElt.addEventListener("transitionend", () => this.updateParcels())
56 | }
57 |
58 |
59 | updateView() {
60 | let pos = places[this.worldState.place]
61 | this.robotElt.style.top = (pos.y - 38) + "px"
62 | this.robotElt.style.left = (pos.x - 16) + "px"
63 |
64 | this.text.textContent = ` Turn ${this.turn} `
65 | }
66 |
67 | updateParcels() {
68 | while (this.parcels.length) this.parcels.pop().remove()
69 | let heights = {}
70 | for (let {place, address} of this.worldState.parcels) {
71 | let height = heights[place] || (heights[place] = 0)
72 | heights[place] += 14
73 | let node = document.createElement("div")
74 | let offset = placeKeys.indexOf(address) * 16
75 | node.style.cssText = "position: absolute; height: 16px; width: 16px; background-image: url(img/parcel2x.png); background-position: 0 -" + offset + "px";
76 | if (place == this.worldState.place) {
77 | node.style.left = "25px"
78 | node.style.bottom = (20 + height) + "px"
79 | this.robotElt.appendChild(node)
80 | } else {
81 | let pos = places[place]
82 | node.style.left = (pos.x - 5) + "px"
83 | node.style.top = (pos.y - 10 - height) + "px"
84 | this.node.appendChild(node)
85 | }
86 | this.parcels.push(node)
87 | }
88 | }
89 |
90 | tick() {
91 | let {direction, memory} = this.robot(this.worldState, this.robotState)
92 | this.worldState = this.worldState.move(direction)
93 | this.robotState = memory
94 | this.turn++
95 | this.updateView()
96 | if (this.worldState.parcels.length == 0) {
97 | this.button.remove()
98 | this.text.textContent = ` Finished after ${this.turn} turns`
99 | this.robotElt.firstChild.src = "img/robot_idle2x.png"
100 | } else {
101 | this.schedule()
102 | }
103 | }
104 |
105 | schedule() {
106 | this.timeout = setTimeout(() => this.tick(), 1000 / speed)
107 | }
108 |
109 | clicked() {
110 | if (this.timeout == null) {
111 | this.schedule()
112 | this.button.textContent = "Stop"
113 | this.robotElt.firstChild.src = "img/robot_moving2x.gif"
114 | } else {
115 | clearTimeout(this.timeout)
116 | this.timeout = null
117 | this.button.textContent = "Start"
118 | this.robotElt.firstChild.src = "img/robot_idle2x.png"
119 | }
120 | }
121 | }
122 |
123 | window.runRobotAnimation = function(worldState, robot, robotState) {
124 | if (active && active.timeout != null)
125 | clearTimeout(active.timeout)
126 | active = new Animation(worldState, robot, robotState)
127 | }
128 | })()
129 |
--------------------------------------------------------------------------------
/code/chapter/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/code/chapter/.keep
--------------------------------------------------------------------------------
/code/chapter/04_data.js:
--------------------------------------------------------------------------------
1 | var journal = [];
2 |
3 | function addEntry(events, squirrel) {
4 | journal.push({events, squirrel});
5 | }
6 |
7 | function phi(table) {
8 | return (table[3] * table[0] - table[2] * table[1]) /
9 | Math.sqrt((table[2] + table[3]) *
10 | (table[0] + table[1]) *
11 | (table[1] + table[3]) *
12 | (table[0] + table[2]));
13 | }
14 |
15 | function tableFor(event, journal) {
16 | let table = [0, 0, 0, 0];
17 | for (let i = 0; i < journal.length; i++) {
18 | let entry = journal[i], index = 0;
19 | if (entry.events.includes(event)) index += 1;
20 | if (entry.squirrel) index += 2;
21 | table[index] += 1;
22 | }
23 | return table;
24 | }
25 |
26 | function journalEvents(journal) {
27 | let events = [];
28 | for (let entry of journal) {
29 | for (let event of entry.events) {
30 | if (!events.includes(event)) {
31 | events.push(event);
32 | }
33 | }
34 | }
35 | return events;
36 | }
37 |
38 | function max(...numbers) {
39 | let result = -Infinity;
40 | for (let number of numbers) {
41 | if (number > result) result = number;
42 | }
43 | return result;
44 | }
45 |
46 | var list = {
47 | value: 1,
48 | rest: {
49 | value: 2,
50 | rest: {
51 | value: 3,
52 | rest: null
53 | }
54 | }
55 | };
56 |
--------------------------------------------------------------------------------
/code/chapter/05_higher_order.js:
--------------------------------------------------------------------------------
1 | function repeat(n, action) {
2 | for (let i = 0; i < n; i++) {
3 | action(i);
4 | }
5 | }
6 |
7 | function characterScript(code) {
8 | for (let script of SCRIPTS) {
9 | if (script.ranges.some(([from, to]) => {
10 | return code >= from && code < to;
11 | })) {
12 | return script;
13 | }
14 | }
15 | return null;
16 | }
17 |
18 | function countBy(items, groupName) {
19 | let counts = [];
20 | for (let item of items) {
21 | let name = groupName(item);
22 | let known = counts.findIndex(c => c.name == name);
23 | if (known == -1) {
24 | counts.push({name, count: 1});
25 | } else {
26 | counts[known].count++;
27 | }
28 | }
29 | return counts;
30 | }
31 |
32 | function textScripts(text) {
33 | let scripts = countBy(text, char => {
34 | let script = characterScript(char.codePointAt(0));
35 | return script ? script.name : "none";
36 | }).filter(({name}) => name != "none");
37 |
38 | let total = scripts.reduce((n, {count}) => n + count, 0);
39 | if (total == 0) return "No scripts found";
40 |
41 | return scripts.map(({name, count}) => {
42 | return `${Math.round(count * 100 / total)}% ${name}`;
43 | }).join(", ");
44 | }
45 |
--------------------------------------------------------------------------------
/code/chapter/06_object.js:
--------------------------------------------------------------------------------
1 | function speak(line) {
2 | console.log(`The ${this.type} rabbit says '${line}'`);
3 | }
4 | var whiteRabbit = {type: "white", speak};
5 | var hungryRabbit = {type: "hungry", speak};
6 |
7 |
8 | var Rabbit = class Rabbit {
9 | constructor(type) {
10 | this.type = type;
11 | }
12 | speak(line) {
13 | console.log(`The ${this.type} rabbit says '${line}'`);
14 | }
15 | }
16 |
17 | var killerRabbit = new Rabbit("killer");
18 | var blackRabbit = new Rabbit("black");
19 |
20 | Rabbit.prototype.toString = function() {
21 | return `a ${this.type} rabbit`;
22 | };
23 |
24 | var toStringSymbol = Symbol("toString");
25 |
26 | var Matrix = class Matrix {
27 | constructor(width, height, element = (x, y) => undefined) {
28 | this.width = width;
29 | this.height = height;
30 | this.content = [];
31 |
32 | for (let y = 0; y < height; y++) {
33 | for (let x = 0; x < width; x++) {
34 | this.content[y * width + x] = element(x, y);
35 | }
36 | }
37 | }
38 |
39 | get(x, y) {
40 | return this.content[y * this.width + x];
41 | }
42 | set(x, y, value) {
43 | this.content[y * this.width + x] = value;
44 | }
45 | }
46 |
47 | var MatrixIterator = class MatrixIterator {
48 | constructor(matrix) {
49 | this.x = 0;
50 | this.y = 0;
51 | this.matrix = matrix;
52 | }
53 |
54 | next() {
55 | if (this.y == this.matrix.height) return {done: true};
56 |
57 | let value = {x: this.x,
58 | y: this.y,
59 | value: this.matrix.get(this.x, this.y)};
60 | this.x++;
61 | if (this.x == this.matrix.width) {
62 | this.x = 0;
63 | this.y++;
64 | }
65 | return {value, done: false};
66 | }
67 | }
68 |
69 | Matrix.prototype[Symbol.iterator] = function() {
70 | return new MatrixIterator(this);
71 | };
72 |
73 | var SymmetricMatrix = class SymmetricMatrix extends Matrix {
74 | constructor(size, element = (x, y) => undefined) {
75 | super(size, size, (x, y) => {
76 | if (x < y) return element(y, x);
77 | else return element(x, y);
78 | });
79 | }
80 |
81 | set(x, y, value) {
82 | super.set(x, y, value);
83 | if (x != y) {
84 | super.set(y, x, value);
85 | }
86 | }
87 | }
88 |
89 | var matrix = new SymmetricMatrix(5, (x, y) => `${x},${y}`);
90 |
--------------------------------------------------------------------------------
/code/chapter/07_robot.js:
--------------------------------------------------------------------------------
1 | var roads = [
2 | "Alice's House-Bob's House", "Alice's House-Cabin",
3 | "Alice's House-Post Office", "Bob's House-Town Hall",
4 | "Daria's House-Ernie's House", "Daria's House-Town Hall",
5 | "Ernie's House-Grete's House", "Grete's House-Farm",
6 | "Grete's House-Shop", "Marketplace-Farm",
7 | "Marketplace-Post Office", "Marketplace-Shop",
8 | "Marketplace-Town Hall", "Shop-Town Hall"
9 | ];
10 |
11 | function buildGraph(edges) {
12 | let graph = Object.create(null);
13 | function addEdge(from, to) {
14 | if (graph[from] == null) {
15 | graph[from] = [to];
16 | } else {
17 | graph[from].push(to);
18 | }
19 | }
20 | for (let [from, to] of edges.map(r => r.split("-"))) {
21 | addEdge(from, to);
22 | addEdge(to, from);
23 | }
24 | return graph;
25 | }
26 |
27 | var roadGraph = buildGraph(roads);
28 |
29 | var VillageState = class VillageState {
30 | constructor(place, parcels) {
31 | this.place = place;
32 | this.parcels = parcels;
33 | }
34 |
35 | move(destination) {
36 | if (!roadGraph[this.place].includes(destination)) {
37 | return this;
38 | } else {
39 | let parcels = this.parcels.map(p => {
40 | if (p.place != this.place) return p;
41 | return {place: destination, address: p.address};
42 | }).filter(p => p.place != p.address);
43 | return new VillageState(destination, parcels);
44 | }
45 | }
46 | }
47 |
48 | function runRobot(state, robot, memory) {
49 | for (let turn = 0;; turn++) {
50 | if (state.parcels.length == 0) {
51 | console.log(`Done in ${turn} turns`);
52 | break;
53 | }
54 | let action = robot(state, memory);
55 | state = state.move(action.direction);
56 | memory = action.memory;
57 | console.log(`Moved to ${action.direction}`);
58 | }
59 | }
60 |
61 | function randomPick(array) {
62 | let choice = Math.floor(Math.random() * array.length);
63 | return array[choice];
64 | }
65 |
66 | function randomRobot(state) {
67 | return {direction: randomPick(roadGraph[state.place])};
68 | }
69 |
70 | VillageState.random = function(parcelCount = 5) {
71 | let parcels = [];
72 | for (let i = 0; i < parcelCount; i++) {
73 | let address = randomPick(Object.keys(roadGraph));
74 | let place;
75 | do {
76 | place = randomPick(Object.keys(roadGraph));
77 | } while (place == address);
78 | parcels.push({place, address});
79 | }
80 | return new VillageState("Post Office", parcels);
81 | };
82 |
83 | var mailRoute = [
84 | "Alice's House", "Cabin", "Alice's House", "Bob's House",
85 | "Town Hall", "Daria's House", "Ernie's House",
86 | "Grete's House", "Shop", "Grete's House", "Farm",
87 | "Marketplace", "Post Office"
88 | ];
89 |
90 | function routeRobot(state, memory) {
91 | if (memory.length == 0) {
92 | memory = mailRoute;
93 | }
94 | return {direction: memory[0], memory: memory.slice(1)};
95 | }
96 |
97 | function findRoute(graph, from, to) {
98 | let work = [{at: from, route: []}];
99 | for (let i = 0; i < work.length; i++) {
100 | let {at, route} = work[i];
101 | for (let place of graph[at]) {
102 | if (place == to) return route.concat(place);
103 | if (!work.some(w => w.at == place)) {
104 | work.push({at: place, route: route.concat(place)});
105 | }
106 | }
107 | }
108 | }
109 |
110 | function goalOrientedRobot({place, parcels}, route) {
111 | if (route.length == 0) {
112 | let parcel = parcels[0];
113 | if (parcel.place != place) {
114 | route = findRoute(roadGraph, place, parcel.place);
115 | } else {
116 | route = findRoute(roadGraph, place, parcel.address);
117 | }
118 | }
119 | return {direction: route[0], memory: route.slice(1)};
120 | }
121 |
--------------------------------------------------------------------------------
/code/chapter/08_error.js:
--------------------------------------------------------------------------------
1 | var accounts = {
2 | a: 100,
3 | b: 0,
4 | c: 20
5 | };
6 |
7 | function getAccount() {
8 | let accountName = prompt("Enter an account name");
9 | if (!accounts.hasOwnProperty(accountName)) {
10 | throw new Error(`No such account: ${accountName}`);
11 | }
12 | return accountName;
13 | }
14 |
15 | function transfer(from, amount) {
16 | if (accounts[from] < amount) return;
17 | accounts[from] -= amount;
18 | accounts[getAccount()] += amount;
19 | }
20 |
21 | function transfer(from, amount) {
22 | if (accounts[from] < amount) return;
23 | let progress = 0;
24 | try {
25 | accounts[from] -= amount;
26 | progress = 1;
27 | accounts[getAccount()] += amount;
28 | progress = 2;
29 | } finally {
30 | if (progress == 1) {
31 | accounts[from] += amount;
32 | }
33 | }
34 | }
35 |
36 | var InputError = class InputError extends Error {}
37 |
38 | function promptDirection(question) {
39 | let result = prompt(question);
40 | if (result.toLowerCase() == "left") return "L";
41 | if (result.toLowerCase() == "right") return "R";
42 | throw new InputError("Invalid direction: " + result);
43 | }
44 |
--------------------------------------------------------------------------------
/code/chapter/11_async.js:
--------------------------------------------------------------------------------
1 | var bigOak = require("./crow-tech").bigOak;
2 |
3 | var defineRequestType = require("./crow-tech").defineRequestType;
4 |
5 | defineRequestType("note", (nest, content, source, done) => {
6 | console.log(`${nest.name} received note: ${content}`);
7 | done();
8 | });
9 |
10 | function storage(nest, name) {
11 | return new Promise(resolve => {
12 | nest.readStorage(name, result => resolve(result));
13 | });
14 | }
15 |
16 | var Timeout = class Timeout extends Error {}
17 |
18 | function request(nest, target, type, content) {
19 | return new Promise((resolve, reject) => {
20 | let done = false;
21 | function attempt(n) {
22 | nest.send(target, type, content, (failed, value) => {
23 | done = true;
24 | if (failed) reject(failed);
25 | else resolve(value);
26 | });
27 | setTimeout(() => {
28 | if (done) return;
29 | else if (n < 3) attempt(n + 1);
30 | else reject(new Timeout("Timed out"));
31 | }, 250);
32 | }
33 | attempt(1);
34 | });
35 | }
36 |
37 | function requestType(name, handler) {
38 | defineRequestType(name, (nest, content, source,
39 | callback) => {
40 | try {
41 | Promise.resolve(handler(nest, content, source))
42 | .then(response => callback(null, response),
43 | failure => callback(failure));
44 | } catch (exception) {
45 | callback(exception);
46 | }
47 | });
48 | }
49 |
50 | requestType("ping", () => "pong");
51 |
52 | function availableNeighbors(nest) {
53 | let requests = nest.neighbors.map(neighbor => {
54 | return request(nest, neighbor, "ping")
55 | .then(() => true, () => false);
56 | });
57 | return Promise.all(requests).then(result => {
58 | return nest.neighbors.filter((_, i) => result[i]);
59 | });
60 | }
61 |
62 | var everywhere = require("./crow-tech").everywhere;
63 |
64 | everywhere(nest => {
65 | nest.state.gossip = [];
66 | });
67 |
68 | function sendGossip(nest, message, exceptFor = null) {
69 | nest.state.gossip.push(message);
70 | for (let neighbor of nest.neighbors) {
71 | if (neighbor == exceptFor) continue;
72 | request(nest, neighbor, "gossip", message);
73 | }
74 | }
75 |
76 | requestType("gossip", (nest, message, source) => {
77 | if (nest.state.gossip.includes(message)) return;
78 | console.log(`${nest.name} received gossip '${
79 | message}' from ${source}`);
80 | sendGossip(nest, message, source);
81 | });
82 |
83 | requestType("connections", (nest, {name, neighbors},
84 | source) => {
85 | let connections = nest.state.connections;
86 | if (JSON.stringify(connections.get(name)) ==
87 | JSON.stringify(neighbors)) return;
88 | connections.set(name, neighbors);
89 | broadcastConnections(nest, name, source);
90 | });
91 |
92 | function broadcastConnections(nest, name, exceptFor = null) {
93 | for (let neighbor of nest.neighbors) {
94 | if (neighbor == exceptFor) continue;
95 | request(nest, neighbor, "connections", {
96 | name,
97 | neighbors: nest.state.connections.get(name)
98 | });
99 | }
100 | }
101 |
102 | everywhere(nest => {
103 | nest.state.connections = new Map;
104 | nest.state.connections.set(nest.name, nest.neighbors);
105 | broadcastConnections(nest, nest.name);
106 | });
107 |
108 | function findRoute(from, to, connections) {
109 | let work = [{at: from, via: null}];
110 | for (let i = 0; i < work.length; i++) {
111 | let {at, via} = work[i];
112 | for (let next of connections.get(at) || []) {
113 | if (next == to) return via;
114 | if (!work.some(w => w.at == next)) {
115 | work.push({at: next, via: via || next});
116 | }
117 | }
118 | }
119 | return null;
120 | }
121 |
122 | function routeRequest(nest, target, type, content) {
123 | if (nest.neighbors.includes(target)) {
124 | return request(nest, target, type, content);
125 | } else {
126 | let via = findRoute(nest.name, target,
127 | nest.state.connections);
128 | if (!via) throw new Error(`No route to ${target}`);
129 | return request(nest, via, "route",
130 | {target, type, content});
131 | }
132 | }
133 |
134 | requestType("route", (nest, {target, type, content}) => {
135 | return routeRequest(nest, target, type, content);
136 | });
137 |
138 | requestType("storage", (nest, name) => storage(nest, name));
139 |
140 | function findInStorage(nest, name) {
141 | return storage(nest, name).then(found => {
142 | if (found != null) return found;
143 | else return findInRemoteStorage(nest, name);
144 | });
145 | }
146 |
147 | function network(nest) {
148 | return Array.from(nest.state.connections.keys());
149 | }
150 |
151 | function findInRemoteStorage(nest, name) {
152 | let sources = network(nest).filter(n => n != nest.name);
153 | function next() {
154 | if (sources.length == 0) {
155 | return Promise.reject(new Error("Not found"));
156 | } else {
157 | let source = sources[Math.floor(Math.random() *
158 | sources.length)];
159 | sources = sources.filter(n => n != source);
160 | return routeRequest(nest, source, "storage", name)
161 | .then(value => value != null ? value : next(),
162 | next);
163 | }
164 | }
165 | return next();
166 | }
167 |
168 | var Group = class Group {
169 | constructor() { this.members = []; }
170 | add(m) { this.members.add(m); }
171 | }
172 |
173 | function anyStorage(nest, source, name) {
174 | if (source == nest.name) return storage(nest, name);
175 | else return routeRequest(nest, source, "storage", name);
176 | }
177 |
178 | async function chicks(nest, year) {
179 | let list = "";
180 | await Promise.all(network(nest).map(async name => {
181 | list += `${name}: ${
182 | await anyStorage(nest, name, `chicks in ${year}`)
183 | }\n`;
184 | }));
185 | return list;
186 | }
187 |
--------------------------------------------------------------------------------
/code/chapter/12_language.js:
--------------------------------------------------------------------------------
1 | function parseExpression(program) {
2 | program = skipSpace(program);
3 | let match, expr;
4 | if (match = /^"([^"]*)"/.exec(program)) {
5 | expr = {type: "value", value: match[1]};
6 | } else if (match = /^\d+\b/.exec(program)) {
7 | expr = {type: "value", value: Number(match[0])};
8 | } else if (match = /^[^\s(),#"]+/.exec(program)) {
9 | expr = {type: "word", name: match[0]};
10 | } else {
11 | throw new SyntaxError("Unexpected syntax: " + program);
12 | }
13 |
14 | return parseApply(expr, program.slice(match[0].length));
15 | }
16 |
17 | function skipSpace(string) {
18 | let first = string.search(/\S/);
19 | if (first == -1) return "";
20 | return string.slice(first);
21 | }
22 |
23 | function parseApply(expr, program) {
24 | program = skipSpace(program);
25 | if (program[0] != "(") {
26 | return {expr: expr, rest: program};
27 | }
28 |
29 | program = skipSpace(program.slice(1));
30 | expr = {type: "apply", operator: expr, args: []};
31 | while (program[0] != ")") {
32 | let arg = parseExpression(program);
33 | expr.args.push(arg.expr);
34 | program = skipSpace(arg.rest);
35 | if (program[0] == ",") {
36 | program = skipSpace(program.slice(1));
37 | } else if (program[0] != ")") {
38 | throw new SyntaxError("Expected ',' or ')'");
39 | }
40 | }
41 | return parseApply(expr, program.slice(1));
42 | }
43 |
44 | function parse(program) {
45 | let {expr, rest} = parseExpression(program);
46 | if (skipSpace(rest).length > 0) {
47 | throw new SyntaxError("Unexpected text after program");
48 | }
49 | return expr;
50 | }
51 | // operator: {type: "word", name: "+"},
52 | // args: [{type: "word", name: "a"},
53 | // {type: "value", value: 10}]}
54 |
55 | var specialForms = Object.create(null);
56 |
57 | function evaluate(expr, scope) {
58 | if (expr.type == "value") {
59 | return expr.value;
60 | } else if (expr.type == "word") {
61 | if (expr.name in scope) {
62 | return scope[expr.name];
63 | } else {
64 | throw new ReferenceError(
65 | `Undefined binding: ${expr.name}`);
66 | }
67 | } else if (expr.type == "apply") {
68 | let {operator, args} = expr;
69 | if (operator.type == "word" &&
70 | operator.name in specialForms) {
71 | return specialForms[operator.name](expr.args, scope);
72 | } else {
73 | let op = evaluate(operator, scope);
74 | if (typeof op == "function") {
75 | return op(...args.map(arg => evaluate(arg, scope)));
76 | } else {
77 | throw new TypeError("Applying a non-function.");
78 | }
79 | }
80 | }
81 | }
82 |
83 | specialForms.if = (args, scope) => {
84 | if (args.length != 3) {
85 | throw new SyntaxError("Wrong number of args to if");
86 | } else if (evaluate(args[0], scope) !== false) {
87 | return evaluate(args[1], scope);
88 | } else {
89 | return evaluate(args[2], scope);
90 | }
91 | };
92 |
93 | specialForms.while = (args, scope) => {
94 | if (args.length != 2) {
95 | throw new SyntaxError("Wrong number of args to while");
96 | }
97 | while (evaluate(args[0], scope) !== false) {
98 | evaluate(args[1], scope);
99 | }
100 |
101 | // Since undefined does not exist in Egg, we return false,
102 | // for lack of a meaningful result.
103 | return false;
104 | };
105 |
106 | specialForms.do = (args, scope) => {
107 | let value = false;
108 | for (let arg of args) {
109 | value = evaluate(arg, scope);
110 | }
111 | return value;
112 | };
113 |
114 | specialForms.define = (args, scope) => {
115 | if (args.length != 2 || args[0].type != "word") {
116 | throw new SyntaxError("Incorrect use of define");
117 | }
118 | let value = evaluate(args[1], scope);
119 | scope[args[0].name] = value;
120 | return value;
121 | };
122 |
123 | var topScope = Object.create(null);
124 |
125 | topScope.true = true;
126 | topScope.false = false;
127 |
128 | for (let op of ["+", "-", "*", "/", "==", "<", ">"]) {
129 | topScope[op] = Function("a, b", `return a ${op} b;`);
130 | }
131 |
132 | topScope.print = value => {
133 | console.log(value);
134 | return value;
135 | };
136 |
137 | function run(program) {
138 | return evaluate(parse(program), Object.create(topScope));
139 | }
140 |
141 | specialForms.fun = (args, scope) => {
142 | if (!args.length) {
143 | throw new SyntaxError("Functions need a body");
144 | }
145 | let body = args[args.length - 1];
146 | let params = args.slice(0, args.length - 1).map(expr => {
147 | if (expr.type != "word") {
148 | throw new SyntaxError("Parameter names must be words");
149 | }
150 | return expr.name;
151 | });
152 |
153 | return function() {
154 | if (arguments.length != params.length) {
155 | throw new TypeError("Wrong number of arguments");
156 | }
157 | let localScope = Object.create(scope);
158 | for (let i = 0; i < arguments.length; i++) {
159 | localScope[params[i]] = arguments[i];
160 | }
161 | return evaluate(body, localScope);
162 | };
163 | };
164 |
--------------------------------------------------------------------------------
/code/chapter/17_canvas.js:
--------------------------------------------------------------------------------
1 | var results = [
2 | {name: "Satisfied", count: 1043, color: "lightblue"},
3 | {name: "Neutral", count: 563, color: "lightgreen"},
4 | {name: "Unsatisfied", count: 510, color: "pink"},
5 | {name: "No comment", count: 175, color: "silver"}
6 | ];
7 |
8 | function flipHorizontally(context, around) {
9 | context.translate(around, 0);
10 | context.scale(-1, 1);
11 | context.translate(-around, 0);
12 | }
13 |
14 | var CanvasDisplay = class CanvasDisplay {
15 | constructor(parent, level) {
16 | this.canvas = document.createElement("canvas");
17 | this.canvas.width = Math.min(600, level.width * scale);
18 | this.canvas.height = Math.min(450, level.height * scale);
19 | parent.appendChild(this.canvas);
20 | this.cx = this.canvas.getContext("2d");
21 |
22 | this.flipPlayer = false;
23 |
24 | this.viewport = {
25 | left: 0,
26 | top: 0,
27 | width: this.canvas.width / scale,
28 | height: this.canvas.height / scale
29 | };
30 | }
31 |
32 | clear() {
33 | this.canvas.remove();
34 | }
35 | }
36 |
37 | CanvasDisplay.prototype.syncState = function(state) {
38 | this.updateViewport(state);
39 | this.clearDisplay(state.status);
40 | this.drawBackground(state.level);
41 | this.drawActors(state.actors);
42 | };
43 |
44 | CanvasDisplay.prototype.updateViewport = function(state) {
45 | let view = this.viewport, margin = view.width / 3;
46 | let player = state.player;
47 | let center = player.pos.plus(player.size.times(0.5));
48 |
49 | if (center.x < view.left + margin) {
50 | view.left = Math.max(center.x - margin, 0);
51 | } else if (center.x > view.left + view.width - margin) {
52 | view.left = Math.min(center.x + margin - view.width,
53 | state.level.width - view.width);
54 | }
55 | if (center.y < view.top + margin) {
56 | view.top = Math.max(center.y - margin, 0);
57 | } else if (center.y > view.top + view.height - margin) {
58 | view.top = Math.min(center.y + margin - view.height,
59 | state.level.height - view.height);
60 | }
61 | };
62 |
63 | CanvasDisplay.prototype.clearDisplay = function(status) {
64 | if (status == "won") {
65 | this.cx.fillStyle = "rgb(68, 191, 255)";
66 | } else if (status == "lost") {
67 | this.cx.fillStyle = "rgb(44, 136, 214)";
68 | } else {
69 | this.cx.fillStyle = "rgb(52, 166, 251)";
70 | }
71 | this.cx.fillRect(0, 0,
72 | this.canvas.width, this.canvas.height);
73 | };
74 |
75 | var otherSprites = document.createElement("img");
76 | otherSprites.src = "img/sprites.png";
77 |
78 | CanvasDisplay.prototype.drawBackground = function(level) {
79 | let {left, top, width, height} = this.viewport;
80 | let xStart = Math.floor(left);
81 | let xEnd = Math.ceil(left + width);
82 | let yStart = Math.floor(top);
83 | let yEnd = Math.ceil(top + height);
84 |
85 | for (let y = yStart; y < yEnd; y++) {
86 | for (let x = xStart; x < xEnd; x++) {
87 | let tile = level.rows[y][x];
88 | if (tile == "empty") continue;
89 | let screenX = (x - left) * scale;
90 | let screenY = (y - top) * scale;
91 | let tileX = tile == "lava" ? scale : 0;
92 | this.cx.drawImage(otherSprites,
93 | tileX, 0, scale, scale,
94 | screenX, screenY, scale, scale);
95 | }
96 | }
97 | };
98 |
99 | var playerSprites = document.createElement("img");
100 | playerSprites.src = "img/player.png";
101 | var playerXOverlap = 4;
102 |
103 | CanvasDisplay.prototype.drawPlayer = function(player, x, y,
104 | width, height){
105 | width += playerXOverlap * 2;
106 | x -= playerXOverlap;
107 | if (player.speed.x != 0) {
108 | this.flipPlayer = player.speed.x < 0;
109 | }
110 |
111 | let tile = 8;
112 | if (player.speed.y != 0) {
113 | tile = 9;
114 | } else if (player.speed.x != 0) {
115 | tile = Math.floor(Date.now() / 60) % 8;
116 | }
117 |
118 | this.cx.save();
119 | if (this.flipPlayer) {
120 | flipHorizontally(this.cx, x + width / 2);
121 | }
122 | let tileX = tile * width;
123 | this.cx.drawImage(playerSprites, tileX, 0, width, height,
124 | x, y, width, height);
125 | this.cx.restore();
126 | };
127 |
128 | CanvasDisplay.prototype.drawActors = function(actors) {
129 | for (let actor of actors) {
130 | let width = actor.size.x * scale;
131 | let height = actor.size.y * scale;
132 | let x = (actor.pos.x - this.viewport.left) * scale;
133 | let y = (actor.pos.y - this.viewport.top) * scale;
134 | if (actor.type == "player") {
135 | this.drawPlayer(actor, x, y, width, height);
136 | } else {
137 | let tileX = (actor.type == "coin" ? 2 : 1) * scale;
138 | this.cx.drawImage(otherSprites,
139 | tileX, 0, width, height,
140 | x, y, width, height);
141 | }
142 | }
143 | };
144 |
--------------------------------------------------------------------------------
/code/chapter/22_fast.js:
--------------------------------------------------------------------------------
1 | var GraphNode = class GraphNode {
2 | constructor() {
3 | this.pos = new Vec(Math.random() * 1000,
4 | Math.random() * 1000);
5 | this.edges = [];
6 | }
7 | connect(other) {
8 | this.edges.push(other);
9 | other.edges.push(this);
10 | }
11 | hasEdge(other) {
12 | return this.edges.includes(other);
13 | }
14 | }
15 |
16 | function treeGraph(depth, branches) {
17 | let graph = [new GraphNode()];
18 | if (depth > 1) {
19 | for (let i = 0; i < branches; i++) {
20 | let subGraph = treeGraph(depth - 1, branches);
21 | graph[0].connect(subGraph[0]);
22 | graph = graph.concat(subGraph);
23 | }
24 | }
25 | return graph;
26 | }
27 |
28 | var springLength = 40;
29 | var springStrength = 0.1;
30 |
31 | var repulsionStrength = 1500;
32 |
33 | function forceDirected_simple(graph) {
34 | for (let node of graph) {
35 | for (let other of graph) {
36 | if (other == node) continue;
37 | let apart = other.pos.minus(node.pos);
38 | let distance = Math.max(1, apart.length);
39 | let forceSize = -repulsionStrength / (distance * distance);
40 | if (node.hasEdge(other)) {
41 | forceSize += (distance - springLength) * springStrength;
42 | }
43 | let normalized = apart.times(1 / distance);
44 | node.pos = node.pos.plus(normalized.times(forceSize));
45 | }
46 | }
47 | }
48 |
49 | function runLayout(implementation, graph) {
50 | function run(steps, time) {
51 | let startTime = Date.now();
52 | for (let i = 0; i < 100; i++) {
53 | implementation(graph);
54 | }
55 | time += Date.now() - startTime;
56 | drawGraph(graph);
57 |
58 | if (steps == 0) console.log(time);
59 | else requestAnimationFrame(() => run(steps - 100, time));
60 | }
61 | run(4000, 0);
62 | }
63 |
64 | function forceDirected_noRepeat(graph) {
65 | for (let i = 0; i < graph.length; i++) {
66 | let node = graph[i];
67 | for (let j = i + 1; j < graph.length; j++) {
68 | let other = graph[j];
69 | let apart = other.pos.minus(node.pos);
70 | let distance = Math.max(1, apart.length);
71 | let forceSize = -repulsionStrength / (distance * distance);
72 | if (node.hasEdge(other)) {
73 | forceSize += (distance - springLength) * springStrength;
74 | }
75 | let applied = apart.times(forceSize / distance);
76 | node.pos = node.pos.plus(applied);
77 | other.pos = other.pos.minus(applied);
78 | }
79 | }
80 | }
81 |
82 | var skipDistance = 175;
83 |
84 | function forceDirected_skip(graph) {
85 | for (let i = 0; i < graph.length; i++) {
86 | let node = graph[i];
87 | for (let j = i + 1; j < graph.length; j++) {
88 | let other = graph[j];
89 | let apart = other.pos.minus(node.pos);
90 | let distance = Math.max(1, apart.length);
91 | let hasEdge = node.hasEdge(other);
92 | if (!hasEdge && distance > skipDistance) continue;
93 | let forceSize = -repulsionStrength / (distance * distance);
94 | if (hasEdge) {
95 | forceSize += (distance - springLength) * springStrength;
96 | }
97 | let applied = apart.times(forceSize / distance);
98 | node.pos = node.pos.plus(applied);
99 | other.pos = other.pos.minus(applied);
100 | }
101 | }
102 | }
103 |
104 | GraphNode.prototype.hasEdgeFast = function(other) {
105 | for (let i = 0; i < this.edges.length; i++) {
106 | if (this.edges[i] === other) return true;
107 | }
108 | return false;
109 | };
110 |
111 | function forceDirected_hasEdgeFast(graph) {
112 | for (let i = 0; i < graph.length; i++) {
113 | let node = graph[i];
114 | for (let j = i + 1; j < graph.length; j++) {
115 | let other = graph[j];
116 | let apart = other.pos.minus(node.pos);
117 | let distance = Math.max(1, apart.length);
118 | let hasEdge = node.hasEdgeFast(other);
119 | if (!hasEdge && distance > skipDistance) continue;
120 | let forceSize = -repulsionStrength / (distance * distance);
121 | if (hasEdge) {
122 | forceSize += (distance - springLength) * springStrength;
123 | }
124 | let applied = apart.times(forceSize / distance);
125 | node.pos = node.pos.plus(applied);
126 | other.pos = other.pos.minus(applied);
127 | }
128 | }
129 | }
130 |
131 | function forceDirected_noVector(graph) {
132 | for (let i = 0; i < graph.length; i++) {
133 | let node = graph[i];
134 | for (let j = i + 1; j < graph.length; j++) {
135 | let other = graph[j];
136 | let apartX = other.pos.x - node.pos.x;
137 | let apartY = other.pos.y - node.pos.y;
138 | let distance = Math.max(1, Math.sqrt(apartX * apartX + apartY * apartY));
139 | let hasEdge = node.hasEdgeFast(other);
140 | if (!hasEdge && distance > skipDistance) continue;
141 | let forceSize = -repulsionStrength / (distance * distance);
142 | if (hasEdge) {
143 | forceSize += (distance - springLength) * springStrength;
144 | }
145 | let forceX = apartX * forceSize / distance;
146 | let forceY = apartY * forceSize / distance;
147 | node.pos.x += forceX; node.pos.y += forceY;
148 | other.pos.x -= forceX; other.pos.y -= forceY;
149 | }
150 | }
151 | }
152 |
153 | var mangledGraph = treeGraph(4, 4);
154 | for (let node of mangledGraph) {
155 | node[`p${Math.floor(Math.random() * 999)}`] = true;
156 | }
157 |
--------------------------------------------------------------------------------
/code/crow-tech.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | const connections = [
3 | "Church Tower-Sportsgrounds", "Church Tower-Big Maple", "Big Maple-Sportsgrounds",
4 | "Big Maple-Woods", "Big Maple-Fabienne's Garden", "Fabienne's Garden-Woods",
5 | "Fabienne's Garden-Cow Pasture", "Cow Pasture-Big Oak", "Big Oak-Butcher Shop",
6 | "Butcher Shop-Tall Poplar", "Tall Poplar-Sportsgrounds", "Tall Poplar-Chateau",
7 | "Chateau-Great Pine", "Great Pine-Jacques' Farm", "Jacques' Farm-Hawthorn",
8 | "Great Pine-Hawthorn", "Hawthorn-Gilles' Garden", "Great Pine-Gilles' Garden",
9 | "Gilles' Garden-Big Oak", "Gilles' Garden-Butcher Shop", "Chateau-Butcher Shop"
10 | ]
11 |
12 | function storageFor(name) {
13 | let storage = Object.create(null)
14 | storage["food caches"] = ["cache in the oak", "cache in the meadow", "cache under the hedge"]
15 | storage["cache in the oak"] = "A hollow above the third big branch from the bottom. Several pieces of bread and a pile of acorns."
16 | storage["cache in the meadow"] = "Buried below the patch of nettles (south side). A dead snake."
17 | storage["cache under the hedge"] = "Middle of the hedge at Gilles' garden. Marked with a forked twig. Two bottles of beer."
18 | storage["enemies"] = ["Farmer Jacques' dog", "The butcher", "That one-legged jackdaw", "The boy with the airgun"]
19 | if (name == "Church Tower" || name == "Hawthorn" || name == "Chateau")
20 | storage["events on 2017-12-21"] = "Deep snow. Butcher's garbage can fell over. We chased off the ravens from Saint-Vulbas."
21 | let hash = 0
22 | for (let i = 0; i < name.length; i++) hash += name.charCodeAt(i)
23 | for (let y = 1985; y <= 2018; y++) {
24 | storage[`chicks in ${y}`] = hash % 6
25 | hash = Math.abs((hash << 2) ^ (hash + y))
26 | }
27 | if (name == "Big Oak") storage.scalpel = "Gilles' Garden"
28 | else if (name == "Gilles' Garden") storage.scalpel = "Woods"
29 | else if (name == "Woods") storage.scalpel = "Chateau"
30 | else if (name == "Chateau" || name == "Butcher Shop") storage.scalpel = "Butcher Shop"
31 | else storage.scalpel = "Big Oak"
32 | for (let prop of Object.keys(storage)) storage[prop] = JSON.stringify(storage[prop])
33 | return storage
34 | }
35 |
36 | class Network {
37 | constructor(connections, storageFor) {
38 | let reachable = Object.create(null)
39 | for (let [from, to] of connections.map(conn => conn.split("-"))) {
40 | ;(reachable[from] || (reachable[from] = [])).push(to)
41 | ;(reachable[to] || (reachable[to] = [])).push(from)
42 | }
43 | this.nodes = Object.create(null)
44 | for (let name of Object.keys(reachable))
45 | this.nodes[name] = new Node(name, reachable[name], this, storageFor(name))
46 | this.types = Object.create(null)
47 | }
48 |
49 | defineRequestType(name, handler) {
50 | this.types[name] = handler
51 | }
52 |
53 | everywhere(f) {
54 | for (let node of Object.values(this.nodes)) f(node)
55 | }
56 | }
57 |
58 | const $storage = Symbol("storage"), $network = Symbol("network")
59 |
60 | function ser(value) {
61 | return value == null ? null : JSON.parse(JSON.stringify(value))
62 | }
63 |
64 | class Node {
65 | constructor(name, neighbors, network, storage) {
66 | this.name = name
67 | this.neighbors = neighbors
68 | this[$network] = network
69 | this.state = Object.create(null)
70 | this[$storage] = storage
71 | }
72 |
73 | send(to, type, message, callback) {
74 | let toNode = this[$network].nodes[to]
75 | if (!toNode || !this.neighbors.includes(to))
76 | return callback(new Error(`${to} is not reachable from ${this.name}`))
77 | let handler = this[$network].types[type]
78 | if (!handler)
79 | return callback(new Error("Unknown request type " + type))
80 | if (Math.random() > 0.03) setTimeout(() => {
81 | try {
82 | handler(toNode, ser(message), this.name, (error, response) => {
83 | setTimeout(() => callback(error, ser(response)), 10)
84 | })
85 | } catch(e) {
86 | callback(e)
87 | }
88 | }, 10 * Math.floor(Math.random() * 10))
89 | }
90 |
91 | readStorage(name, callback) {
92 | let value = this[$storage][name]
93 | setTimeout(() => callback(value && JSON.parse(value)), 20)
94 | }
95 |
96 | writeStorage(name, value, callback) {
97 | setTimeout(() => {
98 | this[$storage][name] = JSON.stringify(value)
99 | callback()
100 | }, 20)
101 | }
102 | }
103 |
104 | let network = new Network(connections, storageFor)
105 | exports.bigOak = network.nodes["Big Oak"]
106 | exports.everywhere = network.everywhere.bind(network)
107 | exports.defineRequestType = network.defineRequestType.bind(network)
108 |
109 | if (typeof __sandbox != "undefined") {
110 | __sandbox.handleDeps = false
111 | __sandbox.notify.onLoad = () => {
112 | // Kludge to make sure some functions are delayed until the
113 | // nodes have been running for 500ms, to give them a chance to
114 | // propagate network information.
115 | let waitFor = Date.now() + 500
116 | function wrapWaiting(f) {
117 | return function(...args) {
118 | let wait = waitFor - Date.now()
119 | if (wait <= 0) return f(...args)
120 | return new Promise(ok => setTimeout(ok, wait)).then(() => f(...args))
121 | }
122 | }
123 | for (let n of ["routeRequest", "findInStorage", "chicks"])
124 | window[n] = wrapWaiting(window[n])
125 | }
126 | }
127 |
128 | if (typeof window != "undefined") {
129 | window.require = name => {
130 | if (name != "./crow-tech") throw new Error("Crow nests can only require \"./crow-tech\"")
131 | return exports
132 | }
133 | } else if (typeof module != "undefined" && module.exports) {
134 | module.exports = exports
135 | }
136 | })()
137 |
--------------------------------------------------------------------------------
/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 = 8;
27 |
28 | function drawGraph(graph) {
29 | let canvas = document.querySelector("canvas");
30 | if (!canvas) {
31 | canvas = document.body.appendChild(document.createElement("canvas"));
32 | canvas.width = canvas.height = 400;
33 | }
34 | let cx = canvas.getContext("2d");
35 |
36 | cx.clearRect(0, 0, canvas.width, canvas.height);
37 | let scale = new Scale(graph, canvas.width, canvas.height);
38 |
39 | // Draw the edges.
40 | cx.strokeStyle = "orange";
41 | cx.lineWidth = 3;
42 | for (let i = 0; i < graph.length; i++) {
43 | let origin = graph[i];
44 | for (let target of origin.edges) {
45 | if (graph.indexOf(target) <= i) continue;
46 | cx.beginPath();
47 | cx.moveTo(scale.x(origin.pos.x), scale.y(origin.pos.y));
48 | cx.lineTo(scale.x(target.pos.x), scale.y(target.pos.y));
49 | cx.stroke();
50 | }
51 | }
52 |
53 | // Draw the nodes.
54 | cx.fillStyle = "purple";
55 | for (let node of graph) {
56 | cx.beginPath();
57 | cx.arc(scale.x(node.pos.x), scale.y(node.pos.y), nodeSize, 0, 7);
58 | cx.fill();
59 | }
60 | }
61 |
62 | // The function starts by drawing the edges, so that they appear
63 | // behind the nodes. Since the nodes on _both_ side of an edge refer
64 | // to each other, and we don't want to draw every edge twice, edges
65 | // are only drawn then the target comes _after_ the current node in
66 | // the `graph` array.
67 |
68 | // When the edges have been drawn, the nodes are drawn on top of them
69 | // as purple discs. Remember that the last argument to `arc` gives the
70 | // rotation, and we have to pass something bigger than 2π to get a
71 | // full circle.
72 |
73 | // Finding a scale at which to draw the graph is done by finding the
74 | // top left and bottom right corners of the area taken up by the
75 | // nodes. The offset at which nodes are drawn is based on the top left
76 | // corner, and the scale is based on the size of the canvas divided by
77 | // the distance between those corners. The function reserves space
78 | // along the sides of the canvas based on the `nodeSize` variable, so
79 | // that the circles drawn around nodes’ center points don't get cut off.
80 |
81 | class Scale {
82 | constructor(graph, width, height) {
83 | let xs = graph.map(node => node.pos.x);
84 | let ys = graph.map(node => node.pos.y);
85 | let minX = Math.min(...xs);
86 | let minY = Math.min(...ys);
87 | let maxX = Math.max(...xs);
88 | let maxY = Math.max(...ys);
89 |
90 | this.offsetX = minX; this.offsetY = minY;
91 | this.scaleX = (width - 2 * nodeSize) / (maxX - minX);
92 | this.scaleY = (height - 2 * nodeSize) / (maxY - minY);
93 | }
94 |
95 | // The `x` and `y` methods convert from graph coordinates into
96 | // canvas coordinates.
97 | x(x) {
98 | return this.scaleX * (x - this.offsetX) + nodeSize;
99 | }
100 | y(y) {
101 | return this.scaleY * (y - this.offsetY) + nodeSize;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/code/file_server.js:
--------------------------------------------------------------------------------
1 | const {createServer} = require("http");
2 |
3 | const methods = Object.create(null);
4 |
5 | createServer((request, response) => {
6 | let handler = methods[request.method] || notAllowed;
7 | handler(request)
8 | .catch(error => {
9 | if (error.status != null) return error;
10 | return {body: String(error), status: 500};
11 | })
12 | .then(({body, status = 200, type = "text/plain"}) => {
13 | response.writeHead(status, {"Content-Type": type});
14 | if (body && body.pipe) body.pipe(response);
15 | else response.end(body);
16 | });
17 | }).listen(8000);
18 |
19 | async function notAllowed(request) {
20 | return {
21 | status: 405,
22 | body: `Method ${request.method} not allowed.`
23 | };
24 | }
25 |
26 | var {parse} = require("url");
27 | var {resolve, sep} = require("path");
28 |
29 | var baseDirectory = process.cwd();
30 |
31 | function urlPath(url) {
32 | let {pathname} = parse(url);
33 | let path = resolve(decodeURIComponent(pathname).slice(1));
34 | if (path != baseDirectory &&
35 | !path.startsWith(baseDirectory + sep)) {
36 | throw {status: 403, body: "Forbidden"};
37 | }
38 | return path;
39 | }
40 |
41 | const {createReadStream} = require("fs");
42 | const {stat, readdir} = require("fs").promises;
43 | const mime = require("mime");
44 |
45 | methods.GET = async function(request) {
46 | let path = urlPath(request.url);
47 | let stats;
48 | try {
49 | stats = await stat(path);
50 | } catch (error) {
51 | if (error.code != "ENOENT") throw error;
52 | else return {status: 404, body: "File not found"};
53 | }
54 | if (stats.isDirectory()) {
55 | return {body: (await readdir(path)).join("\n")};
56 | } else {
57 | return {body: createReadStream(path),
58 | type: mime.getType(path)};
59 | }
60 | };
61 |
62 | const {rmdir, unlink} = require("fs").promises;
63 |
64 | methods.DELETE = async function(request) {
65 | let path = urlPath(request.url);
66 | let stats;
67 | try {
68 | stats = await stat(path);
69 | } catch (error) {
70 | if (error.code != "ENOENT") throw error;
71 | else return {status: 204};
72 | }
73 | if (stats.isDirectory()) await rmdir(path);
74 | else await unlink(path);
75 | return {status: 204};
76 | };
77 |
78 | const {createWriteStream} = require("fs");
79 |
80 | function pipeStream(from, to) {
81 | return new Promise((resolve, reject) => {
82 | from.on("error", reject);
83 | to.on("error", reject);
84 | to.on("finish", resolve);
85 | from.pipe(to);
86 | });
87 | }
88 |
89 | methods.PUT = async function(request) {
90 | let path = urlPath(request.url);
91 | await pipeStream(request, createWriteStream(path));
92 | return {status: 204};
93 | };
94 |
95 | const {mkdir} = require("fs").promises;
96 |
97 | methods.MKCOL = async function(request) {
98 | let path = urlPath(request.url);
99 | let stats;
100 | try {
101 | stats = await stat(path);
102 | } catch (error) {
103 | if (error.code != "ENOENT") throw error;
104 | await mkdir(path);
105 | return {status: 204};
106 | }
107 | if (stats.isDirectory()) return {status: 204};
108 | else return {status: 400, body: "Not a directory"};
109 | };
110 |
--------------------------------------------------------------------------------
/code/hello.js:
--------------------------------------------------------------------------------
1 | alert("hello!");
2 |
--------------------------------------------------------------------------------
/code/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Eloquent JavaScript :: Code Sandbox
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
26 |
27 |
28 | You can use this page to download source code and solutions to
31 | exercises for the book Eloquent JavaScript, and to directly run code
32 | in the context of chapters from that book, either to solve exercises
33 | to simply play around.
34 |
35 |
36 | Chapter:
37 |
38 | run code
39 |
40 |
41 |
45 |
46 |
47 | To run this chapter's code locally, use these files:
48 |
49 |
50 |
51 |
52 | These files contain this chapter’s project code:
53 |
54 |
55 |
56 | If you've solved the exercise and want to compare your code with
57 | mine, or you really tried, but can't get your code to work,
58 | you can look at the
59 | solution (or download it ).
60 |
61 |
62 | The base environment for this chapter (if any) is available in the
63 | sandbox above, allowing you to run the chapter's examples by
64 | simply pasting them into the editor.
65 |
66 |
67 |
--------------------------------------------------------------------------------
/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.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/code/skillsharing.zip
--------------------------------------------------------------------------------
/code/skillsharing/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/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 | "ecstatic": "^3.1.0"
8 | },
9 | "licenses": [
10 | {
11 | "type": "MIT",
12 | "url": "https://opensource.org/licenses/MIT"
13 | }
14 | ],
15 | "bugs": "https://github.com/marijnh/Eloquent-JavaScript/issues",
16 | "homepage": "https://eloquentjavascript.net/21_skillsharing.html",
17 | "maintainers": [
18 | {
19 | "name": "Marijn Haverbeke",
20 | "email": "marijnh@gmail.com",
21 | "web": "https://marijnhaverbeke.nl/"
22 | }
23 | ],
24 | "repository": {
25 | "type": "git",
26 | "url": "https://github.com/marijnh/Eloquent-JavaScript.git"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/code/skillsharing/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Skill Sharing
4 |
5 |
6 | Skill Sharing
7 |
8 |
9 |
--------------------------------------------------------------------------------
/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/skillsharing/public/skillsharing_client.js:
--------------------------------------------------------------------------------
1 | function handleAction(state, action) {
2 | if (action.type == "setUser") {
3 | localStorage.setItem("userName", action.user);
4 | return Object.assign({}, state, {user: action.user});
5 | } else if (action.type == "setTalks") {
6 | return Object.assign({}, state, {talks: action.talks});
7 | } else if (action.type == "newTalk") {
8 | fetchOK(talkURL(action.title), {
9 | method: "PUT",
10 | headers: {"Content-Type": "application/json"},
11 | body: JSON.stringify({
12 | presenter: state.user,
13 | summary: action.summary
14 | })
15 | }).catch(reportError);
16 | } else if (action.type == "deleteTalk") {
17 | fetchOK(talkURL(action.talk), {method: "DELETE"})
18 | .catch(reportError);
19 | } else if (action.type == "newComment") {
20 | fetchOK(talkURL(action.talk) + "/comments", {
21 | method: "POST",
22 | headers: {"Content-Type": "application/json"},
23 | body: JSON.stringify({
24 | author: state.user,
25 | message: action.message
26 | })
27 | }).catch(reportError);
28 | }
29 | return state;
30 | }
31 |
32 | function fetchOK(url, options) {
33 | return fetch(url, options).then(response => {
34 | if (response.status < 400) return response;
35 | else throw new Error(response.statusText);
36 | });
37 | }
38 |
39 | function talkURL(title) {
40 | return "talks/" + encodeURIComponent(title);
41 | }
42 |
43 | function reportError(error) {
44 | alert(String(error));
45 | }
46 |
47 | function renderUserField(name, dispatch) {
48 | return elt("label", {}, "Your name: ", elt("input", {
49 | type: "text",
50 | value: name,
51 | onchange(event) {
52 | dispatch({type: "setUser", user: event.target.value});
53 | }
54 | }));
55 | }
56 |
57 | function elt(type, props, ...children) {
58 | let dom = document.createElement(type);
59 | if (props) Object.assign(dom, props);
60 | for (let child of children) {
61 | if (typeof child != "string") dom.appendChild(child);
62 | else dom.appendChild(document.createTextNode(child));
63 | }
64 | return dom;
65 | }
66 |
67 | function renderTalk(talk, dispatch) {
68 | return elt(
69 | "section", {className: "talk"},
70 | elt("h2", null, talk.title, " ", elt("button", {
71 | type: "button",
72 | onclick() {
73 | dispatch({type: "deleteTalk", talk: talk.title});
74 | }
75 | }, "Delete")),
76 | elt("div", null, "by ",
77 | elt("strong", null, talk.presenter)),
78 | elt("p", null, talk.summary),
79 | ...talk.comments.map(renderComment),
80 | elt("form", {
81 | onsubmit(event) {
82 | event.preventDefault();
83 | let form = event.target;
84 | dispatch({type: "newComment",
85 | talk: talk.title,
86 | message: form.elements.comment.value});
87 | form.reset();
88 | }
89 | }, elt("input", {type: "text", name: "comment"}), " ",
90 | elt("button", {type: "submit"}, "Add comment")));
91 | }
92 |
93 | function renderComment(comment) {
94 | return elt("p", {className: "comment"},
95 | elt("strong", null, comment.author),
96 | ": ", comment.message);
97 | }
98 |
99 | function renderTalkForm(dispatch) {
100 | let title = elt("input", {type: "text"});
101 | let summary = elt("input", {type: "text"});
102 | return elt("form", {
103 | onsubmit(event) {
104 | event.preventDefault();
105 | dispatch({type: "newTalk",
106 | title: title.value,
107 | summary: summary.value});
108 | event.target.reset();
109 | }
110 | }, elt("h3", null, "Submit a Talk"),
111 | elt("label", null, "Title: ", title),
112 | elt("label", null, "Summary: ", summary),
113 | elt("button", {type: "submit"}, "Submit"));
114 | }
115 |
116 | async function pollTalks(update) {
117 | let tag = undefined;
118 | for (;;) {
119 | let response;
120 | try {
121 | response = await fetchOK("/talks", {
122 | headers: tag && {"If-None-Match": tag,
123 | "Prefer": "wait=90"}
124 | });
125 | } catch (e) {
126 | console.log("Request failed: " + e);
127 | await new Promise(resolve => setTimeout(resolve, 500));
128 | continue;
129 | }
130 | if (response.status == 304) continue;
131 | tag = response.headers.get("ETag");
132 | update(await response.json());
133 | }
134 | }
135 |
136 | var SkillShareApp = class SkillShareApp {
137 | constructor(state, dispatch) {
138 | this.dispatch = dispatch;
139 | this.talkDOM = elt("div", {className: "talks"});
140 | this.dom = elt("div", null,
141 | renderUserField(state.user, dispatch),
142 | this.talkDOM,
143 | renderTalkForm(dispatch));
144 | this.syncState(state);
145 | }
146 |
147 | syncState(state) {
148 | if (state.talks != this.talks) {
149 | this.talkDOM.textContent = "";
150 | for (let talk of state.talks) {
151 | this.talkDOM.appendChild(
152 | renderTalk(talk, this.dispatch));
153 | }
154 | this.talks = state.talks;
155 | }
156 | }
157 | }
158 |
159 | function runApp() {
160 | let user = localStorage.getItem("userName") || "Anon";
161 | let state, app;
162 | function dispatch(action) {
163 | state = handleAction(state, action);
164 | app.syncState(state);
165 | }
166 |
167 | pollTalks(talks => {
168 | if (!app) {
169 | state = {user, talks};
170 | app = new SkillShareApp(state, dispatch);
171 | document.body.appendChild(app.dom);
172 | } else {
173 | dispatch({type: "setTalks", talks});
174 | }
175 | }).catch(reportError);
176 | }
177 |
178 | runApp();
179 |
--------------------------------------------------------------------------------
/code/skillsharing/router.js:
--------------------------------------------------------------------------------
1 | var {parse} = require("url");
2 |
3 | module.exports = class Router {
4 | constructor() {
5 | this.routes = [];
6 | }
7 | add(method, url, handler) {
8 | this.routes.push({method, url, handler});
9 | }
10 | resolve(context, request) {
11 | let path = parse(request.url).pathname;
12 |
13 | for (let {method, url, handler} of this.routes) {
14 | let match = url.exec(path);
15 | if (!match || request.method != method) continue;
16 | let urlParts = match.slice(1).map(decodeURIComponent);
17 | return handler(context, ...urlParts, request);
18 | }
19 | return null;
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/code/skillsharing/skillsharing_server.js:
--------------------------------------------------------------------------------
1 | var {createServer} = require("http");
2 | var Router = require("./router");
3 | var ecstatic = require("ecstatic");
4 |
5 | var router = new Router();
6 | var defaultHeaders = {"Content-Type": "text/plain"};
7 |
8 | var SkillShareServer = class SkillShareServer {
9 | constructor(talks) {
10 | this.talks = talks;
11 | this.version = 0;
12 | this.waiting = [];
13 |
14 | let fileServer = ecstatic({root: "./public"});
15 | this.server = createServer((request, response) => {
16 | let resolved = router.resolve(this, request);
17 | if (resolved) {
18 | resolved.catch(error => {
19 | if (error.status != null) return error;
20 | return {body: String(error), status: 500};
21 | }).then(({body,
22 | status = 200,
23 | headers = defaultHeaders}) => {
24 | response.writeHead(status, headers);
25 | response.end(body);
26 | });
27 | } else {
28 | fileServer(request, response);
29 | }
30 | });
31 | }
32 | start(port) {
33 | this.server.listen(port);
34 | }
35 | stop() {
36 | this.server.close();
37 | }
38 | }
39 |
40 | const talkPath = /^\/talks\/([^\/]+)$/;
41 |
42 | router.add("GET", talkPath, async (server, title) => {
43 | if (title in server.talks) {
44 | return {body: JSON.stringify(server.talks[title]),
45 | headers: {"Content-Type": "application/json"}};
46 | } else {
47 | return {status: 404, body: `No talk '${title}' found`};
48 | }
49 | });
50 |
51 | router.add("DELETE", talkPath, async (server, title) => {
52 | if (title in server.talks) {
53 | delete server.talks[title];
54 | server.updated();
55 | }
56 | return {status: 204};
57 | });
58 |
59 | function readStream(stream) {
60 | return new Promise((resolve, reject) => {
61 | let data = "";
62 | stream.on("error", reject);
63 | stream.on("data", chunk => data += chunk.toString());
64 | stream.on("end", () => resolve(data));
65 | });
66 | }
67 |
68 | router.add("PUT", talkPath,
69 | async (server, title, request) => {
70 | let requestBody = await readStream(request);
71 | let talk;
72 | try { talk = JSON.parse(requestBody); }
73 | catch (_) { return {status: 400, body: "Invalid JSON"}; }
74 |
75 | if (!talk ||
76 | typeof talk.presenter != "string" ||
77 | typeof talk.summary != "string") {
78 | return {status: 400, body: "Bad talk data"};
79 | }
80 | server.talks[title] = {title,
81 | presenter: talk.presenter,
82 | summary: talk.summary,
83 | comments: []};
84 | server.updated();
85 | return {status: 204};
86 | });
87 |
88 | router.add("POST", /^\/talks\/([^\/]+)\/comments$/,
89 | async (server, title, request) => {
90 | let requestBody = await readStream(request);
91 | let comment;
92 | try { comment = JSON.parse(requestBody); }
93 | catch (_) { return {status: 400, body: "Invalid JSON"}; }
94 |
95 | if (!comment ||
96 | typeof comment.author != "string" ||
97 | typeof comment.message != "string") {
98 | return {status: 400, body: "Bad comment data"};
99 | } else if (title in server.talks) {
100 | server.talks[title].comments.push(comment);
101 | server.updated();
102 | return {status: 204};
103 | } else {
104 | return {status: 404, body: `No talk '${title}' found`};
105 | }
106 | });
107 |
108 | SkillShareServer.prototype.talkResponse = function() {
109 | let talks = [];
110 | for (let title of Object.keys(this.talks)) {
111 | talks.push(this.talks[title]);
112 | }
113 | return {
114 | body: JSON.stringify(talks),
115 | headers: {"Content-Type": "application/json",
116 | "ETag": `"${this.version}"`}
117 | };
118 | };
119 |
120 | router.add("GET", /^\/talks$/, async (server, request) => {
121 | let tag = /"(.*)"/.exec(request.headers["if-none-match"]);
122 | let wait = /\bwait=(\d+)/.exec(request.headers["prefer"]);
123 | if (!tag || tag[1] != server.version) {
124 | return server.talkResponse();
125 | } else if (!wait) {
126 | return {status: 304};
127 | } else {
128 | return server.waitForChanges(Number(wait[1]));
129 | }
130 | });
131 |
132 | SkillShareServer.prototype.waitForChanges = function(time) {
133 | return new Promise(resolve => {
134 | this.waiting.push(resolve);
135 | setTimeout(() => {
136 | if (!this.waiting.includes(resolve)) return;
137 | this.waiting = this.waiting.filter(r => r != resolve);
138 | resolve({status: 304});
139 | }, time * 1000);
140 | });
141 | };
142 |
143 | SkillShareServer.prototype.updated = function() {
144 | this.version++;
145 | let response = this.talkResponse();
146 | this.waiting.forEach(resolve => resolve(response));
147 | this.waiting = [];
148 | };
149 |
150 | new SkillShareServer(Object.create(null)).start(8000);
151 |
--------------------------------------------------------------------------------
/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 | constructor() {
3 | this.members = [];
4 | }
5 |
6 | add(value) {
7 | if (!this.has(value)) {
8 | this.members.push(value);
9 | }
10 | }
11 |
12 | delete(value) {
13 | this.members = this.members.filter(v => v !== value);
14 | }
15 |
16 | has(value) {
17 | return this.members.includes(value);
18 | }
19 |
20 | static from(collection) {
21 | let group = new Group;
22 | for (let value of collection) {
23 | group.add(value);
24 | }
25 | return group;
26 | }
27 | }
28 |
29 | let group = Group.from([10, 20]);
30 | console.log(group.has(10));
31 | // → true
32 | console.log(group.has(30));
33 | // → false
34 | group.add(10);
35 | group.delete(10);
36 | console.log(group.has(10));
37 |
--------------------------------------------------------------------------------
/code/solutions/06_3_iterable_groups.js:
--------------------------------------------------------------------------------
1 | class Group {
2 | constructor() {
3 | this.members = [];
4 | }
5 |
6 | add(value) {
7 | if (!this.has(value)) {
8 | this.members.push(value);
9 | }
10 | }
11 |
12 | delete(value) {
13 | this.members = this.members.filter(v => v !== value);
14 | }
15 |
16 | has(value) {
17 | return this.members.includes(value);
18 | }
19 |
20 | static from(collection) {
21 | let group = new Group;
22 | for (let value of collection) {
23 | group.add(value);
24 | }
25 | return group;
26 | }
27 |
28 | [Symbol.iterator]() {
29 | return new GroupIterator(this);
30 | }
31 | }
32 |
33 | class GroupIterator {
34 | constructor(group) {
35 | this.group = group;
36 | this.position = 0;
37 | }
38 |
39 | next() {
40 | if (this.position >= this.group.members.length) {
41 | return {done: true};
42 | } else {
43 | let result = {value: this.group.members[this.position],
44 | done: false};
45 | this.position++;
46 | return result;
47 | }
48 | }
49 | }
50 |
51 | for (let value of Group.from(["a", "b", "c"])) {
52 | console.log(value);
53 | }
54 | // → a
55 | // → b
56 | // → c
57 |
--------------------------------------------------------------------------------
/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 | constructor(members) {
3 | this.members = members;
4 | }
5 |
6 | add(value) {
7 | if (this.has(value)) return this;
8 | return new PGroup(this.members.concat([value]));
9 | }
10 |
11 | delete(value) {
12 | if (!this.has(value)) return this;
13 | return new PGroup(this.members.filter(m => m !== value));
14 | }
15 |
16 | has(value) {
17 | return this.members.includes(value);
18 | }
19 | }
20 |
21 | PGroup.empty = new PGroup([]);
22 |
23 | let a = PGroup.empty.add("a");
24 | let ab = a.add("b");
25 | let b = ab.delete("a");
26 |
27 | console.log(b.has("b"));
28 | // → true
29 | console.log(a.has("b"));
30 | // → false
31 | console.log(b.has("a"));
32 | // → false
33 |
--------------------------------------------------------------------------------
/code/solutions/08_1_retry.js:
--------------------------------------------------------------------------------
1 | function MultiplicatorUnitFailure() {}
2 |
3 | function primitiveMultiply(a, b) {
4 | if (Math.random() < 0.5) {
5 | return a * b;
6 | } else {
7 | throw new MultiplicatorUnitFailure();
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 |
25 |
--------------------------------------------------------------------------------
/code/solutions/08_2_the_locked_box.js:
--------------------------------------------------------------------------------
1 | const box = {
2 | locked: true,
3 | unlock() { this.locked = false; },
4 | lock() { this.locked = true; },
5 | _content: [],
6 | get content() {
7 | if (this.locked) throw new Error("Locked!");
8 | return this._content;
9 | }
10 | };
11 |
12 | function withBoxUnlocked(body) {
13 | let locked = box.locked;
14 | if (!locked) {
15 | return body();
16 | }
17 |
18 | box.unlock();
19 | try {
20 | return body();
21 | } finally {
22 | box.lock();
23 | }
24 | }
25 |
26 | withBoxUnlocked(function() {
27 | box.content.push("gold piece");
28 | });
29 |
30 | try {
31 | withBoxUnlocked(function() {
32 | throw new Error("Pirates on the horizon! Abort!");
33 | });
34 | } catch (e) {
35 | console.log("Error raised:", e);
36 | }
37 |
38 | console.log(box.locked);
39 | // → true
40 |
--------------------------------------------------------------------------------
/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\b/,
16 | ["how delicious", "spacious room"],
17 | ["ruinous", "consciousness"]);
18 |
19 | verify(/\s[.,:;]/,
20 | ["bad punctuation ."],
21 | ["escape the dot"]);
22 |
23 | verify(/\w{7}/,
24 | ["hottentottententen"],
25 | ["no", "hotten totten tenten"]);
26 |
27 | verify(/\b[^\We]+\b/i,
28 | ["red platypus", "wobbling nest"],
29 | ["earth bed", "learning ape", "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(/(^|\W)'|'(\W|$)/g, '$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 | const {buildGraph} = require("./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 | exports.roadGraph = buildGraph(roads.map(r => r.split("-")));
14 |
--------------------------------------------------------------------------------
/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_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.prototype.hasOwnProperty.call(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 | Run
5 |
6 |
7 |
19 |
--------------------------------------------------------------------------------
/code/solutions/18_3_conways_game_of_life.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Next generation
5 | Auto run
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.js:
--------------------------------------------------------------------------------
1 | const {statSync, readdirSync, readFileSync} = require("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.js:
--------------------------------------------------------------------------------
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 | const {mkdir} = require("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.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/code/solutions/20_3_a_public_space_on_the_web.zip
--------------------------------------------------------------------------------
/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 | Save
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.js:
--------------------------------------------------------------------------------
1 | // This isn't a stand-alone file, only a redefinition of a few
2 | // fragments from skillsharing/skillsharing_server.js
3 |
4 | const {readFileSync, writeFile} = require("fs");
5 |
6 | const fileName = "./talks.json";
7 |
8 | function loadTalks() {
9 | let json;
10 | try {
11 | json = JSON.parse(readFileSync(fileName, "utf8"));
12 | } catch (e) {
13 | json = {};
14 | }
15 | return Object.assign(Object.create(null), json);
16 | }
17 |
18 | SkillShareServer.prototype.updated = function() {
19 | this.version++;
20 | let response = this.talkResponse();
21 | this.waiting.forEach(resolve => resolve(response));
22 | this.waiting = [];
23 |
24 | writeFile(fileName, JSON.stringify(this.talks));
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.js:
--------------------------------------------------------------------------------
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 cmp = this.talkMap[talk.title];
59 | if (cmp && cmp.talk.author == talk.author &&
60 | cmp.talk.summary == talk.summary) {
61 | cmp.syncState(talk);
62 | } else {
63 | if (cmp) cmp.dom.remove();
64 | cmp = new Talk(talk, this.dispatch);
65 | this.talkMap[talk.title] = cmp;
66 | this.talkDOM.appendChild(cmp.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_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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/empty.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/errata.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Eloquent JavaScript :: Errata
6 |
7 |
8 |
10 |
11 |
12 |
13 |
14 | These are the known mistakes in the third edition
17 | of the book. For errata in the first edition,
18 | see this
19 | page . For the second edition,
20 | see this
21 | page . To report a problem that is not listed
22 | here, send me an email .
23 |
24 |
25 | Chapter 2
26 |
27 | Page 34 (Updating Bindings Succintly): Where it
28 | says counter-
it should be counter--
.
29 |
30 | Chapter 20
31 |
32 | Page 369 (Directory Creation): `MKCOL` stands for
33 | "make collection", not "make column" as the book claims.
34 |
35 |
36 |
--------------------------------------------------------------------------------
/example/bert.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Bert",
3 | "spouse": "example/suzie.json"
4 | }
5 |
--------------------------------------------------------------------------------
/example/data.txt:
--------------------------------------------------------------------------------
1 | This is the content of data.txt
2 |
--------------------------------------------------------------------------------
/example/fruit.json:
--------------------------------------------------------------------------------
1 | {"banana": "yellow",
2 | "lemon": "yellow",
3 | "cherry": "red"}
4 |
--------------------------------------------------------------------------------
/example/fruit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/example/message.html:
--------------------------------------------------------------------------------
1 |
2 | Example form target
3 |
4 |
5 |
6 |
7 |
Hello , we've received your message:
8 |
9 |
10 |
11 |
No message received.
12 |
13 |
14 | (Note that this page is just an illustration. No actual message was delivered anywhere.)
15 |
16 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/example/muriel.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Muriel"
3 | }
4 |
--------------------------------------------------------------------------------
/example/submit.html:
--------------------------------------------------------------------------------
1 |
2 | Example form target
3 |
4 |
5 |
6 | You submitted...
7 |
8 |
9 |
10 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/example/suzie.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Suzie",
3 | "spouse": "example/bert.json",
4 | "mother": "example/muriel.json"
5 | }
6 |
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/favicon.ico
--------------------------------------------------------------------------------
/font/Shabnam-Bold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam-Bold.eot
--------------------------------------------------------------------------------
/font/Shabnam-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam-Bold.ttf
--------------------------------------------------------------------------------
/font/Shabnam-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam-Bold.woff
--------------------------------------------------------------------------------
/font/Shabnam-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam-Bold.woff2
--------------------------------------------------------------------------------
/font/Shabnam-Light.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam-Light.eot
--------------------------------------------------------------------------------
/font/Shabnam-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam-Light.ttf
--------------------------------------------------------------------------------
/font/Shabnam-Light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam-Light.woff
--------------------------------------------------------------------------------
/font/Shabnam-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam-Light.woff2
--------------------------------------------------------------------------------
/font/Shabnam.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam.eot
--------------------------------------------------------------------------------
/font/Shabnam.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam.ttf
--------------------------------------------------------------------------------
/font/Shabnam.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam.woff
--------------------------------------------------------------------------------
/font/Shabnam.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/Shabnam.woff2
--------------------------------------------------------------------------------
/font/cinzel_bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/cinzel_bold.woff
--------------------------------------------------------------------------------
/font/pt_mono.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/font/pt_mono.woff
--------------------------------------------------------------------------------
/img/Hieres-sur-Amby.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/Hieres-sur-Amby.png
--------------------------------------------------------------------------------
/img/blockquote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/blockquote.png
--------------------------------------------------------------------------------
/img/boxed-in.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/boxed-in.png
--------------------------------------------------------------------------------
/img/button_disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/button_disabled.png
--------------------------------------------------------------------------------
/img/canvas_beziercurve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_beziercurve.png
--------------------------------------------------------------------------------
/img/canvas_circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_circle.png
--------------------------------------------------------------------------------
/img/canvas_fill.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_fill.png
--------------------------------------------------------------------------------
/img/canvas_game.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_game.png
--------------------------------------------------------------------------------
/img/canvas_path.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_path.png
--------------------------------------------------------------------------------
/img/canvas_pie_chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_pie_chart.png
--------------------------------------------------------------------------------
/img/canvas_quadraticcurve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_quadraticcurve.png
--------------------------------------------------------------------------------
/img/canvas_scale.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_scale.png
--------------------------------------------------------------------------------
/img/canvas_stroke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_stroke.png
--------------------------------------------------------------------------------
/img/canvas_tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_tree.png
--------------------------------------------------------------------------------
/img/canvas_triangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/canvas_triangle.png
--------------------------------------------------------------------------------
/img/cat-animation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/cat-animation.png
--------------------------------------------------------------------------------
/img/cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/cat.png
--------------------------------------------------------------------------------
/img/chapter_picture_00.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_00.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_1.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_10.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_11.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_11.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_12.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_13.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_13.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_14.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_14.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_15.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_15.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_16.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_16.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_17.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_17.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_18.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_18.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_19.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_19.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_2.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_20.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_20.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_21.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_21.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_3.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_4.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_5.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_6.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_7.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_8.jpg
--------------------------------------------------------------------------------
/img/chapter_picture_9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/chapter_picture_9.jpg
--------------------------------------------------------------------------------
/img/color-field.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/color-field.png
--------------------------------------------------------------------------------
/img/colored-links.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/colored-links.png
--------------------------------------------------------------------------------
/img/control-io.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 | synchronous, single thread of control synchronous, two threads of control asynchronous
11 |
--------------------------------------------------------------------------------
/img/controlflow-if.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
20 |
27 |
33 |
34 |
41 |
47 |
48 |
55 |
61 |
62 |
69 |
75 |
76 |
77 |
99 |
101 |
102 |
104 | image/svg+xml
105 |
107 |
108 |
109 |
110 |
111 |
116 |
119 |
125 |
131 |
137 |
143 |
153 |
154 |
155 |
156 |
--------------------------------------------------------------------------------
/img/controlflow-loop.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
20 |
27 |
33 |
34 |
41 |
47 |
48 |
55 |
61 |
62 |
69 |
75 |
76 |
77 |
101 |
103 |
104 |
106 | image/svg+xml
107 |
109 |
110 |
111 |
112 |
113 |
118 |
124 |
130 |
136 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/img/controlflow-straight.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
20 |
27 |
33 |
34 |
35 |
57 |
59 |
60 |
62 | image/svg+xml
63 |
65 |
66 |
67 |
68 |
69 |
74 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/img/cos_sin.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 | cos(¼π) sin(¼π) cos(-⅔π) sin(-⅔π)
12 |
--------------------------------------------------------------------------------
/img/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/cover.jpg
--------------------------------------------------------------------------------
/img/darkblue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/darkblue.png
--------------------------------------------------------------------------------
/img/display.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/display.png
--------------------------------------------------------------------------------
/img/drag-bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/drag-bar.png
--------------------------------------------------------------------------------
/img/exercise_shapes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/exercise_shapes.png
--------------------------------------------------------------------------------
/img/flood-grid.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/img/form_fields.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/form_fields.png
--------------------------------------------------------------------------------
/img/form_select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/form_select.png
--------------------------------------------------------------------------------
/img/game-grid.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/img/game_simpleLevel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/game_simpleLevel.png
--------------------------------------------------------------------------------
/img/generated/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/generated/.keep
--------------------------------------------------------------------------------
/img/ghostery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/ghostery.png
--------------------------------------------------------------------------------
/img/ghostery_mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/ghostery_mini.png
--------------------------------------------------------------------------------
/img/hack_reactor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/hack_reactor.png
--------------------------------------------------------------------------------
/img/hack_reactor_mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/hack_reactor_mini.png
--------------------------------------------------------------------------------
/img/hat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/hat.png
--------------------------------------------------------------------------------
/img/help-field.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/help-field.png
--------------------------------------------------------------------------------
/img/highlighted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/highlighted.png
--------------------------------------------------------------------------------
/img/holberton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/holberton.png
--------------------------------------------------------------------------------
/img/home-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/home-page.png
--------------------------------------------------------------------------------
/img/html-boxes.svg:
--------------------------------------------------------------------------------
1 |
2 |
23 | here a . I also wrote a book! Read it p Hello, I am Marijn and this is... p My home page h1 body My home page title head html
24 |
--------------------------------------------------------------------------------
/img/html-links.svg:
--------------------------------------------------------------------------------
1 |
2 |
25 | I also wrote a book! ... p Hello, I am Marijn... p My home page h1 body 0 1 2 childNodes firstChild lastChild previousSibling nextSibling parentNode
26 |
--------------------------------------------------------------------------------
/img/line-grid.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/img/linked-list.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 | value: 1 rest: value: 2 rest: value: 3 rest: null
14 |
--------------------------------------------------------------------------------
/img/middle_east_graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/middle_east_graph.png
--------------------------------------------------------------------------------
/img/middle_east_graph_random.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/middle_east_graph_random.png
--------------------------------------------------------------------------------
/img/mirror.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 | mirror 1 2 3 4
12 |
--------------------------------------------------------------------------------
/img/mozilla.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/mozilla.png
--------------------------------------------------------------------------------
/img/mozilla_mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/mozilla_mini.png
--------------------------------------------------------------------------------
/img/nextjournal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/nextjournal.png
--------------------------------------------------------------------------------
/img/object.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/object.jpg
--------------------------------------------------------------------------------
/img/object_full.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/object_full.jpg
--------------------------------------------------------------------------------
/img/ostrich.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/ostrich.png
--------------------------------------------------------------------------------
/img/parcel2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/parcel2x.png
--------------------------------------------------------------------------------
/img/pixel_editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/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 pizza 76 Squirrel, no pizza 4 No squirrel, pizza 9 Squirrel, pizza 1
--------------------------------------------------------------------------------
/img/player.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/player.png
--------------------------------------------------------------------------------
/img/player_big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/player_big.png
--------------------------------------------------------------------------------
/img/prompt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/prompt.png
--------------------------------------------------------------------------------
/img/rabbits.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 | toString: <function> ... teeth: "small" speak: <function> killerRabbit teeth: "long, sharp, ..." type: "killer" Rabbit prototype Object create: <function> prototype ...
14 |
--------------------------------------------------------------------------------
/img/robot_idle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/robot_idle.png
--------------------------------------------------------------------------------
/img/robot_idle2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/robot_idle2x.png
--------------------------------------------------------------------------------
/img/robot_moving.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/robot_moving.gif
--------------------------------------------------------------------------------
/img/robot_moving2x.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/robot_moving2x.gif
--------------------------------------------------------------------------------
/img/skillsharing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/skillsharing.png
--------------------------------------------------------------------------------
/img/sprites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/sprites.png
--------------------------------------------------------------------------------
/img/sprites_big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/sprites_big.png
--------------------------------------------------------------------------------
/img/svg-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/svg-demo.png
--------------------------------------------------------------------------------
/img/syntax_tree.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 | do define x 10 if > x 5 print "large" print "small"
--------------------------------------------------------------------------------
/img/tamil.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/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/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/tree_graph.png
--------------------------------------------------------------------------------
/img/village.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/village.png
--------------------------------------------------------------------------------
/img/village2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/village2x.png
--------------------------------------------------------------------------------
/img/weresquirrel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/img/weresquirrel.png
--------------------------------------------------------------------------------
/js/.tern-project:
--------------------------------------------------------------------------------
1 | {
2 | "libs": ["browser"]
3 | }
--------------------------------------------------------------------------------
/js/chapter_info.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emehran/persian-eloquent-javascript/b4075f261e0f994fedbc866843ba382fda152af8/js/chapter_info.js
--------------------------------------------------------------------------------