├── img ├── eyes.jpg ├── heap.png ├── html.png ├── tree.png ├── height.png ├── queens.png ├── scenic.png ├── sketch.png ├── Hiva Oa.png ├── TadaOnIOS.png ├── WebHeader.png ├── ostrich.jpg ├── CoverCoffee.jpg ├── TadaOnWin7.png ├── objectchain.png ├── TadaOnMacOSX.png ├── diagonalpath.png ├── environments.png └── License.text ├── src ├── prelude │ ├── package.json │ ├── coffeekup-LICENSE │ ├── qc-COPYING │ ├── coffeekup.coffee │ └── ws.coffee ├── 10-CircularTest.coffee ├── A3-Microbench.coffee ├── A3-Microtest.py ├── 01-Introduction.coffee ├── 06-Quote.html ├── 10-Circular.coffee ├── 10-MathFix.coffee ├── node_modules │ └── LICENSE ├── A3-Microrun.cpp ├── 00-Foreword.coffee ├── A3-Queen.coffee ├── A3-Queens.py ├── A2-BinaryHeapTest.coffee ├── 10-Modularity.coffee ├── 10-SeedLife.coffee ├── A4-NoSolutions.coffee ├── 10-TestWebSockets.coffee ├── 04-emails.coffee ├── 10-WebSocketLife.coffee ├── 05-ErrorHandling.coffee ├── A1-LanguageExtras.coffee ├── A2-BinaryHeap.coffee ├── 06-RecluseFile.text ├── 09-RegularExpressions.coffee ├── docs │ ├── 10-CircularTest.html │ ├── A3-Microbench.html │ ├── A3-Microtest.html │ └── docco.css ├── 03-Functions.coffee └── 08-ObjectOrientation.coffee ├── src-no-solutions ├── prelude │ ├── package.json │ ├── coffeekup-LICENSE │ ├── qc-COPYING │ ├── coffeekup.coffee │ └── ws.coffee ├── 10-CircularTest.coffee ├── A3-Microbench.coffee ├── A3-Microtest.py ├── 01-Introduction.coffee ├── 06-Quote.html ├── 10-Circular.coffee ├── 10-MathFix.coffee ├── node_modules │ └── LICENSE ├── A3-Microrun.cpp ├── 00-Foreword.coffee ├── A3-Queen.coffee ├── A3-Queens.py ├── A2-BinaryHeapTest.coffee ├── 10-Modularity.coffee ├── 10-SeedLife.coffee ├── A4-NoSolutions.coffee ├── 10-TestWebSockets.coffee ├── 04-emails.coffee ├── 10-WebSocketLife.coffee ├── 05-ErrorHandling.coffee ├── A1-LanguageExtras.coffee ├── A2-BinaryHeap.coffee ├── 09-RegularExpressions.coffee ├── 06-RecluseFile.text ├── docs │ ├── 10-CircularTest.html │ ├── A3-Microbench.html │ ├── A3-Microtest.html │ └── docco.css └── 03-Functions.coffee ├── Smooth CoffeeScript.pdf ├── Smooth CoffeeScript - No Solutions.pdf ├── readme.md └── changes.md /img/eyes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/img/eyes.jpg -------------------------------------------------------------------------------- /img/heap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/img/heap.png -------------------------------------------------------------------------------- /img/html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/img/html.png -------------------------------------------------------------------------------- /img/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/img/tree.png -------------------------------------------------------------------------------- /src/prelude/package.json: -------------------------------------------------------------------------------- 1 | { "name" : "prelude", 2 | "main" : "./prelude.coffee" } 3 | -------------------------------------------------------------------------------- /img/height.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/img/height.png -------------------------------------------------------------------------------- /img/queens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/img/queens.png -------------------------------------------------------------------------------- /img/scenic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/img/scenic.png -------------------------------------------------------------------------------- /img/sketch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/img/sketch.png -------------------------------------------------------------------------------- /img/Hiva Oa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/img/Hiva Oa.png -------------------------------------------------------------------------------- /img/TadaOnIOS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/img/TadaOnIOS.png -------------------------------------------------------------------------------- /img/WebHeader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/img/WebHeader.png -------------------------------------------------------------------------------- /img/ostrich.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/img/ostrich.jpg -------------------------------------------------------------------------------- /img/CoverCoffee.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/img/CoverCoffee.jpg -------------------------------------------------------------------------------- /img/TadaOnWin7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/img/TadaOnWin7.png -------------------------------------------------------------------------------- /img/objectchain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/img/objectchain.png -------------------------------------------------------------------------------- /src-no-solutions/prelude/package.json: -------------------------------------------------------------------------------- 1 | { "name" : "prelude", 2 | "main" : "./prelude.coffee" } 3 | -------------------------------------------------------------------------------- /img/TadaOnMacOSX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/img/TadaOnMacOSX.png -------------------------------------------------------------------------------- /img/diagonalpath.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/img/diagonalpath.png -------------------------------------------------------------------------------- /img/environments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/img/environments.png -------------------------------------------------------------------------------- /Smooth CoffeeScript.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/Smooth CoffeeScript.pdf -------------------------------------------------------------------------------- /Smooth CoffeeScript - No Solutions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/Smooth-CoffeeScript/master/Smooth CoffeeScript - No Solutions.pdf -------------------------------------------------------------------------------- /src/10-CircularTest.coffee: -------------------------------------------------------------------------------- 1 | cp = require "./10-Circular" 2 | show = console.log 3 | 4 | circ = new cp.CircularPosition 0.01 5 | for i in [0...6] 6 | show "#{i}: #{circ.nextPoint()}" 7 | 8 | try 9 | show "Instantiating a new Point:" 10 | p = new cp.Point 0, 0 11 | show "Created a Point" 12 | catch e then show e.message 13 | 14 | show "CircularPosition namespace:" 15 | show cp 16 | -------------------------------------------------------------------------------- /src-no-solutions/10-CircularTest.coffee: -------------------------------------------------------------------------------- 1 | cp = require "./10-Circular" 2 | show = console.log 3 | 4 | circ = new cp.CircularPosition 0.01 5 | for i in [0...6] 6 | show "#{i}: #{circ.nextPoint()}" 7 | 8 | try 9 | show "Instantiating a new Point:" 10 | p = new cp.Point 0, 0 11 | show "Created a Point" 12 | catch e then show e.message 13 | 14 | show "CircularPosition namespace:" 15 | show cp 16 | -------------------------------------------------------------------------------- /src/A3-Microbench.coffee: -------------------------------------------------------------------------------- 1 | # CoffeeScript 2 | show = console.log 3 | 4 | start = new Date() 5 | N = 1000000 6 | a = Array(N) 7 | for i in [0...N] 8 | a[i] = Math.random() 9 | 10 | s = 0 11 | for v in a 12 | s += v 13 | 14 | t = 0 15 | for v in a 16 | t += v*v 17 | t = Math.sqrt t 18 | 19 | duration = new Date() - start 20 | show "N: #{N} in #{duration*0.001} s" 21 | show "Result: #{s} and #{t}" 22 | -------------------------------------------------------------------------------- /src/A3-Microtest.py: -------------------------------------------------------------------------------- 1 | # CPython 2 | import time 3 | import random 4 | import math 5 | 6 | start = time.clock() 7 | N = 1000000 8 | a = [random.random() 9 | for i in range(N)] 10 | 11 | s = 0 12 | for v in a: 13 | s += v 14 | 15 | t = 0 16 | for v in a: 17 | t += v*v 18 | t = math.sqrt(t) 19 | 20 | duration = time.clock() - start 21 | print 'N:', N, 'in', duration, 's' 22 | print 'Result:', s, 'and', t 23 | -------------------------------------------------------------------------------- /src-no-solutions/A3-Microbench.coffee: -------------------------------------------------------------------------------- 1 | # CoffeeScript 2 | show = console.log 3 | 4 | start = new Date() 5 | N = 1000000 6 | a = Array(N) 7 | for i in [0...N] 8 | a[i] = Math.random() 9 | 10 | s = 0 11 | for v in a 12 | s += v 13 | 14 | t = 0 15 | for v in a 16 | t += v*v 17 | t = Math.sqrt t 18 | 19 | duration = new Date() - start 20 | show "N: #{N} in #{duration*0.001} s" 21 | show "Result: #{s} and #{t}" 22 | -------------------------------------------------------------------------------- /src-no-solutions/A3-Microtest.py: -------------------------------------------------------------------------------- 1 | # CPython 2 | import time 3 | import random 4 | import math 5 | 6 | start = time.clock() 7 | N = 1000000 8 | a = [random.random() 9 | for i in range(N)] 10 | 11 | s = 0 12 | for v in a: 13 | s += v 14 | 15 | t = 0 16 | for v in a: 17 | t += v*v 18 | t = math.sqrt(t) 19 | 20 | duration = time.clock() - start 21 | print 'N:', N, 'in', duration, 's' 22 | print 'Result:', s, 'and', t 23 | -------------------------------------------------------------------------------- /src/01-Introduction.coffee: -------------------------------------------------------------------------------- 1 | require './prelude' 2 | 3 | # Summation using a while loop 4 | show '--- Summation using a while loop ---' 5 | total = 0 6 | count = 1 7 | while count <= 10 8 | total += count 9 | count += 1 10 | show total 11 | 12 | 13 | # Summation using a for loop 14 | show '--- Summation using a for loop ---' 15 | total = 0 16 | total += count for count in [1..10] 17 | show total 18 | 19 | 20 | # Summation using a reduce function 21 | show '--- Summation using a reduce function ---' 22 | sum = (v) -> _.reduce v, ((a,b) -> a+b), 0 23 | show sum [1..10] 24 | 25 | 26 | # Summation using reduce attached to Array 27 | show '--- Summation using reduce attached to Array ---' 28 | Array::sum ?= -> @reduce ((a,b) -> a+b), 0 29 | show [1..10].sum() 30 | 31 | -------------------------------------------------------------------------------- /src-no-solutions/01-Introduction.coffee: -------------------------------------------------------------------------------- 1 | require './prelude' 2 | 3 | # Summation using a while loop 4 | show '--- Summation using a while loop ---' 5 | total = 0 6 | count = 1 7 | while count <= 10 8 | total += count 9 | count += 1 10 | show total 11 | 12 | 13 | # Summation using a for loop 14 | show '--- Summation using a for loop ---' 15 | total = 0 16 | total += count for count in [1..10] 17 | show total 18 | 19 | 20 | # Summation using a reduce function 21 | show '--- Summation using a reduce function ---' 22 | sum = (v) -> _.reduce v, ((a,b) -> a+b), 0 23 | show sum [1..10] 24 | 25 | 26 | # Summation using reduce attached to Array 27 | show '--- Summation using reduce attached to Array ---' 28 | Array::sum ?= -> @reduce ((a,b) -> a+b), 0 29 | show [1..10].sum() 30 | 31 | -------------------------------------------------------------------------------- /src/06-Quote.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | A quote 6 | 7 | 8 |

A quote

9 |
10 |

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

15 |

-- Bjarne Stroustrup

16 |
17 |

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

20 |

Also, here is a picture of an ostrich:

21 | 22 | 23 | -------------------------------------------------------------------------------- /src-no-solutions/06-Quote.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | A quote 6 | 7 | 8 |

A quote

9 |
10 |

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

15 |

-- Bjarne Stroustrup

16 |
17 |

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

20 |

Also, here is a picture of an ostrich:

