├── .eslintignore ├── .github ├── FUNDING.yml └── workflows │ └── test.yml ├── .gitignore ├── JavaScript ├── 7-new.js ├── b-call-apply.js ├── 3-abstraction.js ├── a-iife.js ├── 1-simple.js ├── 6-rest.js ├── 9-this.js ├── c-comments.js ├── 2-scopes.js ├── 4-introspection.js ├── 5-default.js ├── 8-method.js └── d-decompose.js ├── README.md ├── Solutions ├── 3-ip.js ├── 1-random.js ├── 4-methods.js └── 2-key.js ├── Exercises ├── 1-random.js ├── 2-key.js ├── 3-ip.test ├── 2-key.test ├── 4-methods.test ├── 3-ip.js ├── 1-random.test └── 4-methods.js ├── .editorconfig ├── package.json ├── LICENSE ├── Exercises.ru.md └── .eslintrc.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: tshemsedinov 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /JavaScript/7-new.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const sum = new Function('a, b', 'return a + b'); 4 | 5 | console.dir({ 6 | name: sum.name, 7 | length: sum.length, 8 | toString: sum.toString(), 9 | }); 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Functions, lambda-functions, function contexts, closures 2 | 3 | [![Функции, лямбды, контексты, замыкания](https://img.youtube.com/vi/pn5myCmpV2U/0.jpg)](https://www.youtube.com/watch?v=pn5myCmpV2U) 4 | -------------------------------------------------------------------------------- /Solutions/3-ip.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ipToInt = (ip = '127.0.0.1') => { 4 | const fn = (res, item) => (res << 8) + parseInt(item); 5 | return ip.split('.').reduce(fn, 0); 6 | }; 7 | 8 | module.exports = { ipToInt }; 9 | -------------------------------------------------------------------------------- /JavaScript/b-call-apply.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function f1(a, b) { 4 | console.log('f1(' + a + ', ' + b + ')'); 5 | } 6 | 7 | f1(2, 3); 8 | f1.call(null, 2, 3); 9 | 10 | const arr = [2, 3]; 11 | f1(...arr); 12 | f1.apply(null, arr); 13 | -------------------------------------------------------------------------------- /Exercises/1-random.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const random = (min, max) => { 4 | // Generate random Number between from min to max 5 | // Use Math.random() and Math.floor() 6 | // See documentation at MDN 7 | }; 8 | 9 | module.exports = { random }; 10 | -------------------------------------------------------------------------------- /Exercises/2-key.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const generateKey = (length, possible) => { 4 | // Generate string of random characters 5 | // Use Math.random() and Math.floor() 6 | // See documentation at MDN 7 | }; 8 | 9 | module.exports = { generateKey }; 10 | -------------------------------------------------------------------------------- /Solutions/1-random.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const random = (min, max) => { 4 | if (max === undefined) { 5 | max = min; 6 | min = 0; 7 | } 8 | return min + Math.floor(Math.random() * (max - min + 1)); 9 | }; 10 | 11 | module.exports = { random }; 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [{*.js,*.mjs,*.ts,*.json,*.yml}] 11 | indent_size = 2 12 | indent_style = space 13 | -------------------------------------------------------------------------------- /JavaScript/3-abstraction.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const power = Math.pow; 4 | const square = (x) => power(x, 2); 5 | const cube = (x) => power(x, 3); 6 | 7 | console.log(power(10, 2)); 8 | console.log(square(10)); 9 | 10 | console.log(power(10, 3)); 11 | console.log(cube(10)); 12 | -------------------------------------------------------------------------------- /Exercises/3-ip.test: -------------------------------------------------------------------------------- 1 | ({ 2 | name: 'ipToInt', 3 | length: [60, 150], 4 | cases: [ 5 | ['127.0.0.1', 2130706433], 6 | ['10.0.0.1', 167772161], 7 | ['192.168.1.10', -1062731510], 8 | ['165.225.133.150', -1511946858], 9 | ['0.0.0.0', 0], 10 | ['8.8.8.8', 0x08080808], 11 | ] 12 | }) 13 | -------------------------------------------------------------------------------- /JavaScript/a-iife.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Immediately-invoked function expression 4 | 5 | (function f1() { 6 | console.log('f1'); 7 | })(); 8 | 9 | (function() { 10 | console.log('anonymous'); 11 | })(); 12 | 13 | (() => { 14 | console.log('lambda'); 15 | })(); 16 | 17 | { 18 | console.log('block'); 19 | } 20 | -------------------------------------------------------------------------------- /Solutions/4-methods.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const methods = (iface) => { 4 | const names = []; 5 | for (const name in iface) { 6 | const fn = iface[name]; 7 | if (typeof fn === 'function') { 8 | names.push([name, fn.length]); 9 | } 10 | } 11 | return names; 12 | }; 13 | 14 | module.exports = { methods }; 15 | -------------------------------------------------------------------------------- /Solutions/2-key.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const generateKey = (length, possible) => { 4 | const base = possible.length; 5 | let key = ''; 6 | for (let i = 0; i < length; i++) { 7 | const index = Math.floor(Math.random() * base); 8 | key += possible[index]; 9 | } 10 | return key; 11 | }; 12 | 13 | module.exports = { generateKey }; 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "private": true, 4 | "version": "1.1.0", 5 | "author": "Timur Shemsedinov ", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "eslint ./Exercises; hpw", 9 | "ci": "eslint ./Exercises && hpw" 10 | }, 11 | "dependencies": { 12 | "eslint": "^8.57.1", 13 | "hpw": "^0.2.4" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Exercises/2-key.test: -------------------------------------------------------------------------------- 1 | ({ 2 | name: 'generateKey', 3 | length: [200, 210], 4 | test: generateKey => { 5 | const characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; 6 | const key = generateKey(16, characters); 7 | if (typeof key !== 'string') throw new Error('Expected string result'); 8 | if (key.length !== 16) throw new Error('Expected 16 characters in result'); 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /Exercises/4-methods.test: -------------------------------------------------------------------------------- 1 | ({ 2 | name: 'methods', 3 | length: [180, 220], 4 | cases: [ 5 | [ 6 | { 7 | m1: x => [x], 8 | m2: function (x, y) { 9 | return [x, y]; 10 | }, 11 | m3(x, y, z) { 12 | return [x, y, z]; 13 | } 14 | }, [ 15 | ['m1', 1], 16 | ['m2', 2], 17 | ['m3', 3] 18 | ] 19 | ], 20 | ] 21 | }) 22 | -------------------------------------------------------------------------------- /Exercises/3-ip.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ipToInt = (ip = '127.0.0.1') => { 4 | // Parse ip address as string, for example '10.0.0.1' 5 | // to ['10', '0', '0', '1'] to [10, 0, 0, 1] 6 | // and convert to Number value 167772161 with bitwise shift 7 | // (10 << 8 << 8 << 8) + (0 << 8 << 8) + (0 << 8) + 1 === 167772161 8 | // Use Array.prototype.reduce of for loop 9 | }; 10 | 11 | module.exports = { ipToInt }; 12 | -------------------------------------------------------------------------------- /JavaScript/1-simple.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function inc(a) { 4 | return a + 1; 5 | } 6 | 7 | const sum = function(a, b) { 8 | return a + b; 9 | }; 10 | 11 | const max = (a, b) => (a > b ? a : b); 12 | 13 | const avg = (a, b) => { 14 | const s = sum(a, b); 15 | return s / 2; 16 | }; 17 | 18 | console.log('inc(5) = ' + inc(5)); 19 | console.log('sum(1, 3) = ' + sum(1, 3)); 20 | console.log('max(8, 6) = ' + max(8, 6)); 21 | console.log('avg(8, 6) = ' + avg(8, 6)); 22 | -------------------------------------------------------------------------------- /JavaScript/6-rest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const f1 = (...args) => { 4 | console.log(args); 5 | }; 6 | 7 | f1(1, 2, 3); 8 | 9 | const f2 = (...args) => { 10 | args.forEach((arg) => { 11 | const type = typeof arg; 12 | console.log('Type: ' + type); 13 | if (type === 'object') { 14 | console.log('Value: ' + JSON.stringify(arg)); 15 | } else { 16 | console.log('Value: ' + arg); 17 | } 18 | }); 19 | }; 20 | 21 | f2(1, 'Marcus', { field: 'value' }); 22 | -------------------------------------------------------------------------------- /Exercises/1-random.test: -------------------------------------------------------------------------------- 1 | ({ 2 | name: 'random', 3 | length: [120, 150], 4 | test: random => { 5 | const x = random(0, 10); 6 | if (typeof x !== 'number') throw new Error('Expected number result'); 7 | if (x < 0 || x > 10) { 8 | throw new Error('Expected random number from 0 to 10 inclusive'); 9 | } 10 | 11 | const y = random(0, 0); 12 | if (y !== 0) throw new Error('Expected 0'); 13 | 14 | const z = random(10, 10); 15 | if (z !== 10) throw new Error('Expected 10'); 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /Exercises/4-methods.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const methods = iface => { 4 | // Introspect all properties of iface object and 5 | // extract function names and number of arguments 6 | // For example: { 7 | // m1: (x) => [x], 8 | // m2: function (x, y) { 9 | // return [x, y]; 10 | // }, 11 | // m3(x, y, z) { 12 | // return [x, y, z]; 13 | // } 14 | // will return: [ 15 | // ['m1', 1], 16 | // ['m2', 2], 17 | // ['m3', 3] 18 | // ] 19 | }; 20 | 21 | module.exports = { methods }; 22 | -------------------------------------------------------------------------------- /JavaScript/9-this.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Context = function() { 4 | this.name = 'Marcus'; 5 | const city = { 6 | name: 'Kiev', 7 | year: 482, 8 | f1: function() { 9 | return this.name; 10 | }, 11 | f2: () => { 12 | return this.name; 13 | }, 14 | f3() { 15 | return this.name; 16 | } 17 | }; 18 | return city; 19 | }; 20 | 21 | const city = new Context(); 22 | 23 | console.log('city.f1() = ' + city.f1()); 24 | console.log('city.f2() = ' + city.f2()); 25 | console.log('city.f3() = ' + city.f3()); 26 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Check labs 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Node.js 11 | uses: actions/setup-node@v1 12 | with: 13 | node-version: 18 14 | - uses: actions/cache@v2 15 | with: 16 | path: ~/.npm 17 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 18 | restore-keys: | 19 | ${{ runner.os }}-node- 20 | - run: npm ci 21 | - run: npm t 22 | -------------------------------------------------------------------------------- /JavaScript/c-comments.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('node:fs'); 4 | 5 | // Print specified line from a file 6 | // fileName - string, file to parse 7 | // lineNumber - number, line starting from 1 8 | // Returns: boolean, success status 9 | const printLine = (fileName, lineNumber) => { 10 | const content = fs.readFileSync(fileName).toString(); 11 | const lines = content.split('\n'); 12 | const line = lines[lineNumber - 1]; 13 | if (line === undefined) return false; 14 | console.dir({ 15 | fileName, lineNumber, line 16 | }); 17 | }; 18 | 19 | printLine('./c-comments.js', 5); 20 | -------------------------------------------------------------------------------- /JavaScript/2-scopes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const cities = ['Athens', 'Roma', 'London', 'Beijing', 'Kiev', 'Riga']; 4 | const f = (s) => s.length; 5 | 6 | function f1() { 7 | const cities = ['Athens', 'Roma']; 8 | const f = (s) => s.toUpperCase(); 9 | console.dir({ cities }); 10 | console.dir(cities.map(f)); 11 | 12 | { 13 | const f = (s) => s.toLowerCase(); 14 | console.dir({ cities }); 15 | console.dir(cities.map(f)); 16 | } 17 | 18 | { 19 | const cities = ['London', 'Beijing', 'Kiev']; 20 | console.dir({ cities }); 21 | console.dir(cities.map(f)); 22 | } 23 | 24 | } 25 | 26 | f1(); 27 | 28 | console.dir({ cities }); 29 | console.dir(cities.map(f)); 30 | -------------------------------------------------------------------------------- /JavaScript/4-introspection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function inc(a) { 4 | return ++a; 5 | } 6 | 7 | const sum = function(a, b) { 8 | return a + b; 9 | }; 10 | 11 | const max = (a, b) => (a > b ? a : b); 12 | 13 | console.log('Names: '); 14 | console.dir({ 15 | inc: inc.name, 16 | sum: sum.name, 17 | max: max.name, 18 | }); 19 | 20 | console.log('Arguments: '); 21 | console.dir({ 22 | inc: inc.length, 23 | sum: sum.length, 24 | max: max.length, 25 | }); 26 | 27 | console.log('Anonymous function: ' + function(x) { return x; }.name); 28 | console.log('Anonymous lambda: ' + ((x) => x).name); 29 | 30 | console.log('toString: '); 31 | console.dir({ 32 | inc: inc.toString(), 33 | sum: sum.toString(), 34 | max: max.toString(), 35 | }); 36 | -------------------------------------------------------------------------------- /JavaScript/5-default.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // ES6 style for default parameter values 4 | // 5 | function fnNew(a, b = 'Hello', c = 5) { 6 | console.dir({ a, b, c }); 7 | } 8 | 9 | fnNew(1, 2, 3); 10 | fnNew(1, 2); 11 | fnNew(1); 12 | fnNew(); 13 | 14 | // Old style for default parameter values 15 | // 16 | function fnOld(a, b, c) { 17 | b = b || 'Hello'; 18 | c = c || 5; 19 | console.dir({ a, b, c }); 20 | } 21 | 22 | fnOld(1, 2, 3); 23 | fnOld(1, 2); 24 | fnOld(1); 25 | fnOld(); 26 | 27 | // Hash style for default parameter values 28 | // 29 | function fnHash(args) { 30 | args.a = args.a || [7, 25, 10]; 31 | args.b = args.b || 'Hello'; 32 | args.c = args.c || 100; 33 | console.dir(args); 34 | } 35 | 36 | fnHash({ a: [1, 2, 3], b: 'Hi', c: 3 }); 37 | fnHash({ b: 'World' }); 38 | fnHash({ c: 7 }); 39 | -------------------------------------------------------------------------------- /JavaScript/8-method.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const powName = 'pow'; 4 | 5 | const obj1 = { 6 | fn1: function inc(a) { 7 | return ++a; 8 | }, 9 | sum: function(a, b) { 10 | return a + b; 11 | }, 12 | inc(a) { 13 | return ++a; 14 | }, 15 | max: (a, b) => { 16 | return a > b ? a : b; 17 | }, 18 | min: (a, b) => (a < b ? a : b), 19 | dec: a => --a, 20 | [powName](a, b) { 21 | return Math.pow(a, b); 22 | } 23 | }; 24 | 25 | console.log('obj1.fn1.name = ' + obj1.fn1.name); 26 | console.log('obj1.sum.name = ' + obj1.sum.name); 27 | console.log('obj1.inc.name = ' + obj1.inc.name); 28 | console.log('obj1.max.name = ' + obj1.max.name); 29 | console.log('obj1.min.name = ' + obj1.min.name); 30 | console.log('obj1.dec.name = ' + obj1.dec.name); 31 | 32 | console.log('obj1.fn1(5) = ' + obj1.fn1(5)); 33 | console.log('obj1.sum(1, 3) = ' + obj1.sum(1, 3)); 34 | console.log('obj1.max(8, 6) = ' + obj1.max(8, 6)); 35 | 36 | console.log('obj1.pow(8, 6) = ' + obj1.pow(8, 6)); 37 | console.log('obj1[\'pow\'](8, 6) = ' + obj1['pow'](8, 6)); 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2024 How.Programming.Works contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Exercises.ru.md: -------------------------------------------------------------------------------- 1 | # Упражнения: Функции 2 | 3 | 1. Реализуйте функцию `random(min, max)`, возвращающую псевдо-случайное 4 | значение от `min` до `max`. Используйте `Math.random()` и `Math.floor()`. 5 | При вызове `random(max)` нужно считать, что `min = 0`. 6 | 2. Реализуйте функцию `generateKey(length, characters)`, возвращающую строку 7 | случайных символов из набора `characters` длиной `length`. Например: 8 | ```js 9 | const characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; 10 | const key = generateKey(16, characters); 11 | console.log(key); // eg599gb60q926j8i 12 | ``` 13 | 3. Преобразуйте IP адрес (IPv4) в число, содержащее 4 байта адреса, 14 | сдвинутые битовым сдвигом, по такой схеме: 15 | - 1 байт сдвинут 3 раза на 8 бит влево 16 | - 2 байт сдвинут 2 раза на 8 бит влево 17 | - 3 байт сдвинут 1 раз на 8 бит влево 18 | - 4 байт не сдвинут 19 | Например: '10.0.0.1': 20 | - Преобразовываем строку `'10.0.0.1'` в массив `['10', '0', '0', '1']` 21 | - Преобразовываем массив `['10', '0', '0', '1']` в массив `[10, 0, 0, 1]` 22 | - Сдвигаем все элементы по приведенной схеме при помощи цикла: 23 | `[10 << 8 << 8 << 8, 0 << 8 << 8, 0 << 8, 1]` и получаем `[167772160, 0, 0, 1]` 24 | - Суммируем все элементы и получаем `167772161` 25 | - Оптимизируем код через использование `Array.prototype.reduce` 26 | Используйте значение аргумента функции по умолчанию '127.0.0.1'. 27 | 28 | Еще примеры: 29 | ``` 30 | 127.0.0.1 -> 2130706433 31 | 10.0.0.1 -> 167772161 32 | 192.168.1.10 -> -1062731510 33 | 165.225.133.150 -> -1511946858 34 | 0.0.0.0 -> 0 35 | 8.8.8.8 -> 0x08080808 36 | ``` 37 | 4. Реализуйте интроспекцию объекта: 38 | - Проитерируйте все ключи объекта `iface` 39 | - Возьмите ключи функционального типа 40 | - Для каждой функции возьмите количество аргументов 41 | - Сохраните результаты в двумерный массив 42 | 43 | Например, из: 44 | ```js 45 | { 46 | m1: x => [x], 47 | m2: function (x, y) { 48 | return [x, y]; 49 | }, 50 | m3(x, y, z) { 51 | return [x, y, z]; 52 | } 53 | } 54 | ``` 55 | нужно получить: 56 | ```js 57 | [ 58 | ['m1', 1], 59 | ['m2', 2], 60 | ['m3', 3] 61 | ] 62 | ``` 63 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "ecmaVersion": "latest" 10 | }, 11 | "globals": { 12 | "BigInt": true 13 | }, 14 | "rules": { 15 | "indent": [ 16 | "error", 17 | 2 18 | ], 19 | "linebreak-style": [ 20 | "error", 21 | "unix" 22 | ], 23 | "quotes": [ 24 | "error", 25 | "single" 26 | ], 27 | "semi": [ 28 | "error", 29 | "always" 30 | ], 31 | "no-loop-func": [ 32 | "error" 33 | ], 34 | "block-spacing": [ 35 | "error", 36 | "always" 37 | ], 38 | "camelcase": [ 39 | "error" 40 | ], 41 | "eqeqeq": [ 42 | "error", 43 | "always" 44 | ], 45 | "strict": [ 46 | "error", 47 | "global" 48 | ], 49 | "brace-style": [ 50 | "error", 51 | "1tbs", 52 | { 53 | "allowSingleLine": true 54 | } 55 | ], 56 | "comma-style": [ 57 | "error", 58 | "last" 59 | ], 60 | "comma-spacing": [ 61 | "error", 62 | { 63 | "before": false, 64 | "after": true 65 | } 66 | ], 67 | "eol-last": [ 68 | "error" 69 | ], 70 | "func-call-spacing": [ 71 | "error", 72 | "never" 73 | ], 74 | "key-spacing": [ 75 | "error", 76 | { 77 | "beforeColon": false, 78 | "afterColon": true, 79 | "mode": "minimum" 80 | } 81 | ], 82 | "keyword-spacing": [ 83 | "error", 84 | { 85 | "before": true, 86 | "after": true, 87 | "overrides": { 88 | "function": { 89 | "after": false 90 | } 91 | } 92 | } 93 | ], 94 | "max-len": [ 95 | "error", 96 | { 97 | "code": 80, 98 | "ignoreUrls": true 99 | } 100 | ], 101 | "max-nested-callbacks": [ 102 | "error", 103 | { 104 | "max": 7 105 | } 106 | ], 107 | "new-cap": [ 108 | "error", 109 | { 110 | "newIsCap": true, 111 | "capIsNew": false, 112 | "properties": true 113 | } 114 | ], 115 | "new-parens": [ 116 | "error" 117 | ], 118 | "no-lonely-if": [ 119 | "error" 120 | ], 121 | "no-trailing-spaces": [ 122 | "error" 123 | ], 124 | "no-unneeded-ternary": [ 125 | "error" 126 | ], 127 | "no-whitespace-before-property": [ 128 | "error" 129 | ], 130 | "object-curly-spacing": [ 131 | "error", 132 | "always" 133 | ], 134 | "operator-assignment": [ 135 | "error", 136 | "always" 137 | ], 138 | "operator-linebreak": [ 139 | "error", 140 | "after" 141 | ], 142 | "semi-spacing": [ 143 | "error", 144 | { 145 | "before": false, 146 | "after": true 147 | } 148 | ], 149 | "space-before-blocks": [ 150 | "error", 151 | "always" 152 | ], 153 | "space-before-function-paren": [ 154 | "error", 155 | { 156 | "anonymous": "never", 157 | "named": "never", 158 | "asyncArrow": "always" 159 | } 160 | ], 161 | "space-in-parens": [ 162 | "error", 163 | "never" 164 | ], 165 | "space-infix-ops": [ 166 | "error" 167 | ], 168 | "space-unary-ops": [ 169 | "error", 170 | { 171 | "words": true, 172 | "nonwords": false, 173 | "overrides": { 174 | "typeof": false 175 | } 176 | } 177 | ], 178 | "no-unreachable": [ 179 | "error" 180 | ], 181 | "no-global-assign": [ 182 | "error" 183 | ], 184 | "no-self-compare": [ 185 | "error" 186 | ], 187 | "no-unmodified-loop-condition": [ 188 | "error" 189 | ], 190 | "no-constant-condition": [ 191 | "error", 192 | { 193 | "checkLoops": false 194 | } 195 | ], 196 | "no-console": [ 197 | "off" 198 | ], 199 | "no-useless-concat": [ 200 | "error" 201 | ], 202 | "no-useless-escape": [ 203 | "error" 204 | ], 205 | "no-shadow-restricted-names": [ 206 | "error" 207 | ], 208 | "no-use-before-define": [ 209 | "error", 210 | { 211 | "functions": false 212 | } 213 | ], 214 | "arrow-parens": [ 215 | "error", 216 | "always" 217 | ], 218 | "arrow-body-style": [ 219 | "error", 220 | "as-needed" 221 | ], 222 | "arrow-spacing": [ 223 | "error" 224 | ], 225 | "no-confusing-arrow": [ 226 | "error", 227 | { 228 | "allowParens": true 229 | } 230 | ], 231 | "no-useless-computed-key": [ 232 | "error" 233 | ], 234 | "no-useless-rename": [ 235 | "error" 236 | ], 237 | "no-var": [ 238 | "error" 239 | ], 240 | "object-shorthand": [ 241 | "error", 242 | "always" 243 | ], 244 | "prefer-arrow-callback": [ 245 | "error" 246 | ], 247 | "prefer-const": [ 248 | "error" 249 | ], 250 | "prefer-numeric-literals": [ 251 | "error" 252 | ], 253 | "prefer-rest-params": [ 254 | "error" 255 | ], 256 | "prefer-spread": [ 257 | "error" 258 | ], 259 | "rest-spread-spacing": [ 260 | "error", 261 | "never" 262 | ], 263 | "template-curly-spacing": [ 264 | "error", 265 | "never" 266 | ] 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /JavaScript/d-decompose.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const merge = ( 4 | // Distinct merge miltiple arrays 5 | ...args // array of array 6 | // Returns: array 7 | ) => { 8 | const array = args[0]; 9 | for (let i = 1; i < args.length; i++) { 10 | const arr = args[i]; 11 | for (let j = 0; j < arr.length; j++) { 12 | const val = arr[j]; 13 | if (!array.includes(val)) array.push(val); 14 | } 15 | } 16 | return array; 17 | }; 18 | 19 | const section = ( 20 | // Splits string by the first occurrence of separator 21 | s, // string 22 | separator // string, or char 23 | // Example: rsection('All you need is JavaScript', 'is') 24 | // Returns: ['All you need ', ' JavaScript'] 25 | ) => { 26 | const i = s.indexOf(separator); 27 | if (i < 0) return [s, '']; 28 | return [s.slice(0, i), s.slice(i + separator.length)]; 29 | }; 30 | 31 | const SCALAR_TYPES = ['string', 'number', 'boolean', 'undefined']; 32 | const OBJECT_TYPES = ['function', 'array', 'object', 'null', 'symbol']; 33 | const META_TYPES = ['char', 'hash', 'record', 'set', 'map']; 34 | const ALL_TYPES = merge(SCALAR_TYPES, OBJECT_TYPES, META_TYPES); 35 | 36 | const FUNC_TERMS = [') {', ') => {', ') => (']; 37 | const NAMED_LINES = ['Example:', 'Returns:', 'Hint:', 'Result:']; 38 | 39 | const indexing = (s) => (term) => s.indexOf(term); 40 | 41 | const last = (arr) => arr[arr.length - 1]; 42 | 43 | const parseLines = ( 44 | // Parse signature lines 45 | s, // string, signature lines 46 | signature // record, { title, description, parameters, comments } 47 | // Returns: array of string 48 | ) => { 49 | let lines = s.split('\n'); 50 | lines.pop(); 51 | signature.title = (lines.shift() || '').replace('//', '').trim(); 52 | lines = lines.map( 53 | (d) => d.trim().replace(/^(.*) \/\//, '$1:').replace(',:', ':') 54 | ); 55 | for (let line of lines) { 56 | if (line.startsWith('//')) { 57 | line = line.replace(/^\/\/ /, '').trim(); 58 | if (NAMED_LINES.find((s) => line.startsWith(s))) { 59 | const [name, comment] = section(line, ': '); 60 | signature.comments.push({ name, comment }); 61 | } else if (signature.parameters.length === 0) { 62 | if (signature.description.length > 0) { 63 | signature.description += '\n'; 64 | } 65 | signature.description += line; 66 | } else { 67 | const par = last(signature.parameters); 68 | par.comment += '\n' + line; 69 | } 70 | } else { 71 | const [name, text] = section(line, ': '); 72 | let [type, comment] = section(text, ', '); 73 | if (!ALL_TYPES.find((s) => type.startsWith(s))) { 74 | comment = type; 75 | type = ''; 76 | } 77 | signature.parameters.push({ name, type, comment }); 78 | } 79 | } 80 | }; 81 | 82 | const parseSignature = ( 83 | // Parse function signature 84 | fn // function, method 85 | // Returns: { title, description, parameters, comments } 86 | ) => { 87 | const signature = { 88 | title: '', description: '', 89 | parameters: [], comments: [] 90 | }; 91 | let s = fn.toString(); 92 | let pos = FUNC_TERMS.map(indexing(s)) 93 | .filter((k) => k !== -1) 94 | .reduce((prev, cur) => (prev < cur ? prev : cur), s.length); 95 | if (pos !== -1) { 96 | s = s.substring(0, pos); 97 | pos = s.indexOf('\n'); 98 | s = s.substring(pos + 1); 99 | parseLines(s, signature); 100 | } 101 | return signature; 102 | }; 103 | 104 | const introspect = ( 105 | // Introspect interface 106 | namespace // hash of interfaces 107 | // Returns: hash of hash of record, { method, title, parameters } 108 | ) => { 109 | const inventory = {}; 110 | for (const name in namespace) { 111 | const iface = namespace[name]; 112 | const methods = {}; 113 | inventory[name] = methods; 114 | for (const method in iface) { 115 | const fn = iface[method]; 116 | const signature = parseSignature(fn); 117 | methods[method] = Object.assign({ 118 | method: name + '.' + method 119 | }, signature); 120 | } 121 | } 122 | return inventory; 123 | }; 124 | 125 | const badIntrospect = ( 126 | // Introspect interface 127 | namespace // hash of interfaces 128 | // Returns: hash of hash of record, { method, title, parameters } 129 | ) => { 130 | const inventory = {}; 131 | for (const name in namespace) { 132 | const iface = namespace[name]; 133 | const methods = {}; 134 | inventory[name] = methods; 135 | for (const method in iface) { 136 | const fn = iface[method]; 137 | const signature = { 138 | title: '', description: '', 139 | parameters: [], comments: [] 140 | }; 141 | let s = fn.toString(); 142 | let pos = FUNC_TERMS.map(indexing(s)) 143 | .filter((k) => k !== -1) 144 | .reduce((prev, cur) => (prev < cur ? prev : cur), s.length); 145 | if (pos !== -1) { 146 | s = s.substring(0, pos); 147 | pos = s.indexOf('\n'); 148 | s = s.substring(pos + 1); 149 | let lines = s.split('\n'); 150 | lines.pop(); 151 | signature.title = (lines.shift() || '').replace('//', '').trim(); 152 | lines = lines.map( 153 | (d) => d.trim().replace(/^(.*) \/\//, '$1:').replace(',:', ':') 154 | ); 155 | for (let line of lines) { 156 | if (line.startsWith('//')) { 157 | line = line.replace(/^\/\/ /, '').trim(); 158 | if (NAMED_LINES.find((s) => line.startsWith(s))) { 159 | const [name, comment] = section(line, ': '); 160 | signature.comments.push({ name, comment }); 161 | } else if (signature.parameters.length === 0) { 162 | if (signature.description.length > 0) { 163 | signature.description += '\n'; 164 | } 165 | signature.description += line; 166 | } else { 167 | const par = last(signature.parameters); 168 | par.comment += '\n' + line; 169 | } 170 | } else { 171 | const [name, text] = section(line, ': '); 172 | let [type, comment] = section(text, ', '); 173 | if (!ALL_TYPES.find((s) => type.startsWith(s))) { 174 | comment = type; 175 | type = ''; 176 | } 177 | signature.parameters.push({ name, type, comment }); 178 | } 179 | } 180 | } 181 | methods[method] = Object.assign({ 182 | method: name + '.' + method 183 | }, signature); 184 | } 185 | } 186 | return inventory; 187 | }; 188 | 189 | const iface = { common: { merge, section } }; 190 | console.dir(introspect(iface)); 191 | console.dir(badIntrospect(iface), { depth: 5 }); 192 | --------------------------------------------------------------------------------