21 | 22 | 23 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![Smooth CoffeeScript](https://github.com/autotelicum/Smooth-CoffeeScript/raw/master/img/WebHeader.png) 2 | 3 | **Smooth CoffeeScript** is a free book about CoffeeScript and programming. No previous programming knowledge is required. Over 200 pages and 35 exercises. Download includes book and full source code with and without solutions. The samples require a functioning installation of CoffeeScript; see page 9 in the book. Tested on Mac OS X 10.6.7 and on Windows 7. 4 | 5 | More information: 6 | [Book webpage](http://autotelicum.github.com/Smooth-CoffeeScript/). 7 | [Online preview](http://issuu.com/autotelicum/docs/smooth_coffeescript). 8 | 9 | This work is licensed under a [Creative Commons Attribution 3.0 Unported License](http://creativecommons.org/licenses/by/3.0/). 10 | 11 | Created by E. Hoigaard based on [Eloquent JavaScript by Marijn Haverbeke](http://eloquentjavascript.net/). 12 | -------------------------------------------------------------------------------- /src/10-Circular.coffee: -------------------------------------------------------------------------------- 1 | 2 | Pi2 = Math.PI*2 3 | # Create an array with angles on the unit circle. 4 | angles = (angle for angle in [0...Pi2] by 1/3*Math.PI) 5 | # Remove the last element if 2*PI were included 6 | # due to floating point rounding on additions. 7 | epsilon = 1e-14 8 | lastAngle = angles[angles.length - 1] 9 | # Use an interval to test floating point value 10 | if Pi2 - epsilon < lastAngle < Pi2 + epsilon 11 | angles.length = angles.length - 1 12 | 13 | # Encapsulation of a pair of (x, y) coordinates 14 | class Point 15 | constructor: (@x, @y) -> 16 | toString: -> "{x:#{@x.toPrecision 4}," + 17 | " y:#{@y.toPrecision 4}}" 18 | 19 | # Math class that returns points on the unit 20 | # circle, offset by step if given non-zero. 21 | class CircularPosition 22 | constructor: (@_step = 0) -> @_count = 0 23 | nextPoint: -> 24 | index = @_count % angles.length 25 | angle = angles[index] + @_step * @_count++ 26 | new Point Math.cos(angle), Math.sin(angle) 27 | 28 | (exports ? this).CircularPosition = CircularPosition 29 | -------------------------------------------------------------------------------- /src-no-solutions/10-Circular.coffee: -------------------------------------------------------------------------------- 1 | 2 | Pi2 = Math.PI*2 3 | # Create an array with angles on the unit circle. 4 | angles = (angle for angle in [0...Pi2] by 1/3*Math.PI) 5 | # Remove the last element if 2*PI were included 6 | # due to floating point rounding on additions. 7 | epsilon = 1e-14 8 | lastAngle = angles[angles.length - 1] 9 | # Use an interval to test floating point value 10 | if Pi2 - epsilon < lastAngle < Pi2 + epsilon 11 | angles.length = angles.length - 1 12 | 13 | # Encapsulation of a pair of (x, y) coordinates 14 | class Point 15 | constructor: (@x, @y) -> 16 | toString: -> "{x:#{@x.toPrecision 4}," + 17 | " y:#{@y.toPrecision 4}}" 18 | 19 | # Math class that returns points on the unit 20 | # circle, offset by step if given non-zero. 21 | class CircularPosition 22 | constructor: (@_step = 0) -> @_count = 0 23 | nextPoint: -> 24 | index = @_count % angles.length 25 | angle = angles[index] + @_step * @_count++ 26 | new Point Math.cos(angle), Math.sin(angle) 27 | 28 | (exports ? this).CircularPosition = CircularPosition 29 | -------------------------------------------------------------------------------- /src/10-MathFix.coffee: -------------------------------------------------------------------------------- 1 | require "./prelude" 2 | 3 | Pi2 = Math.PI*2 4 | # Create an array with angles on the unit circle. 5 | angles = (angle for angle in [0...Pi2] by 1/3*Math.PI) 6 | # Remove the last element if 2*PI were included 7 | # due to floating point rounding on additions. 8 | epsilon = 1e-14 9 | lastAngle = angles[angles.length - 1] 10 | # Use an interval to test floating point value 11 | if Pi2 - epsilon < lastAngle < Pi2 + epsilon 12 | angles.length = angles.length - 1 13 | 14 | # Encapsulation of a pair of (x, y) coordinates 15 | class Point 16 | constructor: (@x, @y) -> 17 | toString: -> "{x:#{@x.toPrecision 4}," + 18 | " y:#{@y.toPrecision 4}}" 19 | 20 | # Magical class that returns points on the unit 21 | # circle, offset by step if given non-zero. 22 | class CircularPosition 23 | constructor: (@_step = 0) -> @_count = 0 24 | nextPoint: -> 25 | index = @_count % angles.length 26 | angle = angles[index] + @_step * @_count++ 27 | new Point Math.cos(angle), Math.sin(angle) 28 | 29 | circ = new CircularPosition 0.01 30 | for i in [0...6] 31 | show "#{i}: #{circ.nextPoint()}" -------------------------------------------------------------------------------- /src-no-solutions/10-MathFix.coffee: -------------------------------------------------------------------------------- 1 | require "./prelude" 2 | 3 | Pi2 = Math.PI*2 4 | # Create an array with angles on the unit circle. 5 | angles = (angle for angle in [0...Pi2] by 1/3*Math.PI) 6 | # Remove the last element if 2*PI were included 7 | # due to floating point rounding on additions. 8 | epsilon = 1e-14 9 | lastAngle = angles[angles.length - 1] 10 | # Use an interval to test floating point value 11 | if Pi2 - epsilon < lastAngle < Pi2 + epsilon 12 | angles.length = angles.length - 1 13 | 14 | # Encapsulation of a pair of (x, y) coordinates 15 | class Point 16 | constructor: (@x, @y) -> 17 | toString: -> "{x:#{@x.toPrecision 4}," + 18 | " y:#{@y.toPrecision 4}}" 19 | 20 | # Magical class that returns points on the unit 21 | # circle, offset by step if given non-zero. 22 | class CircularPosition 23 | constructor: (@_step = 0) -> @_count = 0 24 | nextPoint: -> 25 | index = @_count % angles.length 26 | angle = angles[index] + @_step * @_count++ 27 | new Point Math.cos(angle), Math.sin(angle) 28 | 29 | circ = new CircularPosition 0.01 30 | for i in [0...6] 31 | show "#{i}: #{circ.nextPoint()}" -------------------------------------------------------------------------------- /src/node_modules/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Jeremy Ashkenas 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src-no-solutions/node_modules/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Jeremy Ashkenas 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/prelude/coffeekup-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Maurice Machado 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/A3-Microrun.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Pointer-free version using valarray template. 3 | // LLVM: clang++ -O3 -std=c++0x A3-Microrun.cpp -o mb; ./mb 4 | // Visual 2010 C++: cl /Ox /Ob2 /Oi /Ot /Oy- /Ox /Ob2 /Oi /Ot /Oy- /EHsc A3-Microrun.cpp 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | inline double rand01() { 15 | return static_cast(rand()) / 16 | static_cast(RAND_MAX); 17 | } 18 | 19 | void test() { 20 | clock_t start = clock(); 21 | 22 | static const size_t N = 1000000; 23 | valarray a(N); 24 | for (size_t i = 0; i < N; ++i) 25 | a[i] = rand01(); 26 | 27 | double s = a.sum(); 28 | double t = sqrt((a*a).sum()); 29 | 30 | double duration = (clock() - start) / 31 | static_cast(CLOCKS_PER_SEC); 32 | cout << "N: " << N << " in " << duration << "s" << endl; 33 | cout << "Result: " << s << " and " << t << endl; 34 | } 35 | 36 | int main() { 37 | srand(static_cast(time(NULL))); 38 | test(); 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /src-no-solutions/prelude/coffeekup-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Maurice Machado 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src-no-solutions/A3-Microrun.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Pointer-free version using valarray template. 3 | // LLVM: clang++ -O3 -std=c++0x A3-Microrun.cpp -o mb; ./mb 4 | // Visual 2010 C++: cl /Ox /Ob2 /Oi /Ot /Oy- /Ox /Ob2 /Oi /Ot /Oy- /EHsc A3-Microrun.cpp 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | inline double rand01() { 15 | return static_cast(rand()) / 16 | static_cast(RAND_MAX); 17 | } 18 | 19 | void test() { 20 | clock_t start = clock(); 21 | 22 | static const size_t N = 1000000; 23 | valarray a(N); 24 | for (size_t i = 0; i < N; ++i) 25 | a[i] = rand01(); 26 | 27 | double s = a.sum(); 28 | double t = sqrt((a*a).sum()); 29 | 30 | double duration = (clock() - start) / 31 | static_cast(CLOCKS_PER_SEC); 32 | cout << "N: " << N << " in " << duration << "s" << endl; 33 | cout << "Result: " << s << " and " << t << endl; 34 | } 35 | 36 | int main() { 37 | srand(static_cast(time(NULL))); 38 | test(); 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /src/00-Foreword.coffee: -------------------------------------------------------------------------------- 1 | require './prelude' 2 | # Client-side web page with canvas 3 | webpage = kup.render -> 4 | doctype 5 5 | html -> 6 | head -> 7 | meta charset: 'utf-8' 8 | title 'My drawing | My awesome website' 9 | style ''' 10 | body {font-family: sans-serif} 11 | header, nav, section, footer {display: block} 12 | ''' 13 | coffeescript -> 14 | draw = (ctx, x, y) -> 15 | circle = (ctx, x, y) -> 16 | ctx.beginPath() 17 | ctx.arc x, y, 100, 0, 2*Math.PI, false 18 | ctx.stroke() 19 | ctx.strokeStyle = 'rgba(255,40,20,0.7)' 20 | circle ctx, x, y 21 | for angle in [0...2*Math.PI] by 1/3*Math.PI 22 | circle ctx, x+100*Math.cos(angle), 23 | y+100*Math.sin(angle) 24 | window.onload = -> 25 | canvas = document.getElementById 'drawCanvas' 26 | context = canvas.getContext '2d' 27 | draw context, 300, 200 28 | body -> 29 | header -> h1 'Seed of Life' 30 | canvas id: 'drawCanvas', width: 600, height: 400 31 | # Server-side HTTP server 32 | http = require 'http' 33 | server = http.createServer (req, res) -> 34 | show "#{req.client.remoteAddress} #{req.method} #{req.url}" 35 | res.writeHead 200, 'Content-Type': 'text/html' 36 | res.write webpage 37 | res.end() 38 | server.listen 3389 39 | show 'Server running at' 40 | show server.address() -------------------------------------------------------------------------------- /src-no-solutions/00-Foreword.coffee: -------------------------------------------------------------------------------- 1 | require './prelude' 2 | # Client-side web page with canvas 3 | webpage = kup.render -> 4 | doctype 5 5 | html -> 6 | head -> 7 | meta charset: 'utf-8' 8 | title 'My drawing | My awesome website' 9 | style ''' 10 | body {font-family: sans-serif} 11 | header, nav, section, footer {display: block} 12 | ''' 13 | coffeescript -> 14 | draw = (ctx, x, y) -> 15 | circle = (ctx, x, y) -> 16 | ctx.beginPath() 17 | ctx.arc x, y, 100, 0, 2*Math.PI, false 18 | ctx.stroke() 19 | ctx.strokeStyle = 'rgba(255,40,20,0.7)' 20 | circle ctx, x, y 21 | for angle in [0...2*Math.PI] by 1/3*Math.PI 22 | circle ctx, x+100*Math.cos(angle), 23 | y+100*Math.sin(angle) 24 | window.onload = -> 25 | canvas = document.getElementById 'drawCanvas' 26 | context = canvas.getContext '2d' 27 | draw context, 300, 200 28 | body -> 29 | header -> h1 'Seed of Life' 30 | canvas id: 'drawCanvas', width: 600, height: 400 31 | # Server-side HTTP server 32 | http = require 'http' 33 | server = http.createServer (req, res) -> 34 | show "#{req.client.remoteAddress} #{req.method} #{req.url}" 35 | res.writeHead 200, 'Content-Type': 'text/html' 36 | res.write webpage 37 | res.end() 38 | server.listen 3389 39 | show 'Server running at' 40 | show server.address() -------------------------------------------------------------------------------- /src/A3-Queen.coffee: -------------------------------------------------------------------------------- 1 | # CoffeeScript encoding: utf-8 2 | start = new Date() 3 | show = console.log 4 | 5 | # Create variations to try 6 | permute = (L) -> 7 | n = L.length 8 | return ([elem] for elem in L) if n is 1 9 | [a, L] = [ [L[0]], L.slice 1 ] 10 | result = [] 11 | for p in permute L 12 | for i in [0...n] 13 | result.push p[...i].concat a, p[i...] 14 | result 15 | 16 | # Check a variation 17 | test = (p, n) -> 18 | for i in [0...n - 1] 19 | for j in [i + 1...n] 20 | d = p[i] - p[j] 21 | return true if j - i == d or i - j == d 22 | false 23 | 24 | # N queens solver 25 | nQueen = (n) -> 26 | result = [] 27 | for p in permute [0...n] 28 | result.push p unless test p, n 29 | result 30 | 31 | # Repeat a string a number of times 32 | rep = (s, n) -> (s for [0...n]).join '' 33 | 34 | # Display a board with a solution 35 | printBoard = (solution) -> 36 | board = "\n" 37 | end = solution.length 38 | for pos, row in solution 39 | board += "#{end - row} #{rep ' ☐ ', pos} " + 40 | "♕ #{rep ' ☐ ', end - pos - 1}\n" 41 | # Using radix 18 hack! 42 | board += ' ' + (n.toString 18 \ 43 | for n in [10...18]).join(' ').toUpperCase() 44 | board + "\n" 45 | 46 | # Find all solutions 47 | solve = (n) -> 48 | for solution, count in nQueen n 49 | show "Solution #{count+1}:" 50 | show printBoard solution 51 | count 52 | 53 | solve 8 # Normal chessboard size 54 | 55 | show "Timing: #{(new Date() - start)*0.001}s" 56 | -------------------------------------------------------------------------------- /src-no-solutions/A3-Queen.coffee: -------------------------------------------------------------------------------- 1 | # CoffeeScript encoding: utf-8 2 | start = new Date() 3 | show = console.log 4 | 5 | # Create variations to try 6 | permute = (L) -> 7 | n = L.length 8 | return ([elem] for elem in L) if n is 1 9 | [a, L] = [ [L[0]], L.slice 1 ] 10 | result = [] 11 | for p in permute L 12 | for i in [0...n] 13 | result.push p[...i].concat a, p[i...] 14 | result 15 | 16 | # Check a variation 17 | test = (p, n) -> 18 | for i in [0...n - 1] 19 | for j in [i + 1...n] 20 | d = p[i] - p[j] 21 | return true if j - i == d or i - j == d 22 | false 23 | 24 | # N queens solver 25 | nQueen = (n) -> 26 | result = [] 27 | for p in permute [0...n] 28 | result.push p unless test p, n 29 | result 30 | 31 | # Repeat a string a number of times 32 | rep = (s, n) -> (s for [0...n]).join '' 33 | 34 | # Display a board with a solution 35 | printBoard = (solution) -> 36 | board = "\n" 37 | end = solution.length 38 | for pos, row in solution 39 | board += "#{end - row} #{rep ' ☐ ', pos} " + 40 | "♕ #{rep ' ☐ ', end - pos - 1}\n" 41 | # Using radix 18 hack! 42 | board += ' ' + (n.toString 18 \ 43 | for n in [10...18]).join(' ').toUpperCase() 44 | board + "\n" 45 | 46 | # Find all solutions 47 | solve = (n) -> 48 | for solution, count in nQueen n 49 | show "Solution #{count+1}:" 50 | show printBoard solution 51 | count 52 | 53 | solve 8 # Normal chessboard size 54 | 55 | show "Timing: #{(new Date() - start)*0.001}s" 56 | -------------------------------------------------------------------------------- /img/License.text: -------------------------------------------------------------------------------- 1 | 2 | All diagrams and drawings created by the editor: 3 | They are available under the same license as the book. 4 | 5 | 6 | 7 | Photos from publicdomainpictures.net: 8 | 9 | Cup Of Coffee Photo 10 | License: This image is public domain. You may use 11 | this picture for any purpose, including commercial. 12 | If you do use it, please consider linking back to us. 13 | If you are going to redistribute this image online, 14 | a hyperlink to this particular page is mandatory. 15 | HTML: Cup Of Coffee by Petr Kratochvil 16 | 17 | Kitten In Hood Photo 18 | License: This image is public domain. You may use 19 | this picture for any purpose, including commercial. 20 | If you do use it, please consider linking back to us. 21 | If you are going to redistribute this image online, 22 | a hyperlink to this particular page is mandatory. 23 | HTML: Kitten In Hood by Margaret Taylor 24 | 25 | Ostriches Look Photo 26 | License: This image is public domain. You may use 27 | this picture for any purpose, including commercial. 28 | If you do use it, please consider linking back to us. 29 | If you are going to redistribute this image online, 30 | a hyperlink to this particular page is mandatory. 31 | HTML: Ostriches Look by Petr Kratochvil 32 | -------------------------------------------------------------------------------- /src/A3-Queens.py: -------------------------------------------------------------------------------- 1 | # CPython encoding: utf-8 2 | import time 3 | t=time.clock() 4 | 5 | def permute(L): 6 | "Create variations to try" 7 | n = len(L) 8 | if n == 1: 9 | for elem in L: 10 | yield [elem] 11 | else: 12 | a = [L.pop(0)] 13 | for p in permute(L): 14 | for i in range(n): 15 | yield p[:i] + a + p[i:] 16 | 17 | def test(p, n): 18 | "Check a variation" 19 | for i in range(n - 1): 20 | for j in range(i + 1, n): 21 | d = p[i] - p[j] 22 | if j - i == d or i - j == d: 23 | return True 24 | return False 25 | 26 | def n_queen(n): 27 | "N queens solver" 28 | for p in permute(range(n)): 29 | if not test(p, n): yield p 30 | 31 | # Start columns from A 32 | base_char = ord('A') 33 | 34 | def print_board(solution): 35 | "Display a board with a solution" 36 | board = [] 37 | end = len(solution) 38 | for row, pos in enumerate(solution): 39 | board += ["\n%s %s ♕ %s" % ((end - row), 40 | (' ☐ ' * pos), 41 | (' ☐ ' * (end - pos - 1)))] 42 | # Using character set hack! 43 | board += '\n ' + \ 44 | ' '.join([chr(base_char+i) 45 | for i in range(0, end)]) 46 | return ''.join(board) + '\n' 47 | 48 | def solve(n): 49 | "Find all solutions" 50 | for count, solution in enumerate(n_queen(n)): 51 | print "Solution %d:" % (count + 1) 52 | print print_board(solution) 53 | return count 54 | 55 | solve(8) # Normal chessboard size 56 | 57 | print "Timing: ", time.clock()-t, "s" 58 | -------------------------------------------------------------------------------- /src-no-solutions/A3-Queens.py: -------------------------------------------------------------------------------- 1 | # CPython encoding: utf-8 2 | import time 3 | t=time.clock() 4 | 5 | def permute(L): 6 | "Create variations to try" 7 | n = len(L) 8 | if n == 1: 9 | for elem in L: 10 | yield [elem] 11 | else: 12 | a = [L.pop(0)] 13 | for p in permute(L): 14 | for i in range(n): 15 | yield p[:i] + a + p[i:] 16 | 17 | def test(p, n): 18 | "Check a variation" 19 | for i in range(n - 1): 20 | for j in range(i + 1, n): 21 | d = p[i] - p[j] 22 | if j - i == d or i - j == d: 23 | return True 24 | return False 25 | 26 | def n_queen(n): 27 | "N queens solver" 28 | for p in permute(range(n)): 29 | if not test(p, n): yield p 30 | 31 | # Start columns from A 32 | base_char = ord('A') 33 | 34 | def print_board(solution): 35 | "Display a board with a solution" 36 | board = [] 37 | end = len(solution) 38 | for row, pos in enumerate(solution): 39 | board += ["\n%s %s ♕ %s" % ((end - row), 40 | (' ☐ ' * pos), 41 | (' ☐ ' * (end - pos - 1)))] 42 | # Using character set hack! 43 | board += '\n ' + \ 44 | ' '.join([chr(base_char+i) 45 | for i in range(0, end)]) 46 | return ''.join(board) + '\n' 47 | 48 | def solve(n): 49 | "Find all solutions" 50 | for count, solution in enumerate(n_queen(n)): 51 | print "Solution %d:" % (count + 1) 52 | print print_board(solution) 53 | return count 54 | 55 | solve(8) # Normal chessboard size 56 | 57 | print "Timing: ", time.clock()-t, "s" 58 | -------------------------------------------------------------------------------- /src/prelude/qc-COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009, Darrin Thompson 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Darrin Thompson nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | -------------------------------------------------------------------------------- /src-no-solutions/prelude/qc-COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009, Darrin Thompson 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Darrin Thompson nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | -------------------------------------------------------------------------------- /src/A2-BinaryHeapTest.coffee: -------------------------------------------------------------------------------- 1 | require './prelude' 2 | bh = require './A2-BinaryHeap' 3 | # Bring _, qc and bh into global 4 | globalize _ 5 | globalize qc 6 | globalize bh 7 | 8 | # Declarative Tests 9 | #------------------- 10 | sortByValue = (obj) -> sortBy obj, (n) -> n 11 | buildHeap = (c, a) -> 12 | heap = new BinaryHeap 13 | heap.push number for number in a 14 | c.note heap 15 | 16 | declare 'heap is created empty', [], 17 | (c) -> c.assert (new BinaryHeap).size() == 0 18 | 19 | declare 'heap pop is undefined when empty', [], 20 | (c) -> c.assert isUndefined (new BinaryHeap).pop() 21 | 22 | declare 'heap contains number of inserted elements', 23 | [arbArray(arbInt)], (c, a) -> 24 | c.assert buildHeap(c, a).size() == a.length 25 | 26 | declare 'heap contains inserted elements', 27 | [arbArray(arbInt)], (c, a) -> 28 | heap = buildHeap c, a 29 | c.assert isEqual sortByValue(a), \ 30 | sortByValue(heap.content) 31 | 32 | declare 'heap pops elements in sorted order', 33 | [arbArray(arbInt)], (c, a) -> 34 | heap = buildHeap c, a 35 | for n in sortByValue a then c.assert n == heap.pop() 36 | c.assert heap.size() == 0 37 | 38 | declare 'heap does not remove non-existent elements', 39 | [arbArray(arbInt), arbInt], 40 | expectException (c, a, b) -> 41 | if b in a then c.guard false 42 | heap = buildHeap c, a 43 | heap.remove b 44 | 45 | declare 'heap removes existing elements', 46 | [arbArray(arbInt), arbInt], (c, a, b) -> 47 | if not (b in a) then c.guard false 48 | aSort = sortByValue without a, b 49 | count = a.length - aSort.length 50 | heap = buildHeap c, a 51 | heap.remove b for i in [0...count] 52 | for n in aSort then c.assert n == heap.pop() 53 | c.assert heap.size() == 0 54 | 55 | test() 56 | -------------------------------------------------------------------------------- /src-no-solutions/A2-BinaryHeapTest.coffee: -------------------------------------------------------------------------------- 1 | require './prelude' 2 | bh = require './A2-BinaryHeap' 3 | # Bring _, qc and bh into global 4 | globalize _ 5 | globalize qc 6 | globalize bh 7 | 8 | # Declarative Tests 9 | #------------------- 10 | sortByValue = (obj) -> sortBy obj, (n) -> n 11 | buildHeap = (c, a) -> 12 | heap = new BinaryHeap 13 | heap.push number for number in a 14 | c.note heap 15 | 16 | declare 'heap is created empty', [], 17 | (c) -> c.assert (new BinaryHeap).size() == 0 18 | 19 | declare 'heap pop is undefined when empty', [], 20 | (c) -> c.assert isUndefined (new BinaryHeap).pop() 21 | 22 | declare 'heap contains number of inserted elements', 23 | [arbArray(arbInt)], (c, a) -> 24 | c.assert buildHeap(c, a).size() == a.length 25 | 26 | declare 'heap contains inserted elements', 27 | [arbArray(arbInt)], (c, a) -> 28 | heap = buildHeap c, a 29 | c.assert isEqual sortByValue(a), \ 30 | sortByValue(heap.content) 31 | 32 | declare 'heap pops elements in sorted order', 33 | [arbArray(arbInt)], (c, a) -> 34 | heap = buildHeap c, a 35 | for n in sortByValue a then c.assert n == heap.pop() 36 | c.assert heap.size() == 0 37 | 38 | declare 'heap does not remove non-existent elements', 39 | [arbArray(arbInt), arbInt], 40 | expectException (c, a, b) -> 41 | if b in a then c.guard false 42 | heap = buildHeap c, a 43 | heap.remove b 44 | 45 | declare 'heap removes existing elements', 46 | [arbArray(arbInt), arbInt], (c, a, b) -> 47 | if not (b in a) then c.guard false 48 | aSort = sortByValue without a, b 49 | count = a.length - aSort.length 50 | heap = buildHeap c, a 51 | heap.remove b for i in [0...count] 52 | for n in aSort then c.assert n == heap.pop() 53 | c.assert heap.size() == 0 54 | 55 | test() 56 | -------------------------------------------------------------------------------- /src/10-Modularity.coffee: -------------------------------------------------------------------------------- 1 | require './prelude' 2 | 3 | # Top level environment 4 | show '--- Top level environment ---' 5 | show global.process.argv[0] 6 | show global.console.log == console.log 7 | show global.global.global.global.global.console 8 | 9 | # Pollution 10 | show '--- Pollution ---' 11 | HTML = 12 | tag: (name, content, properties) -> 13 | name: name 14 | properties: properties 15 | content: content 16 | link: (target, text) -> 17 | HTML.tag 'a', [text], {href: target} 18 | # ... many more HTML-producing functions ... 19 | 20 | globalize HTML 21 | show link 'http://citeseerx.ist.psu.edu/viewdoc/' + 22 | 'download?doi=10.1.1.102.244&rep=rep1&type=pdf', 23 | 'What Every Computer Scientist Should Know ' + 24 | 'About Floating-Point Arithmetic' 25 | 26 | # Unfriendly function 27 | show '--- Unfriendly function ---' 28 | range = (start, end, stepSize, length) -> 29 | if stepSize == undefined 30 | stepSize = 1 31 | if end == undefined 32 | end = start + stepSize * (length - 1) 33 | result = [] 34 | while start <= end 35 | result.push start 36 | start += stepSize 37 | result 38 | show range 0, undefined, 4, 5 39 | 40 | # Object argument 41 | show '--- Object argument ---' 42 | defaultTo = (object, values) -> 43 | for name, value of values 44 | if not object.hasOwnProperty name 45 | object[name] = value 46 | 47 | range = (args) -> 48 | defaultTo args, {start: 0, stepSize: 1} 49 | if args.end == undefined 50 | args.end = args.start + 51 | args.stepSize * (args.length - 1) 52 | result = []; 53 | while args.start <= args.end 54 | result.push args.start 55 | args.start += args.stepSize 56 | result 57 | show range {stepSize: 4, length: 5} 58 | 59 | # CoffeeScript eval 60 | show '--- CoffeeScript eval ---' 61 | eval 'function IamJavaScript() {' + 62 | ' console.log(\"Repeat after me:' + 63 | ' Give me more {();};.\");};' + 64 | ' IamJavaScript();' 65 | 66 | cs = (require 'coffee-script').CoffeeScript 67 | cs.eval 'show ((a, b) -> a + b) 3, 4' 68 | 69 | 70 | -------------------------------------------------------------------------------- /src-no-solutions/10-Modularity.coffee: -------------------------------------------------------------------------------- 1 | require './prelude' 2 | 3 | # Top level environment 4 | show '--- Top level environment ---' 5 | show global.process.argv[0] 6 | show global.console.log == console.log 7 | show global.global.global.global.global.console 8 | 9 | # Pollution 10 | show '--- Pollution ---' 11 | HTML = 12 | tag: (name, content, properties) -> 13 | name: name 14 | properties: properties 15 | content: content 16 | link: (target, text) -> 17 | HTML.tag 'a', [text], {href: target} 18 | # ... many more HTML-producing functions ... 19 | 20 | globalize HTML 21 | show link 'http://citeseerx.ist.psu.edu/viewdoc/' + 22 | 'download?doi=10.1.1.102.244&rep=rep1&type=pdf', 23 | 'What Every Computer Scientist Should Know ' + 24 | 'About Floating-Point Arithmetic' 25 | 26 | # Unfriendly function 27 | show '--- Unfriendly function ---' 28 | range = (start, end, stepSize, length) -> 29 | if stepSize == undefined 30 | stepSize = 1 31 | if end == undefined 32 | end = start + stepSize * (length - 1) 33 | result = [] 34 | while start <= end 35 | result.push start 36 | start += stepSize 37 | result 38 | show range 0, undefined, 4, 5 39 | 40 | # Object argument 41 | show '--- Object argument ---' 42 | defaultTo = (object, values) -> 43 | for name, value of values 44 | if not object.hasOwnProperty name 45 | object[name] = value 46 | 47 | range = (args) -> 48 | defaultTo args, {start: 0, stepSize: 1} 49 | if args.end == undefined 50 | args.end = args.start + 51 | args.stepSize * (args.length - 1) 52 | result = []; 53 | while args.start <= args.end 54 | result.push args.start 55 | args.start += args.stepSize 56 | result 57 | show range {stepSize: 4, length: 5} 58 | 59 | # CoffeeScript eval 60 | show '--- CoffeeScript eval ---' 61 | eval 'function IamJavaScript() {' + 62 | ' console.log(\"Repeat after me:' + 63 | ' Give me more {();};.\");};' + 64 | ' IamJavaScript();' 65 | 66 | cs = (require 'coffee-script').CoffeeScript 67 | cs.eval 'show ((a, b) -> a + b) 3, 4' 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/10-SeedLife.coffee: -------------------------------------------------------------------------------- 1 | kup = require './prelude/coffeekup' 2 | 3 | # Client-side web page with canvas 4 | webpage = kup.render -> 5 | doctype 5 6 | html -> 7 | head -> 8 | meta charset: 'utf-8' 9 | title 'My drawing | My awesome website' 10 | style ''' 11 | body {font-family: sans-serif} 12 | header, nav, section, footer {display: block} 13 | ''' 14 | # DO NOT USE: type: 'text/coffeescript' 15 | script src: './10-Circular.coffee' 16 | 17 | coffeescript -> 18 | draw = (ctx, x, y) -> 19 | circle = (ctx, x, y) -> 20 | ctx.beginPath() 21 | ctx.arc x, y, 100, 0, 2*Math.PI, false 22 | ctx.stroke() 23 | ctx.strokeStyle = 'rgba(255,40,20,0.7)' 24 | circle ctx, x, y 25 | circ = new CircularPosition() 26 | for i in [0...6] 27 | pt = circ.nextPoint() 28 | circle ctx, x+100*pt.x, y+100*pt.y 29 | window.onload = -> 30 | canvas = document.getElementById 'drawCanvas' 31 | context = canvas.getContext '2d' 32 | draw context, 300, 200 33 | body -> 34 | header -> h1 'Seed of Life' 35 | canvas id: 'drawCanvas', width: 600, height: 400 36 | 37 | # Server-side HTTP server 38 | show = console.log 39 | http = require "http" 40 | fs = require "fs" 41 | cs = require("coffee-script").CoffeeScript 42 | server = http.createServer (req, res) -> 43 | show "#{req.client.remoteAddress} " + 44 | "#{req.method} #{req.url}" 45 | if req.method is "GET" 46 | if req.url is "/" 47 | res.writeHead 200, "Content-Type": "text/html" 48 | res.write webpage 49 | res.end() 50 | return 51 | else if req.url is "/10-Circular.coffee" 52 | fs.readFile ".#{req.url}", "utf8", (err, data) -> 53 | if err then throw err 54 | compiledContent = cs.compile data 55 | res.writeHead 200, 56 | "Content-Type": "application/javascript" 57 | res.write compiledContent 58 | res.end() 59 | return 60 | res.writeHead 404, "Content-Type": "text/html" 61 | res.write "404 Not found" 62 | res.end() 63 | server.listen 3389 64 | show "Server running at" 65 | show server.address() 66 | -------------------------------------------------------------------------------- /src-no-solutions/10-SeedLife.coffee: -------------------------------------------------------------------------------- 1 | kup = require './prelude/coffeekup' 2 | 3 | # Client-side web page with canvas 4 | webpage = kup.render -> 5 | doctype 5 6 | html -> 7 | head -> 8 | meta charset: 'utf-8' 9 | title 'My drawing | My awesome website' 10 | style ''' 11 | body {font-family: sans-serif} 12 | header, nav, section, footer {display: block} 13 | ''' 14 | # DO NOT USE: type: 'text/coffeescript' 15 | script src: './10-Circular.coffee' 16 | 17 | coffeescript -> 18 | draw = (ctx, x, y) -> 19 | circle = (ctx, x, y) -> 20 | ctx.beginPath() 21 | ctx.arc x, y, 100, 0, 2*Math.PI, false 22 | ctx.stroke() 23 | ctx.strokeStyle = 'rgba(255,40,20,0.7)' 24 | circle ctx, x, y 25 | circ = new CircularPosition() 26 | for i in [0...6] 27 | pt = circ.nextPoint() 28 | circle ctx, x+100*pt.x, y+100*pt.y 29 | window.onload = -> 30 | canvas = document.getElementById 'drawCanvas' 31 | context = canvas.getContext '2d' 32 | draw context, 300, 200 33 | body -> 34 | header -> h1 'Seed of Life' 35 | canvas id: 'drawCanvas', width: 600, height: 400 36 | 37 | # Server-side HTTP server 38 | show = console.log 39 | http = require "http" 40 | fs = require "fs" 41 | cs = require("coffee-script").CoffeeScript 42 | server = http.createServer (req, res) -> 43 | show "#{req.client.remoteAddress} " + 44 | "#{req.method} #{req.url}" 45 | if req.method is "GET" 46 | if req.url is "/" 47 | res.writeHead 200, "Content-Type": "text/html" 48 | res.write webpage 49 | res.end() 50 | return 51 | else if req.url is "/10-Circular.coffee" 52 | fs.readFile ".#{req.url}", "utf8", (err, data) -> 53 | if err then throw err 54 | compiledContent = cs.compile data 55 | res.writeHead 200, 56 | "Content-Type": "application/javascript" 57 | res.write compiledContent 58 | res.end() 59 | return 60 | res.writeHead 404, "Content-Type": "text/html" 61 | res.write "404 Not found" 62 | res.end() 63 | server.listen 3389 64 | show "Server running at" 65 | show server.address() 66 | -------------------------------------------------------------------------------- /src/A4-NoSolutions.coffee: -------------------------------------------------------------------------------- 1 | fs = require "fs" 2 | show = console.log 3 | 4 | String::contains = (pattern) -> 5 | ///#{pattern}///.test @ 6 | 7 | leadingWhitespace = (str) -> 8 | (str.match /(\s*)\w/)[1] ? "" 9 | 10 | errorWrapper = (action) -> 11 | (err, args...) -> 12 | if err then throw err 13 | action args... 14 | 15 | ifFileExists = (filename, action) -> 16 | fs.stat filename, errorWrapper (stat) -> 17 | if stat.isFile() then action() 18 | 19 | getFileAsLines = (filename, action) -> 20 | ifFileExists filename, -> 21 | fs.readFile filename, "utf8", 22 | errorWrapper (content) -> 23 | action content.split "\n" 24 | 25 | saveFile = (filename, content) -> 26 | fs.writeFile filename, content, 27 | errorWrapper -> show "Saved #{filename}" 28 | 29 | stripSolutions = (lines) -> 30 | out = "" 31 | inSolution = false 32 | concat = (str) -> out += str + "\n" 33 | for line in lines 34 | if line.contains "'--- Exercise \\d+ ---'" 35 | inSolution = true 36 | concat line 37 | indent = leadingWhitespace line 38 | concat "#{indent}process.exit()" + 39 | " # Replace this line with your solution" 40 | else if inSolution 41 | if line.contains "'--- End of Exercise ---'" 42 | concat line 43 | inSolution = false 44 | # else ignore line in solution 45 | else 46 | concat line 47 | # Remove trailing newline 48 | out[...out.length-1] 49 | 50 | stripFile = (fromName, toName) -> 51 | if fromName? 52 | getFileAsLines fromName, (lines) -> 53 | saveFile toName, stripSolutions lines 54 | else 55 | show "Expected a file name " + 56 | "to strip for solutions" 57 | 58 | copyFile = (fromName, toName) -> 59 | if fromName? 60 | ifFileExists fromName, -> 61 | fs.readFile fromName, "utf8", 62 | errorWrapper (content) -> 63 | saveFile toName, content, 64 | else 65 | show "Expected a file name to copy" 66 | 67 | toDir = "../src-no-solutions" 68 | fs.mkdir toDir, 0777, (err) -> 69 | if err 70 | throw err unless err.code is 'EEXIST' 71 | show "Reusing" 72 | else 73 | show "Created" 74 | 75 | fromDir = process.argv[2] 76 | if fromDir? 77 | fs.readdir fromDir, errorWrapper (files) -> 78 | for filename in files 79 | if filename.contains "\\w\\w-\\w+.coffee" 80 | stripFile filename, "#{toDir}/#{filename}" 81 | else 82 | copyFile filename, "#{toDir}/#{filename}" 83 | else 84 | show "Expected a directory with " + 85 | "solutions to strip" 86 | 87 | -------------------------------------------------------------------------------- /src-no-solutions/A4-NoSolutions.coffee: -------------------------------------------------------------------------------- 1 | fs = require "fs" 2 | show = console.log 3 | 4 | String::contains = (pattern) -> 5 | ///#{pattern}///.test @ 6 | 7 | leadingWhitespace = (str) -> 8 | (str.match /(\s*)\w/)[1] ? "" 9 | 10 | errorWrapper = (action) -> 11 | (err, args...) -> 12 | if err then throw err 13 | action args... 14 | 15 | ifFileExists = (filename, action) -> 16 | fs.stat filename, errorWrapper (stat) -> 17 | if stat.isFile() then action() 18 | 19 | getFileAsLines = (filename, action) -> 20 | ifFileExists filename, -> 21 | fs.readFile filename, "utf8", 22 | errorWrapper (content) -> 23 | action content.split "\n" 24 | 25 | saveFile = (filename, content) -> 26 | fs.writeFile filename, content, 27 | errorWrapper -> show "Saved #{filename}" 28 | 29 | stripSolutions = (lines) -> 30 | out = "" 31 | inSolution = false 32 | concat = (str) -> out += str + "\n" 33 | for line in lines 34 | if line.contains "'--- Exercise \\d+ ---'" 35 | inSolution = true 36 | concat line 37 | indent = leadingWhitespace line 38 | concat "#{indent}process.exit()" + 39 | " # Replace this line with your solution" 40 | else if inSolution 41 | if line.contains "'--- End of Exercise ---'" 42 | concat line 43 | inSolution = false 44 | # else ignore line in solution 45 | else 46 | concat line 47 | # Remove trailing newline 48 | out[...out.length-1] 49 | 50 | stripFile = (fromName, toName) -> 51 | if fromName? 52 | getFileAsLines fromName, (lines) -> 53 | saveFile toName, stripSolutions lines 54 | else 55 | show "Expected a file name " + 56 | "to strip for solutions" 57 | 58 | copyFile = (fromName, toName) -> 59 | if fromName? 60 | ifFileExists fromName, -> 61 | fs.readFile fromName, "utf8", 62 | errorWrapper (content) -> 63 | saveFile toName, content, 64 | else 65 | show "Expected a file name to copy" 66 | 67 | toDir = "../src-no-solutions" 68 | fs.mkdir toDir, 0777, (err) -> 69 | if err 70 | throw err unless err.code is 'EEXIST' 71 | show "Reusing" 72 | else 73 | show "Created" 74 | 75 | fromDir = process.argv[2] 76 | if fromDir? 77 | fs.readdir fromDir, errorWrapper (files) -> 78 | for filename in files 79 | if filename.contains "\\w\\w-\\w+.coffee" 80 | stripFile filename, "#{toDir}/#{filename}" 81 | else 82 | copyFile filename, "#{toDir}/#{filename}" 83 | else 84 | show "Expected a directory with " + 85 | "solutions to strip" 86 | 87 | -------------------------------------------------------------------------------- /src/10-TestWebSockets.coffee: -------------------------------------------------------------------------------- 1 | require './prelude' 2 | 3 | webpage = kup.render -> 4 | doctype 5 5 | html -> 6 | head -> 7 | meta charset: 'utf-8' 8 | title 'WebSocket Test' 9 | style ''' 10 | body {font-family: sans-serif} 11 | header, nav, section, footer {display: block} 12 | ''' 13 | coffeescript -> 14 | #wsUri = 'ws://echo.websocket.org/' 15 | wsUri = 'ws://localhost:8080/' 16 | output = null 17 | init = -> 18 | output = document.getElementById 'output' 19 | testWebSocket() 20 | 21 | testWebSocket = -> 22 | websocket = new WebSocket wsUri 23 | websocket.onopen = (evt) -> onOpen evt 24 | websocket.onclose = (evt) -> onClose evt 25 | websocket.onmessage = (evt) -> onMessage evt 26 | websocket.onerror = (evt) -> onError evt 27 | 28 | onOpen = (evt) -> 29 | writeToScreen "CONNECTED" 30 | doSend "WebSocket works!" 31 | 32 | onClose = (evt) -> 33 | writeToScreen "DISCONNECTED" 34 | 35 | onMessage = (evt) -> 36 | writeToScreen 'RESPONSE: ' + evt.data + '' 37 | websocket.close() 38 | 39 | onError = (evt) -> 40 | writeToScreen 'ERROR: ' + evt.data 41 | 42 | doSend = (message) -> 43 | writeToScreen "SENT: " + message 44 | websocket.send message 45 | 46 | writeToScreen = (message) -> 47 | pre = document.createElement "p" 48 | pre.style.wordWrap = "break-word" 49 | pre.innerHTML = message 50 | output.appendChild pre 51 | 52 | window.addEventListener "load", init, false 53 | 54 | body -> 55 | header -> h2 'WebSocket Test' 56 | div id: 'output' 57 | 58 | 59 | wsHandler = (websocket) -> 60 | websocket.on 'connect', (resource) -> 61 | show 'connect: ' + resource 62 | # close connection after 10s 63 | setTimeout websocket.end, 10 * 1000 64 | websocket.on 'data', (data) -> 65 | show data # process data 66 | websocket.write 'Cowabunga!' # respond 67 | websocket.on 'close', -> 68 | show 'closing' 69 | # Exit server completely 70 | process.exit 0 71 | wsServer = ws.createServer wsHandler 72 | wsServer.listen 8080 73 | 74 | 75 | http = require 'http' 76 | server = http.createServer (req, res) -> 77 | show "#{req.client.remoteAddress} #{req.method} #{req.url}" 78 | res.writeHead 200, 'Content-Type': 'text/html' 79 | res.write webpage 80 | res.end() 81 | server.listen 8000 82 | show 'Server running at' 83 | show server.address() 84 | 85 | viewURL getServerURL server 86 | 87 | 88 | -------------------------------------------------------------------------------- /src-no-solutions/10-TestWebSockets.coffee: -------------------------------------------------------------------------------- 1 | require './prelude' 2 | 3 | webpage = kup.render -> 4 | doctype 5 5 | html -> 6 | head -> 7 | meta charset: 'utf-8' 8 | title 'WebSocket Test' 9 | style ''' 10 | body {font-family: sans-serif} 11 | header, nav, section, footer {display: block} 12 | ''' 13 | coffeescript -> 14 | #wsUri = 'ws://echo.websocket.org/' 15 | wsUri = 'ws://localhost:8080/' 16 | output = null 17 | init = -> 18 | output = document.getElementById 'output' 19 | testWebSocket() 20 | 21 | testWebSocket = -> 22 | websocket = new WebSocket wsUri 23 | websocket.onopen = (evt) -> onOpen evt 24 | websocket.onclose = (evt) -> onClose evt 25 | websocket.onmessage = (evt) -> onMessage evt 26 | websocket.onerror = (evt) -> onError evt 27 | 28 | onOpen = (evt) -> 29 | writeToScreen "CONNECTED" 30 | doSend "WebSocket works!" 31 | 32 | onClose = (evt) -> 33 | writeToScreen "DISCONNECTED" 34 | 35 | onMessage = (evt) -> 36 | writeToScreen 'RESPONSE: ' + evt.data + '' 37 | websocket.close() 38 | 39 | onError = (evt) -> 40 | writeToScreen 'ERROR: ' + evt.data 41 | 42 | doSend = (message) -> 43 | writeToScreen "SENT: " + message 44 | websocket.send message 45 | 46 | writeToScreen = (message) -> 47 | pre = document.createElement "p" 48 | pre.style.wordWrap = "break-word" 49 | pre.innerHTML = message 50 | output.appendChild pre 51 | 52 | window.addEventListener "load", init, false 53 | 54 | body -> 55 | header -> h2 'WebSocket Test' 56 | div id: 'output' 57 | 58 | 59 | wsHandler = (websocket) -> 60 | websocket.on 'connect', (resource) -> 61 | show 'connect: ' + resource 62 | # close connection after 10s 63 | setTimeout websocket.end, 10 * 1000 64 | websocket.on 'data', (data) -> 65 | show data # process data 66 | websocket.write 'Cowabunga!' # respond 67 | websocket.on 'close', -> 68 | show 'closing' 69 | # Exit server completely 70 | process.exit 0 71 | wsServer = ws.createServer wsHandler 72 | wsServer.listen 8080 73 | 74 | 75 | http = require 'http' 76 | server = http.createServer (req, res) -> 77 | show "#{req.client.remoteAddress} #{req.method} #{req.url}" 78 | res.writeHead 200, 'Content-Type': 'text/html' 79 | res.write webpage 80 | res.end() 81 | server.listen 8000 82 | show 'Server running at' 83 | show server.address() 84 | 85 | viewURL getServerURL server 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/04-emails.coffee: -------------------------------------------------------------------------------- 1 | retrieveMails = -> [ 2 | "Nephew,\n\nI bought a computer as soon as I received your letter. It took me two days to make it do 'internet', but I just kept calling the nice man at the computer shop, and in the end he came down to help personally. Send me something back if you receive this, so I know whether it actually works.\n\nLove,\nAunt Emily" 3 | "Dear Nephew,\n\nVery good! I feel quite proud about being so technologically minded, having a computer and all. I bet Mrs. Goor down the street wouldn't even know how to plug it in, that witch.\n\nAnyway, thanks for sending me that game, it was great fun. After three days, I beat it. My friend Mrs. Johnson was quite worried when I didn't come outside or answer the phone for three days, but I explained to her that I was working with my computer.\n\nMy cat had two kittens yesterday! I didn't even realize the thing was pregnant. I've listed the names at the bottom of my letter, so that you will know how to greet them the next time you come over.\n\nSincerely,\nAunt Emily\n\nborn 15/02/1999 (mother Spot): Clementine, Fireball" 4 | "[... and so on ...]\n\nborn 21/09/2000 (mother Spot): Yellow Emperor, Black Leclère" 5 | "...\n\nborn 02/04/2001 (mother Clementine): Bugeye, Wolverine, Miss Bushtail" 6 | "...\n\ndied 12/12/2002: Clementine\n\ndied 15/12/2002: Wolverine" 7 | "...\n\nborn 15/11/2003 (mother Spot): White Fang" 8 | "...\n\nborn 10/04/2003 (mother Miss Bushtail): Yellow Bess" 9 | "...\n\ndied 30/05/2004: Yellow Emperor" 10 | "...\n\nborn 01/06/2004 (mother Miss Bushtail): Catharina, Fat Igor" 11 | "...\n\nborn 20/09/2004 (mother Yellow Bess): Doctor Hobbles the 2nd, Noog" 12 | "...\n\nborn 15/01/2005 (mother Yellow Bess): The Moose, Liger\n\ndied 17/01/2005: Liger" 13 | "Dear nephew,\n\nYour mother told me you have taken up skydiving. Is this true? You watch yourself, young man! Remember what happened to my husband? And that was only from the second floor!\n\nAnyway, things are very exciting here. I have spent all week trying to get the attention of Mr. Drake, the nice gentleman who moved in next\ndoor, but I think he is afraid of cats. Or allergic to them? I am\ngoing to try putting Fat Igor on his shoulder next time I see him, very curious what will happen.\n\nAlso, the scam I told you about is going better than expected. I have already gotten back five 'payments', and only one complaint. It is starting to make me feel a bit bad though. And you are right that it is probably illegal in some way.\n\n(... etc ...)\n\nMuch love,\nAunt Emily\n\ndied 27/04/2006: Black Leclère\n\nborn 05/04/2006 (mother Lady Penelope): Red Lion, Doctor Hobbles the 3rd, Little Iroquois" 14 | "...\n\nborn 22/07/2006 (mother Noog): Goblin, Reginald, Little Maggie" 15 | "...\n\ndied 13/02/2007: Spot\n\ndied 21/02/2007: Fireball" 16 | "...\n\nborn 05/02/2007 (mother Noog): Long-ear Johnson" 17 | "...\n\nborn 03/03/2007 (mother Catharina): Asoka, Dark Empress, Rabbitface" 18 | ] 19 | exports.retrieveMails = retrieveMails 20 | -------------------------------------------------------------------------------- /src-no-solutions/04-emails.coffee: -------------------------------------------------------------------------------- 1 | retrieveMails = -> [ 2 | "Nephew,\n\nI bought a computer as soon as I received your letter. It took me two days to make it do 'internet', but I just kept calling the nice man at the computer shop, and in the end he came down to help personally. Send me something back if you receive this, so I know whether it actually works.\n\nLove,\nAunt Emily" 3 | "Dear Nephew,\n\nVery good! I feel quite proud about being so technologically minded, having a computer and all. I bet Mrs. Goor down the street wouldn't even know how to plug it in, that witch.\n\nAnyway, thanks for sending me that game, it was great fun. After three days, I beat it. My friend Mrs. Johnson was quite worried when I didn't come outside or answer the phone for three days, but I explained to her that I was working with my computer.\n\nMy cat had two kittens yesterday! I didn't even realize the thing was pregnant. I've listed the names at the bottom of my letter, so that you will know how to greet them the next time you come over.\n\nSincerely,\nAunt Emily\n\nborn 15/02/1999 (mother Spot): Clementine, Fireball" 4 | "[... and so on ...]\n\nborn 21/09/2000 (mother Spot): Yellow Emperor, Black Leclère" 5 | "...\n\nborn 02/04/2001 (mother Clementine): Bugeye, Wolverine, Miss Bushtail" 6 | "...\n\ndied 12/12/2002: Clementine\n\ndied 15/12/2002: Wolverine" 7 | "...\n\nborn 15/11/2003 (mother Spot): White Fang" 8 | "...\n\nborn 10/04/2003 (mother Miss Bushtail): Yellow Bess" 9 | "...\n\ndied 30/05/2004: Yellow Emperor" 10 | "...\n\nborn 01/06/2004 (mother Miss Bushtail): Catharina, Fat Igor" 11 | "...\n\nborn 20/09/2004 (mother Yellow Bess): Doctor Hobbles the 2nd, Noog" 12 | "...\n\nborn 15/01/2005 (mother Yellow Bess): The Moose, Liger\n\ndied 17/01/2005: Liger" 13 | "Dear nephew,\n\nYour mother told me you have taken up skydiving. Is this true? You watch yourself, young man! Remember what happened to my husband? And that was only from the second floor!\n\nAnyway, things are very exciting here. I have spent all week trying to get the attention of Mr. Drake, the nice gentleman who moved in next\ndoor, but I think he is afraid of cats. Or allergic to them? I am\ngoing to try putting Fat Igor on his shoulder next time I see him, very curious what will happen.\n\nAlso, the scam I told you about is going better than expected. I have already gotten back five 'payments', and only one complaint. It is starting to make me feel a bit bad though. And you are right that it is probably illegal in some way.\n\n(... etc ...)\n\nMuch love,\nAunt Emily\n\ndied 27/04/2006: Black Leclère\n\nborn 05/04/2006 (mother Lady Penelope): Red Lion, Doctor Hobbles the 3rd, Little Iroquois" 14 | "...\n\nborn 22/07/2006 (mother Noog): Goblin, Reginald, Little Maggie" 15 | "...\n\ndied 13/02/2007: Spot\n\ndied 21/02/2007: Fireball" 16 | "...\n\nborn 05/02/2007 (mother Noog): Long-ear Johnson" 17 | "...\n\nborn 03/03/2007 (mother Catharina): Asoka, Dark Empress, Rabbitface" 18 | ] 19 | exports.retrieveMails = retrieveMails 20 | -------------------------------------------------------------------------------- /changes.md: -------------------------------------------------------------------------------- 1 | 1st edition, 1st revision 2 | Addenda, errata & minutiae: 3 | 4 | A1-LanguageExtras.coffee: Additional examples see p 199. 5 | 6 | Page numbers refer to the initial editions. 7 | Instructor edition / Challenge edition without solutions. 8 | 9 | p 1 / 1 10 | Added hyperlink to book webpage. 11 | 12 | p 2 / 2 13 | Added revision number to copyright line. 14 | 15 | p 10 / 10 16 | Change/add to last paragraph: 17 | You can run samples with coffee filename.coffee. 18 | ◦•◦ 19 | Smooth CoffeeScript comes in two editions; with and without solutions. Complete by chapter source code files are in the src directory. A copy of all files are in src-no-solutions, these files have stops where you can insert your own solutions. 20 | Both editions and accompanying source files can be downloaded from: 21 | http://autotelicum.github.com/Smooth-CoffeeScript/ 22 | 23 | p 50 / 47 24 | Move footnote 13 from the number to the preceding text. It looked like a power rather than a footnote reference. 25 | 26 | p 114 / 105 27 | In Exercise 23 add to last sentence in first paragraph: ', they need type and content properties.' 28 | 29 | p 125 / 114 30 | Change last paragraph to: 31 | In `isDefined` we are defining a new function without naming it. This can be useful when you need to create a simple function to give to, for example, `map` or `reduce`. However, when a function becomes more complex than this example, it is usually shorter and clearer to define it by itself and name it. 32 | 33 | p 145 / 132, 2nd line after diagram: 34 | Change from: 'the end points at the value' to: 'the end points are the values' 35 | 36 | p 147 37 | Remove unnecessary parentheses in the solution's 1st line. 38 | 39 | p 156 mid / 141 mid 40 | Change from: 'base classes.' to 'the base class.' 41 | 42 | p 165 / 150 43 | On sketch correct spelling of 'Abstract' in Account Interface note. 44 | 45 | p 173 / 157 46 | Change from: 'pieces of string' to 'pieces of a string' 47 | 48 | p 182 / 165 49 | Change from: 'defining it as an alias for show' to 'defining show as an alias for it' 50 | 51 | p 190 / 173 52 | Change from: 'one response should never stop' to: 'one request should not stop' 53 | 54 | p 195 / 178 55 | Insert before last paragraph: 56 | First choose what your project is going to be about, then look to [https://github.com/languages/CoffeeScript||github] for supporting code and libraries. A couple of interesting ones for web applications are [https://github.com/mauricemach/zappa||Zappa] and [https://github.com/socketstream/socketstream||SocketStream]. 57 | 58 | p 199 top / 182 top 59 | Replace paragraph with: 60 | Unicode can be used in identifiers. Letter forms that are very similar to characters in the western alphabets should be avoided. It can be difficult in internationally shared projects due to different keyboard layouts, but useful in teaching math or in a local language. 61 | 62 | p 199 mid / 182 mid 63 | Expanded samples for the do statement. Added a couple of pages of descriptions of bound functions, destructuring assignment, and for ... when. 64 | 65 | p 217 / 200 66 | In 'Classes, Inheritance, and Super', last line replace: "@attr 'title', type: 'text'" with "@inheritedMethodName()" 67 | 68 | -------------------------------------------------------------------------------- /src/10-WebSocketLife.coffee: -------------------------------------------------------------------------------- 1 | kup = require './prelude/coffeekup' 2 | 3 | # Web page with canvas and client-side WebSocket 4 | webpage = kup.render -> 5 | doctype 5 6 | html -> 7 | head -> 8 | meta charset: 'utf-8' 9 | title 'My animation | My awesome website' 10 | style ''' 11 | body {font-family: sans-serif} 12 | header, nav, section, footer {display: block} 13 | ''' 14 | coffeescript -> 15 | show = (msg) -> console.log msg 16 | color = 'rgba(255,40,20,0.7)' 17 | circle = (ctx, x, y) -> 18 | ctx.strokeStyle = color 19 | ctx.beginPath() 20 | ctx.arc x, y, 100, 0, 2*Math.PI, false 21 | ctx.stroke() 22 | 23 | addElement = (ni, num, text) -> 24 | newdiv = document.createElement 'div' 25 | newdiv.setAttribute 'id', 'div' + num 26 | newdiv.innerHTML = text 27 | ni.appendChild newdiv 28 | 29 | wsUri = 'ws://localhost:8080/' 30 | websocket = undefined 31 | num = 0 32 | socketClient = (buffer, ctx, x, y) -> 33 | websocket = new WebSocket wsUri 34 | websocket.onopen = (evt) -> 35 | show 'Connected' 36 | websocket.onclose = (evt) -> 37 | show 'Closed' 38 | websocket.onerror = (evt) -> 39 | show 'Error: ' + evt.data 40 | websocket.onmessage = (evt) -> 41 | #show evt.data 42 | addElement buffer, num++, evt.data 43 | pt = JSON.parse evt.data 44 | if pt.color? then color = pt.color 45 | circle ctx, x+100*pt.x, y+100*pt.y 46 | 47 | window.onload = -> 48 | canvas = document.getElementById 'drawCanvas' 49 | context = canvas.getContext '2d' 50 | buffer = document.getElementById 'message' 51 | socketClient buffer, context, 300, 200 52 | 53 | window.sendMessage = -> 54 | msg = document.getElementById('entryfield').value 55 | websocket.send msg 56 | 57 | body -> 58 | header -> h1 'Seed of Life' 59 | input id:'entryfield', value:'rgba(40,200,25,0.7)' 60 | button type: 'button', onclick: 'sendMessage()' 61 | 'Change Color' 62 | br 63 | canvas id: 'drawCanvas', width: 600, height: 400 64 | div id: 'message' 65 | 66 | # Server-side WebSocket server 67 | ws = require './prelude/ws' 68 | cp = require './10-Circular' 69 | wsHandler = (websocket) -> 70 | websocket.on 'connect', (resource) -> 71 | show 'connect: ' + resource 72 | # close connection after 10s 73 | setTimeout websocket.end, 10 * 1000 74 | 75 | websocket.on 'data', (data) -> 76 | show data # process data 77 | blue = 'rgba(40,20,255,0.7)' 78 | websocket.write JSON.stringify 79 | color: if data is '' then blue else data 80 | 81 | websocket.on 'close', -> 82 | show 'closing' 83 | process.exit 0 # Exit server completely 84 | 85 | circ = new cp.CircularPosition 0.01 86 | annoy = setInterval (-> 87 | websocket.write JSON.stringify circ.nextPoint()), 20 88 | 89 | wsServer = ws.createServer wsHandler 90 | wsServer.listen 8080 91 | 92 | # Launch test server and client UI 93 | require './prelude' 94 | viewServer webpage 95 | -------------------------------------------------------------------------------- /src-no-solutions/10-WebSocketLife.coffee: -------------------------------------------------------------------------------- 1 | kup = require './prelude/coffeekup' 2 | 3 | # Web page with canvas and client-side WebSocket 4 | webpage = kup.render -> 5 | doctype 5 6 | html -> 7 | head -> 8 | meta charset: 'utf-8' 9 | title 'My animation | My awesome website' 10 | style ''' 11 | body {font-family: sans-serif} 12 | header, nav, section, footer {display: block} 13 | ''' 14 | coffeescript -> 15 | show = (msg) -> console.log msg 16 | color = 'rgba(255,40,20,0.7)' 17 | circle = (ctx, x, y) -> 18 | ctx.strokeStyle = color 19 | ctx.beginPath() 20 | ctx.arc x, y, 100, 0, 2*Math.PI, false 21 | ctx.stroke() 22 | 23 | addElement = (ni, num, text) -> 24 | newdiv = document.createElement 'div' 25 | newdiv.setAttribute 'id', 'div' + num 26 | newdiv.innerHTML = text 27 | ni.appendChild newdiv 28 | 29 | wsUri = 'ws://localhost:8080/' 30 | websocket = undefined 31 | num = 0 32 | socketClient = (buffer, ctx, x, y) -> 33 | websocket = new WebSocket wsUri 34 | websocket.onopen = (evt) -> 35 | show 'Connected' 36 | websocket.onclose = (evt) -> 37 | show 'Closed' 38 | websocket.onerror = (evt) -> 39 | show 'Error: ' + evt.data 40 | websocket.onmessage = (evt) -> 41 | #show evt.data 42 | addElement buffer, num++, evt.data 43 | pt = JSON.parse evt.data 44 | if pt.color? then color = pt.color 45 | circle ctx, x+100*pt.x, y+100*pt.y 46 | 47 | window.onload = -> 48 | canvas = document.getElementById 'drawCanvas' 49 | context = canvas.getContext '2d' 50 | buffer = document.getElementById 'message' 51 | socketClient buffer, context, 300, 200 52 | 53 | window.sendMessage = -> 54 | msg = document.getElementById('entryfield').value 55 | websocket.send msg 56 | 57 | body -> 58 | header -> h1 'Seed of Life' 59 | input id:'entryfield', value:'rgba(40,200,25,0.7)' 60 | button type: 'button', onclick: 'sendMessage()' 61 | 'Change Color' 62 | br 63 | canvas id: 'drawCanvas', width: 600, height: 400 64 | div id: 'message' 65 | 66 | # Server-side WebSocket server 67 | ws = require './prelude/ws' 68 | cp = require './10-Circular' 69 | wsHandler = (websocket) -> 70 | websocket.on 'connect', (resource) -> 71 | show 'connect: ' + resource 72 | # close connection after 10s 73 | setTimeout websocket.end, 10 * 1000 74 | 75 | websocket.on 'data', (data) -> 76 | show data # process data 77 | blue = 'rgba(40,20,255,0.7)' 78 | websocket.write JSON.stringify 79 | color: if data is '' then blue else data 80 | 81 | websocket.on 'close', -> 82 | show 'closing' 83 | process.exit 0 # Exit server completely 84 | 85 | circ = new cp.CircularPosition 0.01 86 | annoy = setInterval (-> 87 | websocket.write JSON.stringify circ.nextPoint()), 20 88 | 89 | wsServer = ws.createServer wsHandler 90 | wsServer.listen 8080 91 | 92 | # Launch test server and client UI 93 | require './prelude' 94 | viewServer webpage 95 | -------------------------------------------------------------------------------- /src/05-ErrorHandling.coffee: -------------------------------------------------------------------------------- 1 | require './prelude' 2 | 3 | # Error checking 4 | show '--- Error checking ---' 5 | between = (string, start, end) -> 6 | startAt = string.indexOf start 7 | if startAt == -1 then return 8 | startAt += start.length 9 | endAt = string.indexOf end, startAt 10 | if endAt == -1 then return 11 | string[startAt...endAt] 12 | 13 | show between 'bu ] boo [ bah ] gzz', '[ ', ' ]' 14 | show between 'bu [ boo bah gzz', '[ ', ' ]' 15 | 16 | prompt "Tell me something", "", (answer) -> 17 | parenthesized = between answer, "(", ")" 18 | if parenthesized? 19 | show "You parenthesized '#{parenthesized}'." 20 | chain1() 21 | 22 | chain1 = -> 23 | 24 | # Mixed return value 25 | show '--- Mixed return value ---' 26 | lastElement = (array) -> 27 | if array.length > 0 28 | array[array.length - 1] 29 | else 30 | undefined 31 | show lastElement [1, 2, undefined] 32 | 33 | # Try/catch sample 34 | show '--- Try/catch sample ---' 35 | lastElement = (array) -> 36 | if array.length > 0 37 | array[array.length - 1] 38 | else 39 | throw 'Can not take the last element' + 40 | ' of an empty array.' 41 | 42 | lastElementPlusTen = (array) -> 43 | lastElement(array) + 10 44 | 45 | try 46 | show lastElementPlusTen [] 47 | catch error 48 | show 'Something went wrong: ' + error 49 | 50 | # Keyword finally 51 | show '--- Keyword finally ---' 52 | currentThing = null 53 | 54 | processThing = (thing) -> 55 | if currentThing != null 56 | throw 'Oh no! We are already processing a thing!' 57 | 58 | currentThing = thing 59 | # do complicated processing... 60 | currentThing = null 61 | 62 | processThing = (thing) -> 63 | if currentThing != null 64 | throw 'Oh no! We are already processing a thing!' 65 | 66 | currentThing = thing 67 | try 68 | # do complicated processing... 69 | finally 70 | currentThing = null 71 | 72 | # System errors 73 | show '--- System errors ---' 74 | try 75 | show Sasquatch 76 | catch error 77 | show 'Caught: ' + error.message 78 | 79 | # Throwing an error 80 | show '--- Throwing an error ---' 81 | try 82 | throw new Error 'Fire!' 83 | catch error 84 | show error 85 | 86 | # Seven Truths 87 | show '--- Seven Truths ---' 88 | FoundSeven = {} 89 | hasSevenTruths = (object) -> 90 | counted = 0 91 | count = (object) -> 92 | for name of object 93 | if object[name] == true 94 | if (++counted) == 7 95 | throw FoundSeven 96 | if typeof object[name] == 'object' 97 | count object[name] 98 | try 99 | count object 100 | return false 101 | catch exception 102 | if exception != FoundSeven 103 | throw exception 104 | return true 105 | 106 | testdata = 107 | a: true 108 | b: true 109 | c: false 110 | d: 111 | a: true 112 | b: false 113 | c: true 114 | d: 115 | a: true 116 | b: 117 | a: true 118 | e: 119 | a: false 120 | b: true 121 | c: true 122 | show hasSevenTruths testdata 123 | -------------------------------------------------------------------------------- /src-no-solutions/05-ErrorHandling.coffee: -------------------------------------------------------------------------------- 1 | require './prelude' 2 | 3 | # Error checking 4 | show '--- Error checking ---' 5 | between = (string, start, end) -> 6 | startAt = string.indexOf start 7 | if startAt == -1 then return 8 | startAt += start.length 9 | endAt = string.indexOf end, startAt 10 | if endAt == -1 then return 11 | string[startAt...endAt] 12 | 13 | show between 'bu ] boo [ bah ] gzz', '[ ', ' ]' 14 | show between 'bu [ boo bah gzz', '[ ', ' ]' 15 | 16 | prompt "Tell me something", "", (answer) -> 17 | parenthesized = between answer, "(", ")" 18 | if parenthesized? 19 | show "You parenthesized '#{parenthesized}'." 20 | chain1() 21 | 22 | chain1 = -> 23 | 24 | # Mixed return value 25 | show '--- Mixed return value ---' 26 | lastElement = (array) -> 27 | if array.length > 0 28 | array[array.length - 1] 29 | else 30 | undefined 31 | show lastElement [1, 2, undefined] 32 | 33 | # Try/catch sample 34 | show '--- Try/catch sample ---' 35 | lastElement = (array) -> 36 | if array.length > 0 37 | array[array.length - 1] 38 | else 39 | throw 'Can not take the last element' + 40 | ' of an empty array.' 41 | 42 | lastElementPlusTen = (array) -> 43 | lastElement(array) + 10 44 | 45 | try 46 | show lastElementPlusTen [] 47 | catch error 48 | show 'Something went wrong: ' + error 49 | 50 | # Keyword finally 51 | show '--- Keyword finally ---' 52 | currentThing = null 53 | 54 | processThing = (thing) -> 55 | if currentThing != null 56 | throw 'Oh no! We are already processing a thing!' 57 | 58 | currentThing = thing 59 | # do complicated processing... 60 | currentThing = null 61 | 62 | processThing = (thing) -> 63 | if currentThing != null 64 | throw 'Oh no! We are already processing a thing!' 65 | 66 | currentThing = thing 67 | try 68 | # do complicated processing... 69 | finally 70 | currentThing = null 71 | 72 | # System errors 73 | show '--- System errors ---' 74 | try 75 | show Sasquatch 76 | catch error 77 | show 'Caught: ' + error.message 78 | 79 | # Throwing an error 80 | show '--- Throwing an error ---' 81 | try 82 | throw new Error 'Fire!' 83 | catch error 84 | show error 85 | 86 | # Seven Truths 87 | show '--- Seven Truths ---' 88 | FoundSeven = {} 89 | hasSevenTruths = (object) -> 90 | counted = 0 91 | count = (object) -> 92 | for name of object 93 | if object[name] == true 94 | if (++counted) == 7 95 | throw FoundSeven 96 | if typeof object[name] == 'object' 97 | count object[name] 98 | try 99 | count object 100 | return false 101 | catch exception 102 | if exception != FoundSeven 103 | throw exception 104 | return true 105 | 106 | testdata = 107 | a: true 108 | b: true 109 | c: false 110 | d: 111 | a: true 112 | b: false 113 | c: true 114 | d: 115 | a: true 116 | b: 117 | a: true 118 | e: 119 | a: false 120 | b: true 121 | c: true 122 | show hasSevenTruths testdata 123 | -------------------------------------------------------------------------------- /src/A1-LanguageExtras.coffee: -------------------------------------------------------------------------------- 1 | require './prelude' 2 | 3 | # Switch statement 4 | show '--- Switch statement ---' 5 | weatherAdvice = (weather) -> 6 | show 'When it is ' + weather 7 | switch weather 8 | when 'sunny' 9 | show 'Dress lightly.' 10 | show 'Go outside.' 11 | when 'cloudy' 12 | show 'Go outside.' 13 | when 'tornado', 'hurricane' 14 | show 'Seek shelter' 15 | else 16 | show 'Unknown weather type: ' + weather 17 | weatherAdvice 'sunny' 18 | weatherAdvice 'cloudy' 19 | weatherAdvice 'tornado' 20 | weatherAdvice 'hailstorm' 21 | 22 | # Continue statement 23 | show '--- Continue statement ---' 24 | for i in [20...30] 25 | if i % 3 != 0 26 | continue 27 | show i + ' is divisible by three.' 28 | 29 | # Pattern matching 30 | show '--- Pattern matching ---' 31 | class Point 32 | constructor: (@x, @y) -> 33 | pt = new Point 3, 4 34 | {x, y} = pt 35 | show "x is #{x} and y is #{y}" 36 | 37 | firstName = "Alan" 38 | lastName = "Turing" 39 | name = {firstName, lastName} 40 | show name 41 | 42 | decorate = ({firstName, lastName}) -> 43 | show "Distinguished #{firstName} " + 44 | "of the #{lastName} family." 45 | decorate name 46 | 47 | # Unicode identifiers 48 | show '--- Unicode identifiers ---' 49 | pi = π = Math.PI 50 | sphereSurfaceArea = (r) -> 4 * π * r * r 51 | radius = 1 52 | show '4 * π * r * r when r = ' + radius 53 | show sphereSurfaceArea radius 54 | 55 | # When filter 56 | show '--- When filter ---' 57 | evens = (n) -> i for i in [0..n] when i % 2 is 0 58 | show evens 6 59 | 60 | steppenwolf = 61 | title: 'Tonight at the Magic Theater' 62 | warning: 'For Madmen only' 63 | caveat: 'Price of Admittance: Your Mind.' 64 | caution: 'Not for Everybody.' 65 | 66 | stipulations = (text for key, text of steppenwolf \ 67 | when key in ['warning', 'caveat']) 68 | show stipulations 69 | show ultimatum for ultimatum in stipulations \ 70 | when ultimatum.match /Price/ 71 | 72 | # Destructuring assignment 73 | show '--- Destructuring assignment ---' 74 | tautounlogical = "the reason is because I say so" 75 | splitStringAt = (str, n) -> 76 | [str.substring(0,n), str.substring(n)] 77 | [pre, post] = splitStringAt tautounlogical, 14 78 | [pre, post] = [post, pre] # swap 79 | show "#{pre} #{post}" 80 | 81 | [re,mi,fa,sol,la,ti] = [1..6] 82 | [dal,ra...,mim] = [ti,re,fa,sol,la,mi] 83 | show "#{dal}, #{ra} and #{mim}" 84 | 85 | [key, word] = if re > ti then [mi, fa] else [fa, mi] 86 | show "#{key} and #{word}" 87 | 88 | # Bound function 89 | show '--- Bound function ---' 90 | class Widget 91 | id: 'I am a widget' 92 | display: => show @id 93 | 94 | class Container 95 | id: 'I am a container' 96 | callback: (f) -> 97 | show @id 98 | f() 99 | 100 | a = new Widget 101 | a.display() 102 | b = new Container 103 | b.callback a.display 104 | 105 | # Do statement 106 | show '--- Do statement ---' 107 | n = 3 108 | f = -> show "Say: 'Yes!'" 109 | do f 110 | (do -> show "Yes!") while n-- > 0 111 | 112 | echoEchoEcho = (msg) -> msg() + msg() + msg() 113 | show echoEchoEcho -> "No" 114 | 115 | foo = (fun) -> 10*fun() 116 | show foo -> 12 117 | 118 | for i in [1..3] 119 | do (i) -> 120 | setTimeout (-> show 'With do: ' + i), 0 121 | for i in [1..3] 122 | setTimeout (-> show 'Without: ' + i), 0 123 | 124 | setTimeout (-> chain()), 0 125 | chain = -> 126 | 127 | -------------------------------------------------------------------------------- /src-no-solutions/A1-LanguageExtras.coffee: -------------------------------------------------------------------------------- 1 | require './prelude' 2 | 3 | # Switch statement 4 | show '--- Switch statement ---' 5 | weatherAdvice = (weather) -> 6 | show 'When it is ' + weather 7 | switch weather 8 | when 'sunny' 9 | show 'Dress lightly.' 10 | show 'Go outside.' 11 | when 'cloudy' 12 | show 'Go outside.' 13 | when 'tornado', 'hurricane' 14 | show 'Seek shelter' 15 | else 16 | show 'Unknown weather type: ' + weather 17 | weatherAdvice 'sunny' 18 | weatherAdvice 'cloudy' 19 | weatherAdvice 'tornado' 20 | weatherAdvice 'hailstorm' 21 | 22 | # Continue statement 23 | show '--- Continue statement ---' 24 | for i in [20...30] 25 | if i % 3 != 0 26 | continue 27 | show i + ' is divisible by three.' 28 | 29 | # Pattern matching 30 | show '--- Pattern matching ---' 31 | class Point 32 | constructor: (@x, @y) -> 33 | pt = new Point 3, 4 34 | {x, y} = pt 35 | show "x is #{x} and y is #{y}" 36 | 37 | firstName = "Alan" 38 | lastName = "Turing" 39 | name = {firstName, lastName} 40 | show name 41 | 42 | decorate = ({firstName, lastName}) -> 43 | show "Distinguished #{firstName} " + 44 | "of the #{lastName} family." 45 | decorate name 46 | 47 | # Unicode identifiers 48 | show '--- Unicode identifiers ---' 49 | pi = π = Math.PI 50 | sphereSurfaceArea = (r) -> 4 * π * r * r 51 | radius = 1 52 | show '4 * π * r * r when r = ' + radius 53 | show sphereSurfaceArea radius 54 | 55 | # When filter 56 | show '--- When filter ---' 57 | evens = (n) -> i for i in [0..n] when i % 2 is 0 58 | show evens 6 59 | 60 | steppenwolf = 61 | title: 'Tonight at the Magic Theater' 62 | warning: 'For Madmen only' 63 | caveat: 'Price of Admittance: Your Mind.' 64 | caution: 'Not for Everybody.' 65 | 66 | stipulations = (text for key, text of steppenwolf \ 67 | when key in ['warning', 'caveat']) 68 | show stipulations 69 | show ultimatum for ultimatum in stipulations \ 70 | when ultimatum.match /Price/ 71 | 72 | # Destructuring assignment 73 | show '--- Destructuring assignment ---' 74 | tautounlogical = "the reason is because I say so" 75 | splitStringAt = (str, n) -> 76 | [str.substring(0,n), str.substring(n)] 77 | [pre, post] = splitStringAt tautounlogical, 14 78 | [pre, post] = [post, pre] # swap 79 | show "#{pre} #{post}" 80 | 81 | [re,mi,fa,sol,la,ti] = [1..6] 82 | [dal,ra...,mim] = [ti,re,fa,sol,la,mi] 83 | show "#{dal}, #{ra} and #{mim}" 84 | 85 | [key, word] = if re > ti then [mi, fa] else [fa, mi] 86 | show "#{key} and #{word}" 87 | 88 | # Bound function 89 | show '--- Bound function ---' 90 | class Widget 91 | id: 'I am a widget' 92 | display: => show @id 93 | 94 | class Container 95 | id: 'I am a container' 96 | callback: (f) -> 97 | show @id 98 | f() 99 | 100 | a = new Widget 101 | a.display() 102 | b = new Container 103 | b.callback a.display 104 | 105 | # Do statement 106 | show '--- Do statement ---' 107 | n = 3 108 | f = -> show "Say: 'Yes!'" 109 | do f 110 | (do -> show "Yes!") while n-- > 0 111 | 112 | echoEchoEcho = (msg) -> msg() + msg() + msg() 113 | show echoEchoEcho -> "No" 114 | 115 | foo = (fun) -> 10*fun() 116 | show foo -> 12 117 | 118 | for i in [1..3] 119 | do (i) -> 120 | setTimeout (-> show 'With do: ' + i), 0 121 | for i in [1..3] 122 | setTimeout (-> show 'Without: ' + i), 0 123 | 124 | setTimeout (-> chain()), 0 125 | chain = -> 126 | 127 | -------------------------------------------------------------------------------- /src/A2-BinaryHeap.coffee: -------------------------------------------------------------------------------- 1 | 2 | class BinaryHeap 3 | 4 | # Public 5 | #-------- 6 | constructor: (@scoreFunction = (x) -> x) -> 7 | @content = [] 8 | 9 | push: (element) -> 10 | # Add the new element to the end of the array. 11 | @content.push element 12 | # Allow it to bubble up. 13 | @_bubbleUp @content.length - 1 14 | 15 | pop: -> 16 | # Store the first element so we can return it later. 17 | result = @content[0] 18 | # Get the element at the end of the array. 19 | end = @content.pop() 20 | # If there are any elements left, put the end 21 | # element at the start, and let it sink down. 22 | if @content.length > 0 23 | @content[0] = end 24 | @_sinkDown 0 25 | result 26 | 27 | size: -> @content.length 28 | 29 | remove: (node) -> 30 | len = @content.length 31 | # To remove a value, we must search through the 32 | # array to find it. 33 | for i in [0...len] 34 | if @content[i] == node 35 | # When it is found, the process seen in 'pop' 36 | # is repeated to fill up the hole. 37 | end = @content.pop() 38 | if i != len - 1 39 | @content[i] = end 40 | if @scoreFunction(end) < @scoreFunction(node) 41 | @_bubbleUp i 42 | else 43 | @_sinkDown i 44 | return 45 | throw new Error 'Node not found.' 46 | 47 | # Private 48 | #--------- 49 | _bubbleUp: (n) -> 50 | # Fetch the element that has to be moved. 51 | element = @content[n] 52 | # When at 0, an element can not go up any further. 53 | while n > 0 54 | # Compute the parent element index, and fetch it. 55 | parentN = Math.floor((n + 1) / 2) - 1 56 | parent = @content[parentN] 57 | # Swap the elements if the parent is greater. 58 | if @scoreFunction(element) < @scoreFunction(parent) 59 | @content[parentN] = element 60 | @content[n] = parent 61 | # Update 'n' to continue at the new position. 62 | n = parentN 63 | # Found a parent that is less, 64 | # no need to move it further. 65 | else 66 | break 67 | return 68 | 69 | _sinkDown: (n) -> 70 | # Look up the target element and its score. 71 | length = @content.length 72 | element = @content[n] 73 | elemScore = @scoreFunction element 74 | loop 75 | # Compute the indices of the child elements. 76 | child2N = (n + 1) * 2 77 | child1N = child2N - 1 78 | # This is used to store the new position of 79 | # the element, if any. 80 | swap = null 81 | # If the first child exists (is inside the array)... 82 | if child1N < length 83 | # Look it up and compute its score. 84 | child1 = @content[child1N] 85 | child1Score = this.scoreFunction child1 86 | # If the score is less than our elements, 87 | # we need to swap. 88 | if child1Score < elemScore 89 | swap = child1N 90 | # Do the same checks for the other child. 91 | if child2N < length 92 | child2 = @content[child2N] 93 | child2Score = @scoreFunction child2 94 | compScore = if swap == null 95 | elemScore 96 | else 97 | child1Score 98 | if child2Score < compScore 99 | swap = child2N 100 | # If the element needs to be moved, 101 | # swap it, and continue. 102 | if swap != null 103 | @content[n] = @content[swap] 104 | @content[swap] = element 105 | n = swap 106 | # Otherwise, we are done. 107 | else 108 | break 109 | return 110 | 111 | (exports ? this).BinaryHeap = BinaryHeap 112 | -------------------------------------------------------------------------------- /src-no-solutions/A2-BinaryHeap.coffee: -------------------------------------------------------------------------------- 1 | 2 | class BinaryHeap 3 | 4 | # Public 5 | #-------- 6 | constructor: (@scoreFunction = (x) -> x) -> 7 | @content = [] 8 | 9 | push: (element) -> 10 | # Add the new element to the end of the array. 11 | @content.push element 12 | # Allow it to bubble up. 13 | @_bubbleUp @content.length - 1 14 | 15 | pop: -> 16 | # Store the first element so we can return it later. 17 | result = @content[0] 18 | # Get the element at the end of the array. 19 | end = @content.pop() 20 | # If there are any elements left, put the end 21 | # element at the start, and let it sink down. 22 | if @content.length > 0 23 | @content[0] = end 24 | @_sinkDown 0 25 | result 26 | 27 | size: -> @content.length 28 | 29 | remove: (node) -> 30 | len = @content.length 31 | # To remove a value, we must search through the 32 | # array to find it. 33 | for i in [0...len] 34 | if @content[i] == node 35 | # When it is found, the process seen in 'pop' 36 | # is repeated to fill up the hole. 37 | end = @content.pop() 38 | if i != len - 1 39 | @content[i] = end 40 | if @scoreFunction(end) < @scoreFunction(node) 41 | @_bubbleUp i 42 | else 43 | @_sinkDown i 44 | return 45 | throw new Error 'Node not found.' 46 | 47 | # Private 48 | #--------- 49 | _bubbleUp: (n) -> 50 | # Fetch the element that has to be moved. 51 | element = @content[n] 52 | # When at 0, an element can not go up any further. 53 | while n > 0 54 | # Compute the parent element index, and fetch it. 55 | parentN = Math.floor((n + 1) / 2) - 1 56 | parent = @content[parentN] 57 | # Swap the elements if the parent is greater. 58 | if @scoreFunction(element) < @scoreFunction(parent) 59 | @content[parentN] = element 60 | @content[n] = parent 61 | # Update 'n' to continue at the new position. 62 | n = parentN 63 | # Found a parent that is less, 64 | # no need to move it further. 65 | else 66 | break 67 | return 68 | 69 | _sinkDown: (n) -> 70 | # Look up the target element and its score. 71 | length = @content.length 72 | element = @content[n] 73 | elemScore = @scoreFunction element 74 | loop 75 | # Compute the indices of the child elements. 76 | child2N = (n + 1) * 2 77 | child1N = child2N - 1 78 | # This is used to store the new position of 79 | # the element, if any. 80 | swap = null 81 | # If the first child exists (is inside the array)... 82 | if child1N < length 83 | # Look it up and compute its score. 84 | child1 = @content[child1N] 85 | child1Score = this.scoreFunction child1 86 | # If the score is less than our elements, 87 | # we need to swap. 88 | if child1Score < elemScore 89 | swap = child1N 90 | # Do the same checks for the other child. 91 | if child2N < length 92 | child2 = @content[child2N] 93 | child2Score = @scoreFunction child2 94 | compScore = if swap == null 95 | elemScore 96 | else 97 | child1Score 98 | if child2Score < compScore 99 | swap = child2N 100 | # If the element needs to be moved, 101 | # swap it, and continue. 102 | if swap != null 103 | @content[n] = @content[swap] 104 | @content[swap] = element 105 | n = swap 106 | # Otherwise, we are done. 107 | else 108 | break 109 | return 110 | 111 | (exports ? this).BinaryHeap = BinaryHeap 112 | -------------------------------------------------------------------------------- /src-no-solutions/09-RegularExpressions.coffee: -------------------------------------------------------------------------------- 1 | require './prelude' 2 | # Bring underscore into global 3 | globalize _ 4 | 5 | # Slashes and escapes 6 | show '--- Slashes and escapes ---' 7 | slash = /\// 8 | show 'AC/DC'.search slash 9 | 10 | asteriskOrBrace = /[\{\*]/ 11 | story = 'We noticed the *giant sloth*, ' + 12 | 'hanging from a giant branch.'; 13 | show story.search asteriskOrBrace 14 | 15 | # Special characters 16 | show '--- Special characters ---' 17 | digitSurroundedBySpace = /\s\d\s/ 18 | show '1a 2 3d'.search digitSurroundedBySpace 19 | 20 | notABC = /[^ABC]/ 21 | show 'ABCBACCBBADABC'.search notABC 22 | 23 | # Exercise 32 24 | show '--- Exercise 32 ---' 25 | process.exit() # Replace this line with your solution 26 | show '--- End of Exercise ---' 27 | 28 | # Boundaries 29 | show '--- Boundaries ---' 30 | show /a+/.test 'blah' 31 | show /^a+$/.test 'blah' 32 | 33 | show /cat/.test 'concatenate' 34 | show /\bcat\b/.test 'concatenate' 35 | 36 | # Repetitions 37 | show '--- Repetitions ---' 38 | parenthesizedText = /\(.*\)/ 39 | show "Its (the sloth's) claws were gigantic!"\ 40 | .search parenthesizedText 41 | 42 | datePattern = /\d{1,2}\/\d\d?\/\d{4}/ 43 | show 'born 15/11/2003 (mother Spot): White Fang'\ 44 | .search datePattern 45 | 46 | datePattern = /// 47 | \d{1,2} # day 48 | / # separator 49 | \d\d? # month 50 | / # separator 51 | \d{4} # year 52 | /// 53 | show 'born 15/11/2003 (mother Spot): White Fang'\ 54 | .search datePattern 55 | 56 | # Exercise 33 57 | show '--- Exercise 33 ---' 58 | process.exit() # Replace this line with your solution 59 | show '--- End of Exercise ---' 60 | 61 | # Grouping 62 | show '--- Grouping ---' 63 | cartoonCrying = /boo(hoo+)+/i 64 | show "Then, he exclaimed 'Boohoooohoohooo'"\ 65 | .search cartoonCrying 66 | 67 | # Choice 68 | show '--- Choice ---' 69 | holyCow = /(sacred|holy) (cow|bovine|bull|taurus)/i 70 | show holyCow.test 'Sacred bovine!' 71 | 72 | # Match method 73 | show '--- Match method ---' 74 | show 'No'.match /Yes/ 75 | show '... yes'.match /yes/ 76 | show 'Giant Ape'.match /giant (\w+)/i 77 | 78 | quote = "My mind is a swirling miasma " + 79 | "(a poisonous fog thought to " + 80 | "cause illness) of titilating " + 81 | "thoughts and turgid ideas." 82 | parenthesized = quote.match /// 83 | (\w+) # Word 84 | \s* # Whitespace 85 | \((.*)\) # Explanation 86 | /// 87 | if parenthesized isnt null 88 | show "Word: #{parenthesized[1]} " + 89 | "Explanation: #{parenthesized[2]}" 90 | 91 | # Exercise 34 92 | show '--- Exercise 34 ---' 93 | process.exit() # Replace this line with your solution 94 | show '--- End of Exercise ---' 95 | 96 | # Replace method 97 | show '--- Replace method ---' 98 | show 'Borobudur'.replace /[ou]/g, 'a' 99 | 100 | # Numbered parts 101 | show '--- Numbered parts ---' 102 | names = '''Picasso, Pablo 103 | Gauguin, Paul 104 | Van Gogh, Vincent''' 105 | 106 | show names.replace /([\w ]+), ([\w ]+)/g, '$2 $1' 107 | 108 | show names.replace /// 109 | ([\w ]+) # Lastname 110 | , 111 | ([\w ]+) # Firstname 112 | ///g, '$2 $1' 113 | 114 | # Replacement function 115 | show '--- Replacement function ---' 116 | eatOne = (match, amount, unit) -> 117 | amount = Number(amount) - 1 118 | if amount == 1 119 | unit = unit.slice 0, unit.length - 1 120 | else if amount == 0 121 | unit = unit + 's' 122 | amount = 'no' 123 | amount + ' ' + unit 124 | 125 | stock = '1 lemon, 2 cabbages, and 101 eggs' 126 | stock = stock.replace /(\d+) (\w+)/g, eatOne 127 | show stock 128 | 129 | # Exercise 35 130 | show '--- Exercise 35 ---' 131 | process.exit() # Replace this line with your solution 132 | show '--- End of Exercise ---' 133 | 134 | # Word filter 135 | show '--- Word filter ---' 136 | badWords = ['ape', 'monkey', 'simian', 137 | 'gorilla', 'evolution'] 138 | pattern = new RegExp badWords.join('|'), 'i' 139 | isAcceptable = (text) -> 140 | !pattern.test text 141 | 142 | show isAcceptable 'Mmmm, grapes.' 143 | show isAcceptable 'No more of that monkeybusiness, now.' 144 | 145 | # Escape escapes 146 | show '--- Escape escapes ---' 147 | digits = new RegExp '\\d+' 148 | show digits.test '101' 149 | 150 | -------------------------------------------------------------------------------- /src/06-RecluseFile.text: -------------------------------------------------------------------------------- 1 | % The Book of Programming 2 | 3 | %% The Two Aspects 4 | 5 | Below the surface of the machine, the program moves. Without effort, it expands and contracts. In great harmony, electrons scatter and regroup. The forms on the monitor are but ripples on the water. The essence stays invisibly below. 6 | 7 | When the creators built the machine, they put in the processor and the memory. From these arise the two aspects of the program. 8 | 9 | The aspect of the processor is the active substance. It is called Control. The aspect of the memory is the passive substance. It is called Data. 10 | 11 | Data is made of merely bits, yet it takes complex forms. Control consists only of simple instructions, yet it performs difficult tasks. From the small and trivial, the large and complex arise. 12 | 13 | The program source is Data. Control arises from it. The Control proceeds to create new Data. The one is born from the other, the other is useless without the one. This is the harmonious cycle of Data and Control. 14 | 15 | Of themselves, Data and Control are without structure. The programmers of old moulded their programs out of this raw substance. Over time, the amorphous Data has crystallised into data types, and the chaotic Control was restricted into control structures and functions. 16 | 17 | %% Short Sayings 18 | 19 | When a student asked Fu-Tzu about the nature of the cycle of Data and Control, Fu-Tzu replied 'Think of a compiler, compiling itself.' 20 | 21 | A student asked 'The programmers of old used only simple machines and no programming languages, yet they made beautiful programs. Why do we use complicated machines and programming languages?'. Fu-Tzu replied 'The builders of old used only sticks and clay, yet they made beautiful huts.' 22 | 23 | A hermit spent ten years writing a program. 'My program can compute the motion of the stars on a 286-computer running MS DOS', he proudly announced. 'Nobody owns a 286-computer or uses MS DOS anymore.', Fu-Tzu responded. 24 | 25 | Fu-Tzu had written a small program that was full of global state and dubious shortcuts. Reading it, a student asked 'You warned us against these techniques, yet I find them in your program. How can this be?' Fu-Tzu said 'There is no need to fetch a water hose when the house is not on fire.'{This is not to be read as an encouragement of sloppy programming, but rather as a warning against neurotic adherence to rules of thumb.} 26 | 27 | %% Wisdom 28 | 29 | A student was complaining about digital numbers. 'When I take the root of two and then square it again, the result is already inaccurate!'. Overhearing him, Fu-Tzu laughed. 'Here is a sheet of paper. Write down the precise value of the square root of two for me.' 30 | 31 | Fu-Tzu said 'When you cut against the grain of the wood, much strength is needed. When you program against the grain of a problem, much code is needed.' 32 | 33 | Tzu-li and Tzu-ssu were boasting about the size of their latest programs. 'Two-hundred thousand lines', said Tzu-li, 'not counting comments!'. 'Psah', said Tzu-ssu, 'mine is almost a *million* lines already.' Fu-Tzu said 'My best program has five hundred lines.' Hearing this, Tzu-li and Tzu-ssu were enlightened. 34 | 35 | A student had been sitting motionless behind his computer for hours, frowning darkly. He was trying to write a beautiful solution to a difficult problem, but could not find the right approach. Fu-Tzu hit him on the back of his head and shouted '*Type something!*' The student started writing an ugly solution. After he had finished, he suddenly understood the beautiful solution. 36 | 37 | %% Progression 38 | 39 | A beginning programmer writes his programs like an ant builds her hill, one piece at a time, without thought for the bigger structure. His programs will be like loose sand. They may stand for a while, but growing too big they fall apart{Referring to the danger of internal inconsistency and duplicated structure in unorganised code.}. 40 | 41 | Realising this problem, the programmer will start to spend a lot of time thinking about structure. His programs will be rigidly structured, like rock sculptures. They are solid, but when they must change, violence must be done to them{Referring to the fact that structure tends to put restrictions on the evolution of a program.}. 42 | 43 | The master programmer knows when to apply structure and when to leave things in their simple form. His programs are like clay, solid yet malleable. 44 | 45 | %% Language 46 | 47 | When a programming language is created, it is given syntax and semantics. The syntax describes the form of the program, the semantics describe the function. When the syntax is beautiful and the semantics are clear, the program will be like a stately tree. When the syntax is clumsy and the semantics confusing, the program will be like a bramble bush. 48 | 49 | Tzu-ssu was asked to write a program in the language called Java, which takes a very primitive approach to functions. Every morning, as he sat down in front of his computer, he started complaining. All day he cursed, blaming the language for all that went wrong. Fu-Tzu listened for a while, and then reproached him, saying 'Every language has its own way. Follow its form, do not try to program as if you were using another language.' 50 | -------------------------------------------------------------------------------- /src-no-solutions/06-RecluseFile.text: -------------------------------------------------------------------------------- 1 | % The Book of Programming 2 | 3 | %% The Two Aspects 4 | 5 | Below the surface of the machine, the program moves. Without effort, it expands and contracts. In great harmony, electrons scatter and regroup. The forms on the monitor are but ripples on the water. The essence stays invisibly below. 6 | 7 | When the creators built the machine, they put in the processor and the memory. From these arise the two aspects of the program. 8 | 9 | The aspect of the processor is the active substance. It is called Control. The aspect of the memory is the passive substance. It is called Data. 10 | 11 | Data is made of merely bits, yet it takes complex forms. Control consists only of simple instructions, yet it performs difficult tasks. From the small and trivial, the large and complex arise. 12 | 13 | The program source is Data. Control arises from it. The Control proceeds to create new Data. The one is born from the other, the other is useless without the one. This is the harmonious cycle of Data and Control. 14 | 15 | Of themselves, Data and Control are without structure. The programmers of old moulded their programs out of this raw substance. Over time, the amorphous Data has crystallised into data types, and the chaotic Control was restricted into control structures and functions. 16 | 17 | %% Short Sayings 18 | 19 | When a student asked Fu-Tzu about the nature of the cycle of Data and Control, Fu-Tzu replied 'Think of a compiler, compiling itself.' 20 | 21 | A student asked 'The programmers of old used only simple machines and no programming languages, yet they made beautiful programs. Why do we use complicated machines and programming languages?'. Fu-Tzu replied 'The builders of old used only sticks and clay, yet they made beautiful huts.' 22 | 23 | A hermit spent ten years writing a program. 'My program can compute the motion of the stars on a 286-computer running MS DOS', he proudly announced. 'Nobody owns a 286-computer or uses MS DOS anymore.', Fu-Tzu responded. 24 | 25 | Fu-Tzu had written a small program that was full of global state and dubious shortcuts. Reading it, a student asked 'You warned us against these techniques, yet I find them in your program. How can this be?' Fu-Tzu said 'There is no need to fetch a water hose when the house is not on fire.'{This is not to be read as an encouragement of sloppy programming, but rather as a warning against neurotic adherence to rules of thumb.} 26 | 27 | %% Wisdom 28 | 29 | A student was complaining about digital numbers. 'When I take the root of two and then square it again, the result is already inaccurate!'. Overhearing him, Fu-Tzu laughed. 'Here is a sheet of paper. Write down the precise value of the square root of two for me.' 30 | 31 | Fu-Tzu said 'When you cut against the grain of the wood, much strength is needed. When you program against the grain of a problem, much code is needed.' 32 | 33 | Tzu-li and Tzu-ssu were boasting about the size of their latest programs. 'Two-hundred thousand lines', said Tzu-li, 'not counting comments!'. 'Psah', said Tzu-ssu, 'mine is almost a *million* lines already.' Fu-Tzu said 'My best program has five hundred lines.' Hearing this, Tzu-li and Tzu-ssu were enlightened. 34 | 35 | A student had been sitting motionless behind his computer for hours, frowning darkly. He was trying to write a beautiful solution to a difficult problem, but could not find the right approach. Fu-Tzu hit him on the back of his head and shouted '*Type something!*' The student started writing an ugly solution. After he had finished, he suddenly understood the beautiful solution. 36 | 37 | %% Progression 38 | 39 | A beginning programmer writes his programs like an ant builds her hill, one piece at a time, without thought for the bigger structure. His programs will be like loose sand. They may stand for a while, but growing too big they fall apart{Referring to the danger of internal inconsistency and duplicated structure in unorganised code.}. 40 | 41 | Realising this problem, the programmer will start to spend a lot of time thinking about structure. His programs will be rigidly structured, like rock sculptures. They are solid, but when they must change, violence must be done to them{Referring to the fact that structure tends to put restrictions on the evolution of a program.}. 42 | 43 | The master programmer knows when to apply structure and when to leave things in their simple form. His programs are like clay, solid yet malleable. 44 | 45 | %% Language 46 | 47 | When a programming language is created, it is given syntax and semantics. The syntax describes the form of the program, the semantics describe the function. When the syntax is beautiful and the semantics are clear, the program will be like a stately tree. When the syntax is clumsy and the semantics confusing, the program will be like a bramble bush. 48 | 49 | Tzu-ssu was asked to write a program in the language called Java, which takes a very primitive approach to functions. Every morning, as he sat down in front of his computer, he started complaining. All day he cursed, blaming the language for all that went wrong. Fu-Tzu listened for a while, and then reproached him, saying 'Every language has its own way. Follow its form, do not try to program as if you were using another language.' 50 | -------------------------------------------------------------------------------- /src/09-RegularExpressions.coffee: -------------------------------------------------------------------------------- 1 | require './prelude' 2 | # Bring underscore into global 3 | globalize _ 4 | 5 | # Slashes and escapes 6 | show '--- Slashes and escapes ---' 7 | slash = /\// 8 | show 'AC/DC'.search slash 9 | 10 | asteriskOrBrace = /[\{\*]/ 11 | story = 'We noticed the *giant sloth*, ' + 12 | 'hanging from a giant branch.'; 13 | show story.search asteriskOrBrace 14 | 15 | # Special characters 16 | show '--- Special characters ---' 17 | digitSurroundedBySpace = /\s\d\s/ 18 | show '1a 2 3d'.search digitSurroundedBySpace 19 | 20 | notABC = /[^ABC]/ 21 | show 'ABCBACCBBADABC'.search notABC 22 | 23 | # Exercise 32 24 | show '--- Exercise 32 ---' 25 | datePattern = /\d\d\/\d\d\/\d\d\d\d/ 26 | show 'born 15/11/2003 (mother Spot): White Fang'\ 27 | .search datePattern 28 | show '--- End of Exercise ---' 29 | 30 | # Boundaries 31 | show '--- Boundaries ---' 32 | show /a+/.test 'blah' 33 | show /^a+$/.test 'blah' 34 | 35 | show /cat/.test 'concatenate' 36 | show /\bcat\b/.test 'concatenate' 37 | 38 | # Repetitions 39 | show '--- Repetitions ---' 40 | parenthesizedText = /\(.*\)/ 41 | show "Its (the sloth's) claws were gigantic!"\ 42 | .search parenthesizedText 43 | 44 | datePattern = /\d{1,2}\/\d\d?\/\d{4}/ 45 | show 'born 15/11/2003 (mother Spot): White Fang'\ 46 | .search datePattern 47 | 48 | datePattern = /// 49 | \d{1,2} # day 50 | / # separator 51 | \d\d? # month 52 | / # separator 53 | \d{4} # year 54 | /// 55 | show 'born 15/11/2003 (mother Spot): White Fang'\ 56 | .search datePattern 57 | 58 | # Exercise 33 59 | show '--- Exercise 33 ---' 60 | mailAddress = /\b[\w\.-]+@[\w\.-]+\.\w{2,3}\b/ 61 | 62 | mailAddress = /// 63 | \b[\w\.-]+ # username 64 | @ 65 | [\w\.-]+\ # provider 66 | . 67 | \w{2,3}\b # domain 68 | /// 69 | 70 | show mailAddress.test 'kenny@test.net' 71 | show mailAddress.test 'I mailt kenny@tets.nets, ' + 72 | 'but it didn wrok!' 73 | show mailAddress.test 'the_giant_sloth@gmail.com' 74 | show '--- End of Exercise ---' 75 | 76 | # Grouping 77 | show '--- Grouping ---' 78 | cartoonCrying = /boo(hoo+)+/i 79 | show "Then, he exclaimed 'Boohoooohoohooo'"\ 80 | .search cartoonCrying 81 | 82 | # Choice 83 | show '--- Choice ---' 84 | holyCow = /(sacred|holy) (cow|bovine|bull|taurus)/i 85 | show holyCow.test 'Sacred bovine!' 86 | 87 | # Match method 88 | show '--- Match method ---' 89 | show 'No'.match /Yes/ 90 | show '... yes'.match /yes/ 91 | show 'Giant Ape'.match /giant (\w+)/i 92 | 93 | quote = "My mind is a swirling miasma " + 94 | "(a poisonous fog thought to " + 95 | "cause illness) of titilating " + 96 | "thoughts and turgid ideas." 97 | parenthesized = quote.match /// 98 | (\w+) # Word 99 | \s* # Whitespace 100 | \((.*)\) # Explanation 101 | /// 102 | if parenthesized isnt null 103 | show "Word: #{parenthesized[1]} " + 104 | "Explanation: #{parenthesized[2]}" 105 | 106 | # Exercise 34 107 | show '--- Exercise 34 ---' 108 | extractDate = (string) -> 109 | found = string.match /(\d\d?)\/(\d\d?)\/(\d{4})/ 110 | if found == null 111 | throw new Error "No date found in '#{string}'." 112 | new Date Number(found[3]), 113 | Number(found[2]) - 1, 114 | Number(found[1]) 115 | show extractDate \ 116 | "born 5/2/2007 (mother Noog): Long-ear Johnson" 117 | show '--- End of Exercise ---' 118 | 119 | # Replace method 120 | show '--- Replace method ---' 121 | show 'Borobudur'.replace /[ou]/g, 'a' 122 | 123 | # Numbered parts 124 | show '--- Numbered parts ---' 125 | names = '''Picasso, Pablo 126 | Gauguin, Paul 127 | Van Gogh, Vincent''' 128 | 129 | show names.replace /([\w ]+), ([\w ]+)/g, '$2 $1' 130 | 131 | show names.replace /// 132 | ([\w ]+) # Lastname 133 | , 134 | ([\w ]+) # Firstname 135 | ///g, '$2 $1' 136 | 137 | # Replacement function 138 | show '--- Replacement function ---' 139 | eatOne = (match, amount, unit) -> 140 | amount = Number(amount) - 1 141 | if amount == 1 142 | unit = unit.slice 0, unit.length - 1 143 | else if amount == 0 144 | unit = unit + 's' 145 | amount = 'no' 146 | amount + ' ' + unit 147 | 148 | stock = '1 lemon, 2 cabbages, and 101 eggs' 149 | stock = stock.replace /(\d+) (\w+)/g, eatOne 150 | show stock 151 | 152 | # Exercise 35 153 | show '--- Exercise 35 ---' 154 | escapeHTML = (text) -> 155 | replacements = [[/&/g, '&'] 156 | [/"/g, '"'] 157 | [//g, '>']] 159 | forEach replacements, (replace) -> 160 | text = text.replace replace[0], replace[1] 161 | text 162 | show escapeHTML '< " & " >' 163 | 164 | escapeHTML = (text) -> 165 | replacements = 166 | "<": "<" 167 | ">": ">" 168 | "&": "&" 169 | "\"": """ 170 | text.replace /[<>&"]/g, (character) -> 171 | replacements[character] 172 | 173 | show escapeHTML "The 'pre-formatted' tag " + 174 | "is written \"
\"."
175 | show '--- End of Exercise ---'
176 | 
177 | # Word filter
178 | show '--- Word filter ---'
179 | badWords = ['ape', 'monkey', 'simian',
180 |             'gorilla', 'evolution']
181 | pattern = new RegExp badWords.join('|'), 'i'
182 | isAcceptable = (text) ->
183 |   !pattern.test text
184 | 
185 | show isAcceptable 'Mmmm, grapes.'
186 | show isAcceptable 'No more of that monkeybusiness, now.'
187 | 
188 | # Escape escapes
189 | show '--- Escape escapes ---'
190 | digits = new RegExp '\\d+'
191 | show digits.test '101'
192 | 
193 | 


--------------------------------------------------------------------------------
/src/docs/10-CircularTest.html:
--------------------------------------------------------------------------------
 1 |       10-CircularTest.coffee              


--------------------------------------------------------------------------------
/src-no-solutions/docs/10-CircularTest.html:
--------------------------------------------------------------------------------
 1 |       10-CircularTest.coffee              


--------------------------------------------------------------------------------
/src-no-solutions/03-Functions.coffee:
--------------------------------------------------------------------------------
  1 | require './prelude'
  2 | 
  3 | # Pure functions
  4 | show '--- Pure functions ---'
  5 | add = (a, b) -> a + b
  6 | show add 2, 2
  7 | 
  8 | power = (base, exponent) ->
  9 |   result = 1
 10 |   for count in [0...exponent]
 11 |     result *= base
 12 |   result
 13 | show power 2, 10
 14 | 
 15 | # Exercise 7
 16 | show '--- Exercise 7 ---'
 17 | process.exit() # Replace this line with your solution
 18 | show '--- End of Exercise ---'
 19 | 
 20 | # Declarative tests
 21 | show '--- Declarative tests ---'
 22 | testAbsolute = (name, property) ->
 23 |   qc.testPure absolute, [qc.arbInt], name, property
 24 | 
 25 | testAbsolute 'returns positive integers',
 26 |   (c, arg, result) -> result >= 0
 27 | 
 28 | testAbsolute 'positive returns positive',
 29 |   (c, arg, result) -> c.guard arg >= 0; result is arg
 30 | 
 31 | testAbsolute 'negative returns positive',
 32 |   (c, arg, result) -> c.guard arg < 0; result is -arg
 33 | 
 34 | qc.testPure power, [qc.arbInt, qc.arbInt],
 35 |   'power == Math.pow for integers',
 36 |   (c, base, exponent, result) ->
 37 |     result == c.note Math.pow base, exponent
 38 | 
 39 | qc.testPure power, [qc.arbWholeNum, qc.arbWholeNum],
 40 |   'power == Math.pow for positive integers',
 41 |   (c, base, exponent, result) ->
 42 |     result == c.note Math.pow base, exponent
 43 | 
 44 | qc.test()
 45 | 
 46 | # Quick hack to avoid garbling of output
 47 | # when async test cases are reported.
 48 | # A better idea is one test suite in one file.
 49 | setTimeout ( ->
 50 | 
 51 |   # Exercise 8
 52 | 
 53 |   intensify = (n) ->
 54 |     2 # This will fail
 55 | 
 56 |   show '--- Exercise 8 ---'
 57 |   process.exit() # Replace this line with your solution
 58 |   show '--- End of Exercise ---'
 59 | 
 60 |   qc.testPure intensify, [qc.arbInt],
 61 |     'intensify grows by 2 when positive',
 62 |     (c, arg, result) ->
 63 |       c.guard arg > 0
 64 |       arg + 2 == result
 65 | 
 66 |   qc.testPure intensify, [qc.arbInt],
 67 |     'intensify grows by 2 when negative',
 68 |     (c, arg, result) ->
 69 |       c.guard arg < 0
 70 |       arg - 2 == result
 71 | 
 72 |   qc.testPure intensify, [qc.arbConst(0)],
 73 |     'only non-zero intensify grows',
 74 |     (c, arg, result) ->
 75 |       result is arg
 76 | 
 77 |   qc.test()
 78 | 
 79 |   setTimeout ( ->
 80 |     
 81 |     # Return revisited
 82 |     show '--- Return revisited ---'
 83 |     yell = (message) ->
 84 |       show message + '!!'
 85 |       return
 86 |     yell 'Yow'
 87 | 
 88 |     # Local scopes
 89 |     show '--- Local scopes ---'
 90 |     dino = 'I am alive'
 91 |     reptile = 'I am A-OK'
 92 |     meteor = (reptile) ->
 93 |       show reptile            # Argument
 94 |       dino = 'I am extinct'
 95 |       reptile = 'I survived'
 96 |       possum = 'I am new'
 97 |     show dino                 # Outer
 98 |     meteor 'What happened?'
 99 |     show dino                 # Outer changed
100 |     show reptile              # Outer unchanged
101 |     try show possum catch e
102 |       show e.message          # Error undefined
103 | 
104 |     # Local environment
105 |     show '--- Local environment ---'
106 |     variable = 'first'                    # Definition
107 | 
108 |     showVariable = ->
109 |       show 'In showVariable, the variable holds: ' +
110 |             variable                      # second
111 | 
112 |     test = ->
113 |       variable = 'second'                 # Assignment
114 |       show 'In test, the variable holds ' +
115 |            variable + '.'                 # second
116 |       showVariable()
117 | 
118 |     show 'The variable is: ' + variable   # first
119 |     test()
120 |     show 'The variable is: ' + variable   # second
121 | 
122 |     # Siblings and children
123 |     show '--- Siblings and children ---'
124 |     andHere = ->
125 |       try show aLocal                     # Not defined
126 |       catch e then show e.message
127 |     isHere = ->
128 |       aLocal = 'aLocal is defined'
129 |       andHere()
130 |     isHere()
131 | 
132 |     isHere = ->
133 |       andHere = ->
134 |         try show aLocal                   # Is defined
135 |         catch e then show e.message
136 |       aLocal = 'aLocal is defined'
137 |       andHere()
138 |     isHere()
139 | 
140 |     # Internal function variable
141 |     show '--- Internal function variable ---'
142 |     varWhich = 'top-level'
143 |     parentFunction = ->
144 |       varWhich = 'local'
145 |       childFunction = ->
146 |         show varWhich
147 |       childFunction
148 |     child = parentFunction()
149 |     child()
150 | 
151 |     # Synthesized function
152 |     show '--- Synthesized function ---'
153 |     makeAddFunction = (amount) ->
154 |       add = (number) -> number + amount
155 | 
156 |     addTwo = makeAddFunction 2
157 |     addFive = makeAddFunction 5
158 |     show addTwo(1) + addFive(1)
159 | 
160 |     # Recursion
161 |     show '--- Recursion ---'
162 |     powerRec = (base, exponent) ->
163 |       if exponent == 0
164 |         1
165 |       else
166 |         base * powerRec base, exponent - 1
167 |     show 'power 3, 3 = ' + powerRec 3, 3
168 | 
169 |     # Timing
170 |     show '--- Timing ---'
171 |     timeIt = (func) ->
172 |       start = new Date()
173 |       for i in [0...1000000] then func()
174 |       show "Timing: #{(new Date() - start)*0.001}s"
175 | 
176 |     timeIt -> p = add 9,18                # 0.042s
177 |     timeIt -> p = Math.pow 9,18           # 0.049s
178 |     timeIt -> p = power 9,18              # 0.464s
179 |     timeIt -> p = powerRec 9,18           # 0.544s
180 | 
181 |     # Indirect recursion
182 |     show '--- Indirect recursion ---'
183 |     chicken = ->
184 |       show 'Lay an egg'
185 |       egg()
186 |     egg     = ->
187 |       show 'Chick hatched'
188 |       chicken()
189 |     try show chicken() + ' came first.'
190 |     catch error then show error.message
191 | 
192 |     # Recursive puzzle solving
193 |     show '--- Recursive puzzle solving ---'
194 |     findSequence = (goal) ->
195 |       find = (start, history) ->
196 |         if start == goal
197 |           history
198 |         else if start > goal
199 |           null
200 |         else
201 |           find(start + 5, '(' + history + ' + 5)') ? \
202 |           find(start * 3, '(' + history + ' * 3)')
203 |       find 1, '1'
204 |     show findSequence 24
205 | 
206 |     # Anonymous functions
207 |     show '--- Anonymous functions ---'
208 |     makeAddFunction = (amount) ->
209 |       (number) -> number + amount
210 |     show makeAddFunction(11) 3
211 | 
212 |     # Exercise 9
213 |     show '--- Exercise 9 ---'
214 |     process.exit() # Replace this line with your solution
215 |     show '--- End of Exercise ---'
216 | 
217 |     # Extra function arguments
218 |     show '--- Extra function arguments ---'
219 |     yell 'Hello', 'Good Evening', 'How do you do?'
220 |     yell()
221 | 
222 |     # Variable number of arguments
223 |     show '--- Variable number of arguments ---'
224 |     console.log 'R', 2, 'D', 2
225 | 
226 |     ),300
227 |   ),300
228 | 
229 | 
230 | 
231 | 
232 | 
233 | 


--------------------------------------------------------------------------------
/src/prelude/coffeekup.coffee:
--------------------------------------------------------------------------------
  1 | if window?
  2 |   coffeekup = window.CoffeeKup = {}
  3 |   coffee = if CoffeeScript? then CoffeeScript else null
  4 | else
  5 |   coffeekup = exports
  6 |   coffee = require 'coffee-script'
  7 | 
  8 | coffeekup.version = '0.2.3'
  9 | 
 10 | skeleton = (ck_options = {}) ->
 11 |   ck_options.context ?= {}
 12 |   ck_options.locals ?= {}
 13 |   ck_options.format ?= off
 14 |   ck_options.autoescape ?= off
 15 |   ck_buffer = []
 16 | 
 17 |   ck_render_attrs = (obj) ->
 18 |     str = ''
 19 |     for k, v of obj
 20 |       str += " #{k}=\"#{ck_esc(v)}\""
 21 |     str
 22 | 
 23 |   ck_doctypes =
 24 |     '5': ''
 25 |     'xml': ''
 26 |     'default': ''
 27 |     'transitional': ''
 28 |     'strict': ''
 29 |     'frameset': ''
 30 |     '1.1': '',
 31 |     'basic': ''
 32 |     'mobile': ''
 33 | 
 34 |   ck_self_closing = ['area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img', 'input', 'link', 'meta', 'param']
 35 | 
 36 |   ck_esc = (txt) ->
 37 |     if ck_options.autoescape then h(txt) else String(txt)
 38 | 
 39 |   ck_tabs = 0
 40 | 
 41 |   ck_repeat = (string, count) -> Array(count + 1).join string
 42 | 
 43 |   ck_indent = -> text ck_repeat('  ', ck_tabs) if ck_options.format
 44 | 
 45 |   h = (txt) ->
 46 |     String(txt).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"')
 47 |     
 48 |   doctype = (type = 5) ->
 49 |     text ck_doctypes[type]
 50 |     text '\n' if ck_options.format
 51 |     
 52 |   text = (txt) ->
 53 |     ck_buffer.push String(txt)
 54 |     null
 55 | 
 56 |   comment = (cmt) ->
 57 |     text ""
 58 |     text '\n' if ck_options.format
 59 |   
 60 |   tag = -> name = arguments[0]; delete arguments[0]; ck_tag(name, arguments)
 61 | 
 62 |   ck_tag = (name, opts) ->
 63 |     ck_indent()
 64 |     text "<#{name}"
 65 |   
 66 |     for o in opts
 67 |       text ck_render_attrs(o) if typeof o is 'object'
 68 |   
 69 |     if name in ck_self_closing
 70 |       text ' />'
 71 |       text '\n' if ck_options.format
 72 |     else
 73 |       text '>'
 74 |   
 75 |       for o in opts
 76 |         switch typeof o
 77 |           when 'string', 'number'
 78 |             text ck_esc(o)
 79 |           when 'function'
 80 |             text '\n' if ck_options.format
 81 |             ck_tabs++
 82 |             result = o.call ck_options.context
 83 |             if typeof result is 'string'
 84 |               ck_indent()
 85 |               text ck_esc(result)
 86 |               text '\n' if ck_options.format
 87 |             ck_tabs--
 88 |             ck_indent()
 89 |       text ""
 90 |       text '\n' if ck_options.format
 91 |   
 92 |     null
 93 |   
 94 |   coffeescript = (code) ->
 95 |     script ";(#{code})();"
 96 | 
 97 |   null
 98 | 
 99 | support = '''
100 |   var __slice = Array.prototype.slice;
101 |   var __hasProp = Object.prototype.hasOwnProperty;
102 |   var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
103 |   var __extends = function(child, parent) {
104 |     for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
105 |     function ctor() { this.constructor = child; }
106 |     ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype;
107 |     return child;
108 |   };
109 |   var __indexOf = Array.prototype.indexOf || function(item) {
110 |     for (var i = 0, l = this.length; i < l; i++) {
111 |       if (this[i] === item) return i;
112 |     }
113 |     return -1;
114 |   };
115 | '''
116 | 
117 | skeleton = String(skeleton).replace(/function\s*\(ck_options\)\s*\{/, '').replace /return null;\s*\}$/, ''
118 | skeleton = support + skeleton
119 | 
120 | tags = 'a|abbr|acronym|address|applet|area|article|aside|audio|b|base|basefont|bdo|big|blockquote|body|br|button|canvas|caption|center|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|dir|div|dl|dt|em|embed|fieldset|figcaption|figure|font|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|img|input|ins|keygen|kbd|label|legend|li|link|map|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|s|samp|script|section|select|small|source|span|strike|strong|style|sub|summary|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|u|ul|video|xmp'.split '|'
121 | 
122 | coffeekup.compile = (template, options = {}) ->
123 |   options.locals ?= {}
124 | 
125 |   # Shim for express.
126 |   if options.locals.body?
127 |     options.context.body = options.locals.body
128 |     delete options.locals.body
129 | 
130 |   if options.body?
131 |     options.context.body = options.body
132 |     delete options.body
133 | 
134 |   
135 |   if typeof template is 'function' then template = String(template)
136 |   else if typeof template is 'string' and coffee?
137 |     template = coffee.compile template, bare: yes
138 |     template = "function(){#{template}}"
139 |   
140 |   tags_here = []
141 |   for t in tags
142 |     if template.indexOf(t) > -1
143 |       tags_here.push t
144 | 
145 |   code = skeleton + "var #{tags_here.join ','};"
146 |   for t in tags_here
147 |     code += "#{t} = function(){return ck_tag('#{t}', arguments)};"
148 |   
149 |   for k, v of options.locals
150 |     if typeof v is 'function' then code += "var #{k} = #{v};"
151 |     else code += "var #{k} = #{JSON.stringify v};"
152 |   
153 |   code += 'with(ck_options.locals){' if options.dynamic_locals
154 |   code += "(#{template}).call(ck_options.context);"
155 |   code += '}' if options.dynamic_locals
156 |   code += "return ck_buffer.join('');"
157 | 
158 |   new Function('ck_options', code)
159 | 
160 | cache = {}
161 | coffeekup.render = (template, options = {}) ->
162 |   options.context ?= {}
163 |   options.locals ?= {}
164 |   options.cache ?= on
165 | 
166 | 
167 |   if options.cache and cache[template]? then tpl = cache[template]
168 |   else if options.cache then tpl = cache[template] = coffeekup.compile(template, options)
169 |   else tpl = coffeekup.compile(template, options)
170 |   tpl(options)
171 | 
172 | unless window?
173 |   coffeekup.adapters =
174 |     simple: (template, data) -> coffeekup.render template, context: data
175 |   coffeekup.adapters.meryl = coffeekup.adapters.simple
176 | 


--------------------------------------------------------------------------------
/src-no-solutions/prelude/coffeekup.coffee:
--------------------------------------------------------------------------------
  1 | if window?
  2 |   coffeekup = window.CoffeeKup = {}
  3 |   coffee = if CoffeeScript? then CoffeeScript else null
  4 | else
  5 |   coffeekup = exports
  6 |   coffee = require 'coffee-script'
  7 | 
  8 | coffeekup.version = '0.2.3'
  9 | 
 10 | skeleton = (ck_options = {}) ->
 11 |   ck_options.context ?= {}
 12 |   ck_options.locals ?= {}
 13 |   ck_options.format ?= off
 14 |   ck_options.autoescape ?= off
 15 |   ck_buffer = []
 16 | 
 17 |   ck_render_attrs = (obj) ->
 18 |     str = ''
 19 |     for k, v of obj
 20 |       str += " #{k}=\"#{ck_esc(v)}\""
 21 |     str
 22 | 
 23 |   ck_doctypes =
 24 |     '5': ''
 25 |     'xml': ''
 26 |     'default': ''
 27 |     'transitional': ''
 28 |     'strict': ''
 29 |     'frameset': ''
 30 |     '1.1': '',
 31 |     'basic': ''
 32 |     'mobile': ''
 33 | 
 34 |   ck_self_closing = ['area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img', 'input', 'link', 'meta', 'param']
 35 | 
 36 |   ck_esc = (txt) ->
 37 |     if ck_options.autoescape then h(txt) else String(txt)
 38 | 
 39 |   ck_tabs = 0
 40 | 
 41 |   ck_repeat = (string, count) -> Array(count + 1).join string
 42 | 
 43 |   ck_indent = -> text ck_repeat('  ', ck_tabs) if ck_options.format
 44 | 
 45 |   h = (txt) ->
 46 |     String(txt).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"')
 47 |     
 48 |   doctype = (type = 5) ->
 49 |     text ck_doctypes[type]
 50 |     text '\n' if ck_options.format
 51 |     
 52 |   text = (txt) ->
 53 |     ck_buffer.push String(txt)
 54 |     null
 55 | 
 56 |   comment = (cmt) ->
 57 |     text ""
 58 |     text '\n' if ck_options.format
 59 |   
 60 |   tag = -> name = arguments[0]; delete arguments[0]; ck_tag(name, arguments)
 61 | 
 62 |   ck_tag = (name, opts) ->
 63 |     ck_indent()
 64 |     text "<#{name}"
 65 |   
 66 |     for o in opts
 67 |       text ck_render_attrs(o) if typeof o is 'object'
 68 |   
 69 |     if name in ck_self_closing
 70 |       text ' />'
 71 |       text '\n' if ck_options.format
 72 |     else
 73 |       text '>'
 74 |   
 75 |       for o in opts
 76 |         switch typeof o
 77 |           when 'string', 'number'
 78 |             text ck_esc(o)
 79 |           when 'function'
 80 |             text '\n' if ck_options.format
 81 |             ck_tabs++
 82 |             result = o.call ck_options.context
 83 |             if typeof result is 'string'
 84 |               ck_indent()
 85 |               text ck_esc(result)
 86 |               text '\n' if ck_options.format
 87 |             ck_tabs--
 88 |             ck_indent()
 89 |       text ""
 90 |       text '\n' if ck_options.format
 91 |   
 92 |     null
 93 |   
 94 |   coffeescript = (code) ->
 95 |     script ";(#{code})();"
 96 | 
 97 |   null
 98 | 
 99 | support = '''
100 |   var __slice = Array.prototype.slice;
101 |   var __hasProp = Object.prototype.hasOwnProperty;
102 |   var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
103 |   var __extends = function(child, parent) {
104 |     for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
105 |     function ctor() { this.constructor = child; }
106 |     ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype;
107 |     return child;
108 |   };
109 |   var __indexOf = Array.prototype.indexOf || function(item) {
110 |     for (var i = 0, l = this.length; i < l; i++) {
111 |       if (this[i] === item) return i;
112 |     }
113 |     return -1;
114 |   };
115 | '''
116 | 
117 | skeleton = String(skeleton).replace(/function\s*\(ck_options\)\s*\{/, '').replace /return null;\s*\}$/, ''
118 | skeleton = support + skeleton
119 | 
120 | tags = 'a|abbr|acronym|address|applet|area|article|aside|audio|b|base|basefont|bdo|big|blockquote|body|br|button|canvas|caption|center|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|dir|div|dl|dt|em|embed|fieldset|figcaption|figure|font|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|img|input|ins|keygen|kbd|label|legend|li|link|map|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|s|samp|script|section|select|small|source|span|strike|strong|style|sub|summary|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|u|ul|video|xmp'.split '|'
121 | 
122 | coffeekup.compile = (template, options = {}) ->
123 |   options.locals ?= {}
124 | 
125 |   # Shim for express.
126 |   if options.locals.body?
127 |     options.context.body = options.locals.body
128 |     delete options.locals.body
129 | 
130 |   if options.body?
131 |     options.context.body = options.body
132 |     delete options.body
133 | 
134 |   
135 |   if typeof template is 'function' then template = String(template)
136 |   else if typeof template is 'string' and coffee?
137 |     template = coffee.compile template, bare: yes
138 |     template = "function(){#{template}}"
139 |   
140 |   tags_here = []
141 |   for t in tags
142 |     if template.indexOf(t) > -1
143 |       tags_here.push t
144 | 
145 |   code = skeleton + "var #{tags_here.join ','};"
146 |   for t in tags_here
147 |     code += "#{t} = function(){return ck_tag('#{t}', arguments)};"
148 |   
149 |   for k, v of options.locals
150 |     if typeof v is 'function' then code += "var #{k} = #{v};"
151 |     else code += "var #{k} = #{JSON.stringify v};"
152 |   
153 |   code += 'with(ck_options.locals){' if options.dynamic_locals
154 |   code += "(#{template}).call(ck_options.context);"
155 |   code += '}' if options.dynamic_locals
156 |   code += "return ck_buffer.join('');"
157 | 
158 |   new Function('ck_options', code)
159 | 
160 | cache = {}
161 | coffeekup.render = (template, options = {}) ->
162 |   options.context ?= {}
163 |   options.locals ?= {}
164 |   options.cache ?= on
165 | 
166 | 
167 |   if options.cache and cache[template]? then tpl = cache[template]
168 |   else if options.cache then tpl = cache[template] = coffeekup.compile(template, options)
169 |   else tpl = coffeekup.compile(template, options)
170 |   tpl(options)
171 | 
172 | unless window?
173 |   coffeekup.adapters =
174 |     simple: (template, data) -> coffeekup.render template, context: data
175 |   coffeekup.adapters.meryl = coffeekup.adapters.simple
176 | 


--------------------------------------------------------------------------------
/src/03-Functions.coffee:
--------------------------------------------------------------------------------
  1 | require './prelude'
  2 | 
  3 | # Pure functions
  4 | show '--- Pure functions ---'
  5 | add = (a, b) -> a + b
  6 | show add 2, 2
  7 | 
  8 | power = (base, exponent) ->
  9 |   result = 1
 10 |   for count in [0...exponent]
 11 |     result *= base
 12 |   result
 13 | show power 2, 10
 14 | 
 15 | # Exercise 7
 16 | show '--- Exercise 7 ---'
 17 | absolute = (number) ->
 18 |   if number < 0
 19 |     -number
 20 |   else
 21 |     number
 22 | show absolute -144
 23 | show '--- End of Exercise ---'
 24 | 
 25 | # Declarative tests
 26 | show '--- Declarative tests ---'
 27 | testAbsolute = (name, property) ->
 28 |   qc.testPure absolute, [qc.arbInt], name, property
 29 | 
 30 | testAbsolute 'returns positive integers',
 31 |   (c, arg, result) -> result >= 0
 32 | 
 33 | testAbsolute 'positive returns positive',
 34 |   (c, arg, result) -> c.guard arg >= 0; result is arg
 35 | 
 36 | testAbsolute 'negative returns positive',
 37 |   (c, arg, result) -> c.guard arg < 0; result is -arg
 38 | 
 39 | qc.testPure power, [qc.arbInt, qc.arbInt],
 40 |   'power == Math.pow for integers',
 41 |   (c, base, exponent, result) ->
 42 |     result == c.note Math.pow base, exponent
 43 | 
 44 | qc.testPure power, [qc.arbWholeNum, qc.arbWholeNum],
 45 |   'power == Math.pow for positive integers',
 46 |   (c, base, exponent, result) ->
 47 |     result == c.note Math.pow base, exponent
 48 | 
 49 | qc.test()
 50 | 
 51 | # Quick hack to avoid garbling of output
 52 | # when async test cases are reported.
 53 | # A better idea is one test suite in one file.
 54 | setTimeout ( ->
 55 | 
 56 |   # Exercise 8
 57 | 
 58 |   intensify = (n) ->
 59 |     2 # This will fail
 60 | 
 61 |   show '--- Exercise 8 ---'
 62 |   intensify = (n) ->
 63 |     if n > 0
 64 |       n + 2
 65 |     else if n < 0
 66 |       n - 2
 67 |     else
 68 |       n
 69 |   show '--- End of Exercise ---'
 70 | 
 71 |   qc.testPure intensify, [qc.arbInt],
 72 |     'intensify grows by 2 when positive',
 73 |     (c, arg, result) ->
 74 |       c.guard arg > 0
 75 |       arg + 2 == result
 76 | 
 77 |   qc.testPure intensify, [qc.arbInt],
 78 |     'intensify grows by 2 when negative',
 79 |     (c, arg, result) ->
 80 |       c.guard arg < 0
 81 |       arg - 2 == result
 82 | 
 83 |   qc.testPure intensify, [qc.arbConst(0)],
 84 |     'only non-zero intensify grows',
 85 |     (c, arg, result) ->
 86 |       result is arg
 87 | 
 88 |   qc.test()
 89 | 
 90 |   setTimeout ( ->
 91 |     
 92 |     # Return revisited
 93 |     show '--- Return revisited ---'
 94 |     yell = (message) ->
 95 |       show message + '!!'
 96 |       return
 97 |     yell 'Yow'
 98 | 
 99 |     # Local scopes
100 |     show '--- Local scopes ---'
101 |     dino = 'I am alive'
102 |     reptile = 'I am A-OK'
103 |     meteor = (reptile) ->
104 |       show reptile            # Argument
105 |       dino = 'I am extinct'
106 |       reptile = 'I survived'
107 |       possum = 'I am new'
108 |     show dino                 # Outer
109 |     meteor 'What happened?'
110 |     show dino                 # Outer changed
111 |     show reptile              # Outer unchanged
112 |     try show possum catch e
113 |       show e.message          # Error undefined
114 | 
115 |     # Local environment
116 |     show '--- Local environment ---'
117 |     variable = 'first'                    # Definition
118 | 
119 |     showVariable = ->
120 |       show 'In showVariable, the variable holds: ' +
121 |             variable                      # second
122 | 
123 |     test = ->
124 |       variable = 'second'                 # Assignment
125 |       show 'In test, the variable holds ' +
126 |            variable + '.'                 # second
127 |       showVariable()
128 | 
129 |     show 'The variable is: ' + variable   # first
130 |     test()
131 |     show 'The variable is: ' + variable   # second
132 | 
133 |     # Siblings and children
134 |     show '--- Siblings and children ---'
135 |     andHere = ->
136 |       try show aLocal                     # Not defined
137 |       catch e then show e.message
138 |     isHere = ->
139 |       aLocal = 'aLocal is defined'
140 |       andHere()
141 |     isHere()
142 | 
143 |     isHere = ->
144 |       andHere = ->
145 |         try show aLocal                   # Is defined
146 |         catch e then show e.message
147 |       aLocal = 'aLocal is defined'
148 |       andHere()
149 |     isHere()
150 | 
151 |     # Internal function variable
152 |     show '--- Internal function variable ---'
153 |     varWhich = 'top-level'
154 |     parentFunction = ->
155 |       varWhich = 'local'
156 |       childFunction = ->
157 |         show varWhich
158 |       childFunction
159 |     child = parentFunction()
160 |     child()
161 | 
162 |     # Synthesized function
163 |     show '--- Synthesized function ---'
164 |     makeAddFunction = (amount) ->
165 |       add = (number) -> number + amount
166 | 
167 |     addTwo = makeAddFunction 2
168 |     addFive = makeAddFunction 5
169 |     show addTwo(1) + addFive(1)
170 | 
171 |     # Recursion
172 |     show '--- Recursion ---'
173 |     powerRec = (base, exponent) ->
174 |       if exponent == 0
175 |         1
176 |       else
177 |         base * powerRec base, exponent - 1
178 |     show 'power 3, 3 = ' + powerRec 3, 3
179 | 
180 |     # Timing
181 |     show '--- Timing ---'
182 |     timeIt = (func) ->
183 |       start = new Date()
184 |       for i in [0...1000000] then func()
185 |       show "Timing: #{(new Date() - start)*0.001}s"
186 | 
187 |     timeIt -> p = add 9,18                # 0.042s
188 |     timeIt -> p = Math.pow 9,18           # 0.049s
189 |     timeIt -> p = power 9,18              # 0.464s
190 |     timeIt -> p = powerRec 9,18           # 0.544s
191 | 
192 |     # Indirect recursion
193 |     show '--- Indirect recursion ---'
194 |     chicken = ->
195 |       show 'Lay an egg'
196 |       egg()
197 |     egg     = ->
198 |       show 'Chick hatched'
199 |       chicken()
200 |     try show chicken() + ' came first.'
201 |     catch error then show error.message
202 | 
203 |     # Recursive puzzle solving
204 |     show '--- Recursive puzzle solving ---'
205 |     findSequence = (goal) ->
206 |       find = (start, history) ->
207 |         if start == goal
208 |           history
209 |         else if start > goal
210 |           null
211 |         else
212 |           find(start + 5, '(' + history + ' + 5)') ? \
213 |           find(start * 3, '(' + history + ' * 3)')
214 |       find 1, '1'
215 |     show findSequence 24
216 | 
217 |     # Anonymous functions
218 |     show '--- Anonymous functions ---'
219 |     makeAddFunction = (amount) ->
220 |       (number) -> number + amount
221 |     show makeAddFunction(11) 3
222 | 
223 |     # Exercise 9
224 |     show '--- Exercise 9 ---'
225 |     greaterThan = (x) ->
226 |       (y) -> y > x
227 |     greaterThanTen = greaterThan 10
228 |     show greaterThanTen 9
229 |     show '--- End of Exercise ---'
230 | 
231 |     # Extra function arguments
232 |     show '--- Extra function arguments ---'
233 |     yell 'Hello', 'Good Evening', 'How do you do?'
234 |     yell()
235 | 
236 |     # Variable number of arguments
237 |     show '--- Variable number of arguments ---'
238 |     console.log 'R', 2, 'D', 2
239 | 
240 |     ),300
241 |   ),300
242 | 
243 | 
244 | 
245 | 
246 | 
247 | 


--------------------------------------------------------------------------------
/src/docs/A3-Microbench.html:
--------------------------------------------------------------------------------
 1 |       A3-Microbench.coffee              


--------------------------------------------------------------------------------
/src-no-solutions/docs/A3-Microbench.html:
--------------------------------------------------------------------------------
 1 |       A3-Microbench.coffee              


--------------------------------------------------------------------------------
/src/prelude/ws.coffee:
--------------------------------------------------------------------------------
  1 | # *WebSocket Draft 75/76 implementation.*
  2 | # Straight port to CoffeeScript.
  3 | # No intentional changes to style or substance.
  4 | 
  5 | # Github: http://github.com/ncr/node.ws.js
  6 | # Compatible with node v0.1.91
  7 | # Author: Jacek Becela
  8 | # Contributors:
  9 | #   Michael Stillwell  http://github.com/ithinkihaveacat
 10 | #   Nick Chapman       http://github.com/nchapman
 11 | #   Dmitriy Shalashov  http://github.com/skaurus
 12 | #   Johan Dahlberg
 13 | #   Andreas Kompanez
 14 | #   Samuel Cyprian		http://github.com/samcyp
 15 | # License: MIT
 16 | # Based on: http://github.com/Guille/node.websocket.js
 17 | 
 18 | nano = (template, data) ->
 19 |   template.replace /\{([\w\.]*)}/g, (str, key) ->
 20 |     keys = key.split "."
 21 |     value = data[keys.shift()]
 22 |     keys.forEach (key) -> value = value[key]
 23 |     value
 24 | 
 25 | pack = (num) ->
 26 |   result = ''
 27 |   result += String.fromCharCode(num >> 24 & 0xFF)
 28 |   result += String.fromCharCode(num >> 16 & 0xFF)
 29 |   result += String.fromCharCode(num >> 8 & 0xFF)
 30 |   result += String.fromCharCode(num & 0xFF)
 31 |   result
 32 | 
 33 | sys    = require "sys"
 34 | net    = require "net"
 35 | crypto = require "crypto"
 36 | requiredHeaders = {
 37 |   'get': /^GET (\/[^\s]*)/,
 38 |   'upgrade': /^WebSocket$/,
 39 |   'connection': /^Upgrade$/,
 40 |   'host': /^(.+)$/,
 41 |   'origin': /^(.+)$/
 42 | }
 43 | handshakeTemplate75 = [
 44 |   'HTTP/1.1 101 Web Socket Protocol Handshake', 
 45 |   'Upgrade: WebSocket', 
 46 |   'Connection: Upgrade',
 47 |   'WebSocket-Origin: {origin}',
 48 |   'WebSocket-Location: {protocol}://{host}{resource}',
 49 |   '',
 50 |   ''
 51 | ].join("\r\n")
 52 | handshakeTemplate76 = [
 53 |   'HTTP/1.1 101 WebSocket Protocol Handshake', # note a diff here
 54 |   'Upgrade: WebSocket',
 55 |   'Connection: Upgrade',
 56 |   'Sec-WebSocket-Origin: {origin}',
 57 |   'Sec-WebSocket-Location: {protocol}://{host}{resource}',
 58 |   '',
 59 |   '{data}'
 60 | ].join("\r\n")
 61 | flashPolicy = ''
 62 | 
 63 | 
 64 | exports.createSecureServer = (websocketListener, credentials, options) ->
 65 | 	if !options then options = {}
 66 | 	options.secure = credentials
 67 | 	@createServer websocketListener, options
 68 | 
 69 | exports.createServer = (websocketListener, options) ->
 70 |   if !options then options = {}
 71 |   if !options.flashPolicy then options.flashPolicy = flashPolicy
 72 |   # The value should be a crypto credentials
 73 |   if !options.secure then options.secure = null
 74 | 
 75 |   net.createServer (socket) ->
 76 |   	#Secure WebSockets
 77 |   	wsProtocol = 'ws'
 78 |   	if options.secure
 79 |   	  wsProtocol = 'wss'
 80 |   	  socket.setSecure options.secure
 81 |     socket.setTimeout 0
 82 |     socket.setNoDelay true
 83 |     socket.setKeepAlive true, 0
 84 | 
 85 |     emitter = new process.EventEmitter()
 86 |     handshaked = false
 87 |     buffer = ""
 88 |       
 89 |     handle = (data) ->
 90 |       buffer += data
 91 |       
 92 |       chunks = buffer.split "\ufffd"
 93 |       count = chunks.length - 1 # last is "" or a partial packet
 94 |         
 95 |       for i in [0...count]
 96 |         chunk = chunks[i]
 97 |         if chunk[0] == "\u0000"
 98 |           emitter.emit "data", chunk.slice 1
 99 |         else
100 |           socket.end()
101 |           return
102 |       
103 |       buffer = chunks[count]
104 | 
105 |     handshake = (data) ->
106 |       _headers = data.split "\r\n"
107 | 
108 |       if //.exec _headers[0] 
109 |         socket.write options.flashPolicy
110 |         socket.end()
111 |         return
112 | 
113 |       # go to more convenient hash form
114 |       headers = {}
115 |       upgradeHead = null
116 |       len = _headers.length
117 |       if _headers[0].match /^GET /
118 |         headers["get"] = _headers[0]
119 |       else
120 |         socket.end()
121 |         return
122 | 
123 |       if _headers[ _headers.length - 1 ]
124 |         upgradeHead = _headers[ _headers.length - 1 ]
125 |         len--
126 | 
127 |       while --len # _headers[0] will be skipped
128 |         header = _headers[len]
129 |         if !header then continue
130 | 
131 |         split = header.split ": ", 2 # second parameter actually seems to not work in node
132 |         headers[ split[0].toLowerCase() ] = split[1]
133 | 
134 |       # check if we have all needed headers and fetch data from them
135 |       data = {}
136 |       match = null
137 |       for header of requiredHeaders
138 |         #           regexp                          actual header value
139 |         if match = requiredHeaders[ header ].exec headers[header]
140 |           data[header] = match
141 |         else
142 |           socket.end()
143 |           return
144 | 
145 |       # draft auto-sensing
146 |       if headers["sec-websocket-key1"] && headers["sec-websocket-key2"] && upgradeHead # 76
147 |         strkey1 = headers["sec-websocket-key1"]
148 |         strkey2 = headers["sec-websocket-key2"]
149 | 
150 |         numkey1 = parseInt strkey1.replace(/[^\d]/g, ""), 10
151 |         numkey2 = parseInt strkey2.replace(/[^\d]/g, ""), 10
152 | 
153 |         spaces1 = strkey1.replace(/[^\ ]/g, "").length
154 |         spaces2 = strkey2.replace(/[^\ ]/g, "").length
155 | 
156 |         if spaces1 == 0 || spaces2 == 0 || numkey1 % spaces1 != 0 || numkey2 % spaces2 != 0
157 |           socket.end()
158 |           return
159 | 
160 |         hash = crypto.createHash "md5"
161 |         key1 = pack parseInt numkey1/spaces1
162 |         key2 = pack parseInt numkey2/spaces2
163 |         
164 |         hash.update key1
165 |         hash.update key2
166 |         hash.update upgradeHead
167 | 
168 |         socket.write(nano(handshakeTemplate76, {
169 |           protocol: wsProtocol,
170 |           resource: data.get[1],
171 |           host:     data.host[1],
172 |           origin:   data.origin[1],
173 |           data:     hash.digest("binary")
174 |         }), "binary")
175 | 
176 |       else # 75
177 |         socket.write(nano(handshakeTemplate75, {
178 |           protocol: wsProtocol,
179 |           resource: data.get[1],
180 |           host:     data.host[1],
181 |           origin:   data.origin[1]
182 |         }))
183 | 
184 |       handshaked = true
185 |       emitter.emit "connect", data.get[1]
186 | 
187 |     socket.addListener("data", (data) ->
188 |       if handshaked
189 |         handle data.toString "utf8"
190 |       else
191 |         handshake data.toString "binary" # because of draft76 handshakes
192 |     ).addListener("end", ->
193 |       socket.end()
194 |     ).addListener("close", ->
195 |       if handshaked # don't emit close from policy-requests
196 |         emitter.emit "close"
197 |     ).addListener("error", (exception) ->
198 |       if emitter.listeners("error").length > 0
199 |         emitter.emit "error", exception
200 |       else
201 |         throw exception
202 |     )
203 | 
204 |     emitter.remoteAddress = socket.remoteAddress
205 |     
206 |     emitter.write = (data) ->
207 |       try
208 |         socket.write '\u0000', 'binary'
209 |         socket.write data, 'utf8'
210 |         socket.write '\uffff', 'binary'
211 |       catch e
212 |         # Socket not open for writing, 
213 |         # should get "close" event just before.
214 |         socket.end()
215 |     
216 |     emitter.end = ->
217 |       socket.end()
218 |     
219 |     # emits: "connect", "data", "close", provides: write(data), end()
220 |     websocketListener emitter
221 | 
222 | 
223 | 


--------------------------------------------------------------------------------
/src-no-solutions/prelude/ws.coffee:
--------------------------------------------------------------------------------
  1 | # *WebSocket Draft 75/76 implementation.*
  2 | # Straight port to CoffeeScript.
  3 | # No intentional changes to style or substance.
  4 | 
  5 | # Github: http://github.com/ncr/node.ws.js
  6 | # Compatible with node v0.1.91
  7 | # Author: Jacek Becela
  8 | # Contributors:
  9 | #   Michael Stillwell  http://github.com/ithinkihaveacat
 10 | #   Nick Chapman       http://github.com/nchapman
 11 | #   Dmitriy Shalashov  http://github.com/skaurus
 12 | #   Johan Dahlberg
 13 | #   Andreas Kompanez
 14 | #   Samuel Cyprian		http://github.com/samcyp
 15 | # License: MIT
 16 | # Based on: http://github.com/Guille/node.websocket.js
 17 | 
 18 | nano = (template, data) ->
 19 |   template.replace /\{([\w\.]*)}/g, (str, key) ->
 20 |     keys = key.split "."
 21 |     value = data[keys.shift()]
 22 |     keys.forEach (key) -> value = value[key]
 23 |     value
 24 | 
 25 | pack = (num) ->
 26 |   result = ''
 27 |   result += String.fromCharCode(num >> 24 & 0xFF)
 28 |   result += String.fromCharCode(num >> 16 & 0xFF)
 29 |   result += String.fromCharCode(num >> 8 & 0xFF)
 30 |   result += String.fromCharCode(num & 0xFF)
 31 |   result
 32 | 
 33 | sys    = require "sys"
 34 | net    = require "net"
 35 | crypto = require "crypto"
 36 | requiredHeaders = {
 37 |   'get': /^GET (\/[^\s]*)/,
 38 |   'upgrade': /^WebSocket$/,
 39 |   'connection': /^Upgrade$/,
 40 |   'host': /^(.+)$/,
 41 |   'origin': /^(.+)$/
 42 | }
 43 | handshakeTemplate75 = [
 44 |   'HTTP/1.1 101 Web Socket Protocol Handshake', 
 45 |   'Upgrade: WebSocket', 
 46 |   'Connection: Upgrade',
 47 |   'WebSocket-Origin: {origin}',
 48 |   'WebSocket-Location: {protocol}://{host}{resource}',
 49 |   '',
 50 |   ''
 51 | ].join("\r\n")
 52 | handshakeTemplate76 = [
 53 |   'HTTP/1.1 101 WebSocket Protocol Handshake', # note a diff here
 54 |   'Upgrade: WebSocket',
 55 |   'Connection: Upgrade',
 56 |   'Sec-WebSocket-Origin: {origin}',
 57 |   'Sec-WebSocket-Location: {protocol}://{host}{resource}',
 58 |   '',
 59 |   '{data}'
 60 | ].join("\r\n")
 61 | flashPolicy = ''
 62 | 
 63 | 
 64 | exports.createSecureServer = (websocketListener, credentials, options) ->
 65 | 	if !options then options = {}
 66 | 	options.secure = credentials
 67 | 	@createServer websocketListener, options
 68 | 
 69 | exports.createServer = (websocketListener, options) ->
 70 |   if !options then options = {}
 71 |   if !options.flashPolicy then options.flashPolicy = flashPolicy
 72 |   # The value should be a crypto credentials
 73 |   if !options.secure then options.secure = null
 74 | 
 75 |   net.createServer (socket) ->
 76 |   	#Secure WebSockets
 77 |   	wsProtocol = 'ws'
 78 |   	if options.secure
 79 |   	  wsProtocol = 'wss'
 80 |   	  socket.setSecure options.secure
 81 |     socket.setTimeout 0
 82 |     socket.setNoDelay true
 83 |     socket.setKeepAlive true, 0
 84 | 
 85 |     emitter = new process.EventEmitter()
 86 |     handshaked = false
 87 |     buffer = ""
 88 |       
 89 |     handle = (data) ->
 90 |       buffer += data
 91 |       
 92 |       chunks = buffer.split "\ufffd"
 93 |       count = chunks.length - 1 # last is "" or a partial packet
 94 |         
 95 |       for i in [0...count]
 96 |         chunk = chunks[i]
 97 |         if chunk[0] == "\u0000"
 98 |           emitter.emit "data", chunk.slice 1
 99 |         else
100 |           socket.end()
101 |           return
102 |       
103 |       buffer = chunks[count]
104 | 
105 |     handshake = (data) ->
106 |       _headers = data.split "\r\n"
107 | 
108 |       if //.exec _headers[0] 
109 |         socket.write options.flashPolicy
110 |         socket.end()
111 |         return
112 | 
113 |       # go to more convenient hash form
114 |       headers = {}
115 |       upgradeHead = null
116 |       len = _headers.length
117 |       if _headers[0].match /^GET /
118 |         headers["get"] = _headers[0]
119 |       else
120 |         socket.end()
121 |         return
122 | 
123 |       if _headers[ _headers.length - 1 ]
124 |         upgradeHead = _headers[ _headers.length - 1 ]
125 |         len--
126 | 
127 |       while --len # _headers[0] will be skipped
128 |         header = _headers[len]
129 |         if !header then continue
130 | 
131 |         split = header.split ": ", 2 # second parameter actually seems to not work in node
132 |         headers[ split[0].toLowerCase() ] = split[1]
133 | 
134 |       # check if we have all needed headers and fetch data from them
135 |       data = {}
136 |       match = null
137 |       for header of requiredHeaders
138 |         #           regexp                          actual header value
139 |         if match = requiredHeaders[ header ].exec headers[header]
140 |           data[header] = match
141 |         else
142 |           socket.end()
143 |           return
144 | 
145 |       # draft auto-sensing
146 |       if headers["sec-websocket-key1"] && headers["sec-websocket-key2"] && upgradeHead # 76
147 |         strkey1 = headers["sec-websocket-key1"]
148 |         strkey2 = headers["sec-websocket-key2"]
149 | 
150 |         numkey1 = parseInt strkey1.replace(/[^\d]/g, ""), 10
151 |         numkey2 = parseInt strkey2.replace(/[^\d]/g, ""), 10
152 | 
153 |         spaces1 = strkey1.replace(/[^\ ]/g, "").length
154 |         spaces2 = strkey2.replace(/[^\ ]/g, "").length
155 | 
156 |         if spaces1 == 0 || spaces2 == 0 || numkey1 % spaces1 != 0 || numkey2 % spaces2 != 0
157 |           socket.end()
158 |           return
159 | 
160 |         hash = crypto.createHash "md5"
161 |         key1 = pack parseInt numkey1/spaces1
162 |         key2 = pack parseInt numkey2/spaces2
163 |         
164 |         hash.update key1
165 |         hash.update key2
166 |         hash.update upgradeHead
167 | 
168 |         socket.write(nano(handshakeTemplate76, {
169 |           protocol: wsProtocol,
170 |           resource: data.get[1],
171 |           host:     data.host[1],
172 |           origin:   data.origin[1],
173 |           data:     hash.digest("binary")
174 |         }), "binary")
175 | 
176 |       else # 75
177 |         socket.write(nano(handshakeTemplate75, {
178 |           protocol: wsProtocol,
179 |           resource: data.get[1],
180 |           host:     data.host[1],
181 |           origin:   data.origin[1]
182 |         }))
183 | 
184 |       handshaked = true
185 |       emitter.emit "connect", data.get[1]
186 | 
187 |     socket.addListener("data", (data) ->
188 |       if handshaked
189 |         handle data.toString "utf8"
190 |       else
191 |         handshake data.toString "binary" # because of draft76 handshakes
192 |     ).addListener("end", ->
193 |       socket.end()
194 |     ).addListener("close", ->
195 |       if handshaked # don't emit close from policy-requests
196 |         emitter.emit "close"
197 |     ).addListener("error", (exception) ->
198 |       if emitter.listeners("error").length > 0
199 |         emitter.emit "error", exception
200 |       else
201 |         throw exception
202 |     )
203 | 
204 |     emitter.remoteAddress = socket.remoteAddress
205 |     
206 |     emitter.write = (data) ->
207 |       try
208 |         socket.write '\u0000', 'binary'
209 |         socket.write data, 'utf8'
210 |         socket.write '\uffff', 'binary'
211 |       catch e
212 |         # Socket not open for writing, 
213 |         # should get "close" event just before.
214 |         socket.end()
215 |     
216 |     emitter.end = ->
217 |       socket.end()
218 |     
219 |     # emits: "connect", "data", "close", provides: write(data), end()
220 |     websocketListener emitter
221 | 
222 | 
223 | 


--------------------------------------------------------------------------------
/src/docs/A3-Microtest.html:
--------------------------------------------------------------------------------
 1 |       A3-Microtest.py              


--------------------------------------------------------------------------------
/src-no-solutions/docs/A3-Microtest.html:
--------------------------------------------------------------------------------
 1 |       A3-Microtest.py              


--------------------------------------------------------------------------------
/src/docs/docco.css:
--------------------------------------------------------------------------------
  1 | /*--------------------- Layout and Typography ----------------------------*/
  2 | body {
  3 |   font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif;
  4 |   font-size: 15px;
  5 |   line-height: 22px;
  6 |   color: #252519;
  7 |   margin: 0; padding: 0;
  8 | }
  9 | a {
 10 |   color: #261a3b;
 11 | }
 12 |   a:visited {
 13 |     color: #261a3b;
 14 |   }
 15 | p {
 16 |   margin: 0 0 15px 0;
 17 | }
 18 | h1, h2, h3, h4, h5, h6 {
 19 |   margin: 0px 0 15px 0;
 20 | }
 21 |   h1 {
 22 |     margin-top: 40px;
 23 |   }
 24 | #container {
 25 |   position: relative;
 26 | }
 27 | #background {
 28 |   position: fixed;
 29 |   top: 0; left: 525px; right: 0; bottom: 0;
 30 |   background: #f5f5ff;
 31 |   border-left: 1px solid #e5e5ee;
 32 |   z-index: -1;
 33 | }
 34 | #jump_to, #jump_page {
 35 |   background: white;
 36 |   -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777;
 37 |   -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px;
 38 |   font: 10px Arial;
 39 |   text-transform: uppercase;
 40 |   cursor: pointer;
 41 |   text-align: right;
 42 | }
 43 | #jump_to, #jump_wrapper {
 44 |   position: fixed;
 45 |   right: 0; top: 0;
 46 |   padding: 5px 10px;
 47 | }
 48 |   #jump_wrapper {
 49 |     padding: 0;
 50 |     display: none;
 51 |   }
 52 |     #jump_to:hover #jump_wrapper {
 53 |       display: block;
 54 |     }
 55 |     #jump_page {
 56 |       padding: 5px 0 3px;
 57 |       margin: 0 0 25px 25px;
 58 |     }
 59 |       #jump_page .source {
 60 |         display: block;
 61 |         padding: 5px 10px;
 62 |         text-decoration: none;
 63 |         border-top: 1px solid #eee;
 64 |       }
 65 |         #jump_page .source:hover {
 66 |           background: #f5f5ff;
 67 |         }
 68 |         #jump_page .source:first-child {
 69 |         }
 70 | table td {
 71 |   border: 0;
 72 |   outline: 0;
 73 | }
 74 |   td.docs, th.docs {
 75 |     max-width: 450px;
 76 |     min-width: 450px;
 77 |     min-height: 5px;
 78 |     padding: 10px 25px 1px 50px;
 79 |     overflow-x: hidden;
 80 |     vertical-align: top;
 81 |     text-align: left;
 82 |   }
 83 |     .docs pre {
 84 |       margin: 15px 0 15px;
 85 |       padding-left: 15px;
 86 |     }
 87 |     .docs p tt, .docs p code {
 88 |       background: #f8f8ff;
 89 |       border: 1px solid #dedede;
 90 |       font-size: 12px;
 91 |       padding: 0 0.2em;
 92 |     }
 93 |     .pilwrap {
 94 |       position: relative;
 95 |     }
 96 |       .pilcrow {
 97 |         font: 12px Arial;
 98 |         text-decoration: none;
 99 |         color: #454545;
100 |         position: absolute;
101 |         top: 3px; left: -20px;
102 |         padding: 1px 2px;
103 |         opacity: 0;
104 |         -webkit-transition: opacity 0.2s linear;
105 |       }
106 |         td.docs:hover .pilcrow {
107 |           opacity: 1;
108 |         }
109 |   td.code, th.code {
110 |     padding: 14px 15px 16px 25px;
111 |     width: 100%;
112 |     vertical-align: top;
113 |     background: #f5f5ff;
114 |     border-left: 1px solid #e5e5ee;
115 |   }
116 |     pre, tt, code {
117 |       font-size: 12px; line-height: 18px;
118 |       font-family: Monaco, Consolas, "Lucida Console", monospace;
119 |       margin: 0; padding: 0;
120 |     }
121 | 
122 | 
123 | /*---------------------- Syntax Highlighting -----------------------------*/
124 | td.linenos { background-color: #f0f0f0; padding-right: 10px; }
125 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; }
126 | body .hll { background-color: #ffffcc }
127 | body .c { color: #408080; font-style: italic }  /* Comment */
128 | body .err { border: 1px solid #FF0000 }         /* Error */
129 | body .k { color: #954121 }                      /* Keyword */
130 | body .o { color: #666666 }                      /* Operator */
131 | body .cm { color: #408080; font-style: italic } /* Comment.Multiline */
132 | body .cp { color: #BC7A00 }                     /* Comment.Preproc */
133 | body .c1 { color: #408080; font-style: italic } /* Comment.Single */
134 | body .cs { color: #408080; font-style: italic } /* Comment.Special */
135 | body .gd { color: #A00000 }                     /* Generic.Deleted */
136 | body .ge { font-style: italic }                 /* Generic.Emph */
137 | body .gr { color: #FF0000 }                     /* Generic.Error */
138 | body .gh { color: #000080; font-weight: bold }  /* Generic.Heading */
139 | body .gi { color: #00A000 }                     /* Generic.Inserted */
140 | body .go { color: #808080 }                     /* Generic.Output */
141 | body .gp { color: #000080; font-weight: bold }  /* Generic.Prompt */
142 | body .gs { font-weight: bold }                  /* Generic.Strong */
143 | body .gu { color: #800080; font-weight: bold }  /* Generic.Subheading */
144 | body .gt { color: #0040D0 }                     /* Generic.Traceback */
145 | body .kc { color: #954121 }                     /* Keyword.Constant */
146 | body .kd { color: #954121; font-weight: bold }  /* Keyword.Declaration */
147 | body .kn { color: #954121; font-weight: bold }  /* Keyword.Namespace */
148 | body .kp { color: #954121 }                     /* Keyword.Pseudo */
149 | body .kr { color: #954121; font-weight: bold }  /* Keyword.Reserved */
150 | body .kt { color: #B00040 }                     /* Keyword.Type */
151 | body .m { color: #666666 }                      /* Literal.Number */
152 | body .s { color: #219161 }                      /* Literal.String */
153 | body .na { color: #7D9029 }                     /* Name.Attribute */
154 | body .nb { color: #954121 }                     /* Name.Builtin */
155 | body .nc { color: #0000FF; font-weight: bold }  /* Name.Class */
156 | body .no { color: #880000 }                     /* Name.Constant */
157 | body .nd { color: #AA22FF }                     /* Name.Decorator */
158 | body .ni { color: #999999; font-weight: bold }  /* Name.Entity */
159 | body .ne { color: #D2413A; font-weight: bold }  /* Name.Exception */
160 | body .nf { color: #0000FF }                     /* Name.Function */
161 | body .nl { color: #A0A000 }                     /* Name.Label */
162 | body .nn { color: #0000FF; font-weight: bold }  /* Name.Namespace */
163 | body .nt { color: #954121; font-weight: bold }  /* Name.Tag */
164 | body .nv { color: #19469D }                     /* Name.Variable */
165 | body .ow { color: #AA22FF; font-weight: bold }  /* Operator.Word */
166 | body .w { color: #bbbbbb }                      /* Text.Whitespace */
167 | body .mf { color: #666666 }                     /* Literal.Number.Float */
168 | body .mh { color: #666666 }                     /* Literal.Number.Hex */
169 | body .mi { color: #666666 }                     /* Literal.Number.Integer */
170 | body .mo { color: #666666 }                     /* Literal.Number.Oct */
171 | body .sb { color: #219161 }                     /* Literal.String.Backtick */
172 | body .sc { color: #219161 }                     /* Literal.String.Char */
173 | body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */
174 | body .s2 { color: #219161 }                     /* Literal.String.Double */
175 | body .se { color: #BB6622; font-weight: bold }  /* Literal.String.Escape */
176 | body .sh { color: #219161 }                     /* Literal.String.Heredoc */
177 | body .si { color: #BB6688; font-weight: bold }  /* Literal.String.Interpol */
178 | body .sx { color: #954121 }                     /* Literal.String.Other */
179 | body .sr { color: #BB6688 }                     /* Literal.String.Regex */
180 | body .s1 { color: #219161 }                     /* Literal.String.Single */
181 | body .ss { color: #19469D }                     /* Literal.String.Symbol */
182 | body .bp { color: #954121 }                     /* Name.Builtin.Pseudo */
183 | body .vc { color: #19469D }                     /* Name.Variable.Class */
184 | body .vg { color: #19469D }                     /* Name.Variable.Global */
185 | body .vi { color: #19469D }                     /* Name.Variable.Instance */
186 | body .il { color: #666666 }                     /* Literal.Number.Integer.Long */


--------------------------------------------------------------------------------
/src-no-solutions/docs/docco.css:
--------------------------------------------------------------------------------
  1 | /*--------------------- Layout and Typography ----------------------------*/
  2 | body {
  3 |   font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif;
  4 |   font-size: 15px;
  5 |   line-height: 22px;
  6 |   color: #252519;
  7 |   margin: 0; padding: 0;
  8 | }
  9 | a {
 10 |   color: #261a3b;
 11 | }
 12 |   a:visited {
 13 |     color: #261a3b;
 14 |   }
 15 | p {
 16 |   margin: 0 0 15px 0;
 17 | }
 18 | h1, h2, h3, h4, h5, h6 {
 19 |   margin: 0px 0 15px 0;
 20 | }
 21 |   h1 {
 22 |     margin-top: 40px;
 23 |   }
 24 | #container {
 25 |   position: relative;
 26 | }
 27 | #background {
 28 |   position: fixed;
 29 |   top: 0; left: 525px; right: 0; bottom: 0;
 30 |   background: #f5f5ff;
 31 |   border-left: 1px solid #e5e5ee;
 32 |   z-index: -1;
 33 | }
 34 | #jump_to, #jump_page {
 35 |   background: white;
 36 |   -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777;
 37 |   -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px;
 38 |   font: 10px Arial;
 39 |   text-transform: uppercase;
 40 |   cursor: pointer;
 41 |   text-align: right;
 42 | }
 43 | #jump_to, #jump_wrapper {
 44 |   position: fixed;
 45 |   right: 0; top: 0;
 46 |   padding: 5px 10px;
 47 | }
 48 |   #jump_wrapper {
 49 |     padding: 0;
 50 |     display: none;
 51 |   }
 52 |     #jump_to:hover #jump_wrapper {
 53 |       display: block;
 54 |     }
 55 |     #jump_page {
 56 |       padding: 5px 0 3px;
 57 |       margin: 0 0 25px 25px;
 58 |     }
 59 |       #jump_page .source {
 60 |         display: block;
 61 |         padding: 5px 10px;
 62 |         text-decoration: none;
 63 |         border-top: 1px solid #eee;
 64 |       }
 65 |         #jump_page .source:hover {
 66 |           background: #f5f5ff;
 67 |         }
 68 |         #jump_page .source:first-child {
 69 |         }
 70 | table td {
 71 |   border: 0;
 72 |   outline: 0;
 73 | }
 74 |   td.docs, th.docs {
 75 |     max-width: 450px;
 76 |     min-width: 450px;
 77 |     min-height: 5px;
 78 |     padding: 10px 25px 1px 50px;
 79 |     overflow-x: hidden;
 80 |     vertical-align: top;
 81 |     text-align: left;
 82 |   }
 83 |     .docs pre {
 84 |       margin: 15px 0 15px;
 85 |       padding-left: 15px;
 86 |     }
 87 |     .docs p tt, .docs p code {
 88 |       background: #f8f8ff;
 89 |       border: 1px solid #dedede;
 90 |       font-size: 12px;
 91 |       padding: 0 0.2em;
 92 |     }
 93 |     .pilwrap {
 94 |       position: relative;
 95 |     }
 96 |       .pilcrow {
 97 |         font: 12px Arial;
 98 |         text-decoration: none;
 99 |         color: #454545;
100 |         position: absolute;
101 |         top: 3px; left: -20px;
102 |         padding: 1px 2px;
103 |         opacity: 0;
104 |         -webkit-transition: opacity 0.2s linear;
105 |       }
106 |         td.docs:hover .pilcrow {
107 |           opacity: 1;
108 |         }
109 |   td.code, th.code {
110 |     padding: 14px 15px 16px 25px;
111 |     width: 100%;
112 |     vertical-align: top;
113 |     background: #f5f5ff;
114 |     border-left: 1px solid #e5e5ee;
115 |   }
116 |     pre, tt, code {
117 |       font-size: 12px; line-height: 18px;
118 |       font-family: Monaco, Consolas, "Lucida Console", monospace;
119 |       margin: 0; padding: 0;
120 |     }
121 | 
122 | 
123 | /*---------------------- Syntax Highlighting -----------------------------*/
124 | td.linenos { background-color: #f0f0f0; padding-right: 10px; }
125 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; }
126 | body .hll { background-color: #ffffcc }
127 | body .c { color: #408080; font-style: italic }  /* Comment */
128 | body .err { border: 1px solid #FF0000 }         /* Error */
129 | body .k { color: #954121 }                      /* Keyword */
130 | body .o { color: #666666 }                      /* Operator */
131 | body .cm { color: #408080; font-style: italic } /* Comment.Multiline */
132 | body .cp { color: #BC7A00 }                     /* Comment.Preproc */
133 | body .c1 { color: #408080; font-style: italic } /* Comment.Single */
134 | body .cs { color: #408080; font-style: italic } /* Comment.Special */
135 | body .gd { color: #A00000 }                     /* Generic.Deleted */
136 | body .ge { font-style: italic }                 /* Generic.Emph */
137 | body .gr { color: #FF0000 }                     /* Generic.Error */
138 | body .gh { color: #000080; font-weight: bold }  /* Generic.Heading */
139 | body .gi { color: #00A000 }                     /* Generic.Inserted */
140 | body .go { color: #808080 }                     /* Generic.Output */
141 | body .gp { color: #000080; font-weight: bold }  /* Generic.Prompt */
142 | body .gs { font-weight: bold }                  /* Generic.Strong */
143 | body .gu { color: #800080; font-weight: bold }  /* Generic.Subheading */
144 | body .gt { color: #0040D0 }                     /* Generic.Traceback */
145 | body .kc { color: #954121 }                     /* Keyword.Constant */
146 | body .kd { color: #954121; font-weight: bold }  /* Keyword.Declaration */
147 | body .kn { color: #954121; font-weight: bold }  /* Keyword.Namespace */
148 | body .kp { color: #954121 }                     /* Keyword.Pseudo */
149 | body .kr { color: #954121; font-weight: bold }  /* Keyword.Reserved */
150 | body .kt { color: #B00040 }                     /* Keyword.Type */
151 | body .m { color: #666666 }                      /* Literal.Number */
152 | body .s { color: #219161 }                      /* Literal.String */
153 | body .na { color: #7D9029 }                     /* Name.Attribute */
154 | body .nb { color: #954121 }                     /* Name.Builtin */
155 | body .nc { color: #0000FF; font-weight: bold }  /* Name.Class */
156 | body .no { color: #880000 }                     /* Name.Constant */
157 | body .nd { color: #AA22FF }                     /* Name.Decorator */
158 | body .ni { color: #999999; font-weight: bold }  /* Name.Entity */
159 | body .ne { color: #D2413A; font-weight: bold }  /* Name.Exception */
160 | body .nf { color: #0000FF }                     /* Name.Function */
161 | body .nl { color: #A0A000 }                     /* Name.Label */
162 | body .nn { color: #0000FF; font-weight: bold }  /* Name.Namespace */
163 | body .nt { color: #954121; font-weight: bold }  /* Name.Tag */
164 | body .nv { color: #19469D }                     /* Name.Variable */
165 | body .ow { color: #AA22FF; font-weight: bold }  /* Operator.Word */
166 | body .w { color: #bbbbbb }                      /* Text.Whitespace */
167 | body .mf { color: #666666 }                     /* Literal.Number.Float */
168 | body .mh { color: #666666 }                     /* Literal.Number.Hex */
169 | body .mi { color: #666666 }                     /* Literal.Number.Integer */
170 | body .mo { color: #666666 }                     /* Literal.Number.Oct */
171 | body .sb { color: #219161 }                     /* Literal.String.Backtick */
172 | body .sc { color: #219161 }                     /* Literal.String.Char */
173 | body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */
174 | body .s2 { color: #219161 }                     /* Literal.String.Double */
175 | body .se { color: #BB6622; font-weight: bold }  /* Literal.String.Escape */
176 | body .sh { color: #219161 }                     /* Literal.String.Heredoc */
177 | body .si { color: #BB6688; font-weight: bold }  /* Literal.String.Interpol */
178 | body .sx { color: #954121 }                     /* Literal.String.Other */
179 | body .sr { color: #BB6688 }                     /* Literal.String.Regex */
180 | body .s1 { color: #219161 }                     /* Literal.String.Single */
181 | body .ss { color: #19469D }                     /* Literal.String.Symbol */
182 | body .bp { color: #954121 }                     /* Name.Builtin.Pseudo */
183 | body .vc { color: #19469D }                     /* Name.Variable.Class */
184 | body .vg { color: #19469D }                     /* Name.Variable.Global */
185 | body .vi { color: #19469D }                     /* Name.Variable.Instance */
186 | body .il { color: #666666 }                     /* Literal.Number.Integer.Long */


--------------------------------------------------------------------------------
/src/08-ObjectOrientation.coffee:
--------------------------------------------------------------------------------
  1 | require './prelude'
  2 | # Bring underscore into global
  3 | globalize _
  4 | 
  5 | # Attach function
  6 | show '--- Attach function ---'
  7 | rabbit = {}
  8 | rabbit.speak = (line) ->
  9 |   show "The rabbit says '#{line}'"
 10 | rabbit.speak "Well, now you're asking me."
 11 | 
 12 | # Object programming
 13 | show '--- Object programming ---'
 14 | speak = (line) ->
 15 |   show "The #{this.adjective} rabbit says '#{line}'"
 16 | 
 17 | whiteRabbit = adjective: "white", speak: speak
 18 | fatRabbit = adjective: "fat", speak: speak
 19 | 
 20 | whiteRabbit.speak "Oh my ears and whiskers, " +
 21 |                   "how late it's getting!"
 22 | fatRabbit.speak "I could sure use a carrot right now."
 23 | 
 24 | speak.apply fatRabbit, ['Yum.']
 25 | speak.call fatRabbit, 'Burp.'
 26 | 
 27 | # Class concept
 28 | show '--- Class concept ---'
 29 | class Rabbit
 30 |   constructor: (@adjective) ->
 31 |   speak: (line) ->
 32 |     show "The #{@adjective} rabbit says '#{line}'"
 33 | 
 34 | whiteRabbit = new Rabbit "white"
 35 | fatRabbit = new Rabbit "fat"
 36 | 
 37 | whiteRabbit.speak "Hurry!"
 38 | fatRabbit.speak "Tasty!"
 39 | 
 40 | # New constructor
 41 | show '--- New constructor ---'
 42 | killerRabbit = new Rabbit 'killer'
 43 | killerRabbit.speak 'GRAAAAAAAAAH!'
 44 | show killerRabbit
 45 | 
 46 | makeRabbit = (adjective) ->
 47 |   adjective: adjective
 48 |   speak: (line) -> show adjective + ': ' + line
 49 | blackRabbit = makeRabbit 'black'
 50 | 
 51 | show killerRabbit.constructor.name
 52 | show blackRabbit.constructor.name
 53 | 
 54 | # Extends keyword
 55 | show '--- Extends keyword ---'
 56 | class WeightyRabbit extends Rabbit
 57 |   constructor: (adjective, @weight) ->
 58 |     super adjective
 59 |   adjustedWeight: (relativeGravity) ->
 60 |     (@weight * relativeGravity).toPrecision 2
 61 | 
 62 | tinyRabbit = new WeightyRabbit "tiny", 1.01
 63 | jumboRabbit = new WeightyRabbit "jumbo", 7.47
 64 | 
 65 | moonGravity = 1/6
 66 | jumboRabbit.speak "Carry me, I weigh #{jumboRabbit.adjustedWeight(moonGravity)} stones"
 67 | tinyRabbit.speak "He ain't heavy, he is my brother"
 68 | 
 69 | 
 70 | # Inheritance pitfalls
 71 | show '--- Inheritance pitfalls ---'
 72 | class Account
 73 |   constructor: -> @balance = 0
 74 |   transfer: (amount) -> @balance += amount
 75 |   getBalance: -> @balance
 76 |   batchTransfer: (amtList) ->
 77 |     for amount in amtList
 78 |       @transfer amount
 79 | 
 80 | yourAccount = new Account()
 81 | oldBalance = yourAccount.getBalance()
 82 | yourAccount.transfer salary = 1000
 83 | newBalance = yourAccount.getBalance()
 84 | show "Books balance: #{salary == newBalance - oldBalance}."
 85 | 
 86 | # Violation of substitution property
 87 | show '--- Violation of substitution property ---'
 88 | class AccountWithFee extends Account
 89 |   fee: 5
 90 |   transfer: (amount) ->
 91 |     super amount - @fee
 92 |     # feeAccount.transfer @fee
 93 | 
 94 | yourAccount = new AccountWithFee()
 95 | oldBalance = yourAccount.getBalance()
 96 | yourAccount.transfer salary = 1000
 97 | newBalance = yourAccount.getBalance()
 98 | show "Books balance: #{salary == newBalance - oldBalance}."
 99 | 
100 | # Limited account class
101 | show '--- Limited account class ---'
102 | class LimitedAccount extends Account
103 |   constructor: -> super; @resetLimit()
104 |   resetLimit: -> @dailyLimit = 50
105 |   transfer: (amount) ->
106 |     if amount < 0 and (@dailyLimit += amount) < 0
107 |       throw new Error "You maxed out!"
108 |     else
109 |       super amount
110 | lacc = new LimitedAccount()
111 | lacc.transfer 50
112 | show "Start balance #{lacc.getBalance()}"
113 | 
114 | try lacc.batchTransfer [-1..-10]
115 | catch error then show error.message
116 | show "After batch balance #{lacc.getBalance()}"
117 | 
118 | # Fragile base class
119 | show '--- Fragile base class ---'
120 | class Account
121 |   constructor: -> @balance = 0
122 |   transfer: (amount) -> @balance += amount
123 |   getBalance: -> @balance
124 |   batchTransfer: (amtList) ->
125 |     add = (a,b) -> a+b
126 |     sum = (list) -> reduce list, add, 0
127 |     @balance += sum amtList
128 | 
129 | class LimitedAccount extends Account
130 |   constructor: -> super; @resetLimit()
131 |   resetLimit: -> @dailyLimit = 50
132 |   transfer: (amount) ->
133 |     if amount < 0 and (@dailyLimit += amount) < 0
134 |       throw new Error "You maxed out!"
135 |     else
136 |       super amount
137 | 
138 | lacc = new LimitedAccount()
139 | lacc.transfer 50
140 | show "Starting with #{lacc.getBalance()}"
141 | 
142 | try lacc.batchTransfer [-1..-10]
143 | catch error then show error.message
144 | show "After batch balance #{lacc.getBalance()}"
145 | 
146 | # Prototypes
147 | show '--- Prototypes ---'
148 | simpleObject = {}
149 | show simpleObject.constructor.name
150 | show simpleObject.toString()
151 | 
152 | show Rabbit.prototype
153 | show Rabbit.prototype.constructor.name
154 | Rabbit.prototype.speak 'I am generic'
155 | Rabbit::speak 'I am not initialized'
156 | 
157 | show killerRabbit.toString == simpleObject.toString
158 | 
159 | # Shared properties
160 | show '--- Shared properties ---'
161 | Rabbit::teeth = 'small'
162 | show killerRabbit.teeth
163 | killerRabbit.teeth = 'long, sharp, and bloody'
164 | show killerRabbit.teeth
165 | show Rabbit::teeth
166 | 
167 | Rabbit::dance = ->
168 |   show "The #{@adjective} rabbit dances a jig."
169 | killerRabbit.dance()
170 | 
171 | Rabbit = (adjective) ->
172 |   @adjective = adjective
173 | 
174 | Rabbit::speak = (line) ->
175 |   show "The #{@adjective} rabbit says '#{line}'"
176 | 
177 | hazelRabbit = new Rabbit "hazel"
178 | hazelRabbit.speak "Good Frith!"
179 | 
180 | # Exposed properties
181 | show '--- Exposed properties ---'
182 | noCatsAtAll = {}
183 | if "constructor" of noCatsAtAll
184 |   show "Yes, there is a cat called 'constructor'."
185 | 
186 | Object::allProperties = ->
187 |   for property of this
188 |     property
189 | 
190 | test = x: 10, y: 3
191 | show test.allProperties()
192 | 
193 | Object::ownProperties = ->
194 |   for own property of @
195 |     property
196 | 
197 | test = 'Fat Igor': true, 'Fireball': true
198 | show test.ownProperties()
199 | 
200 | # Function forEachOf
201 | show '--- Function forEachOf ---'
202 | forEachOf = (object, action) ->
203 |   for own property, value of object
204 |     action property, value
205 | 
206 | chimera = head: "lion", body: "goat", tail: "snake"
207 | forEachOf chimera, (name, value) ->
208 |   show "The #{name} of a #{value}."
209 | 
210 | # Object methods
211 | show '--- Object methods ---'
212 | forEachIn = (object, action) ->
213 |   for property of object
214 |     if (Object::hasOwnProperty.call(object, property))
215 |       action property, object[property]
216 | 
217 | test = name: "Mordecai", hasOwnProperty: "Uh-oh"
218 | forEachIn test, (name, value) ->
219 |   show "Property #{name} = #{value}"
220 | 
221 | for own property, value of test
222 |   show "Property #{property} = #{value}"
223 | 
224 | obj = foo: 'bar'
225 | # This test is needed to avoid hidden properties ...
226 | show Object::hasOwnProperty.call(obj, 'foo') and
227 |      Object::propertyIsEnumerable.call(obj, 'foo')
228 | # ... because this returns true ...
229 | show Object::hasOwnProperty.call(obj, '__proto__')
230 | # ... this is required to get false.
231 | show Object::hasOwnProperty.call(obj, '__proto__') and
232 |      Object::propertyIsEnumerable.call(obj, '__proto__')
233 | 
234 | # Dictionary
235 | show '--- Dictionary ---'
236 | class Dictionary
237 |   constructor: (@values = {}) ->
238 | 
239 |   store: (name, value) ->
240 |     @values[name] = value
241 | 
242 |   lookup: (name) ->
243 |     @values[name]
244 | 
245 |   contains: (name) ->
246 |     Object::hasOwnProperty.call(@values, name) and
247 |     Object::propertyIsEnumerable.call(@values, name)
248 | 
249 |   each: (action) ->
250 |     for own property, value of @values
251 |       action property, value
252 | 
253 | colours = new Dictionary
254 |   Grover: 'blue'
255 |   Elmo:   'orange'
256 |   Bert:   'yellow'
257 | 
258 | show colours.contains 'Grover'
259 | colours.each (name, colour) ->
260 |   show name + ' is ' + colour
261 | 
262 | 


--------------------------------------------------------------------------------