среда, 13 апреля 2016 г.

JavaScript code to string converter

github.com/latentflip/deval

Sometimes you're doing interesting things, and you want a block of code as a multiline string.
But doing this is super annoying:

var codeString = [
  "var foo = 'bar'",
  "function stuff () {",
  "  console.log('The thing is \"10\"');"
  "}"
].join('\n');

Quotes everywhere, keeping track of indentation is a pain if you want it properly formatted, no syntax highlighting.

Function code to string makes it look like this:

var codeToString = require('codeToString');

var codeString = codeToString(function () {
    var foo = 'bar';
    function stuff () {
        console.log('The thing is "10"');
    }
});

// codeString -> "var foo = 'bar';\nfunction stuff () {\n    console.log('The thing is \"10\"');\n}"


It even figures out what indentation you meant and cleans that up.

If eval() takes a string representing code, and turns it into actual code, codeToString() takes actual code, and returns a string representation of it.

Basic usage.

Call codeToString() with a function containing the code you want to get back as a string.
The function wrapper will be removed.

var codeToString = require('codeToString');

var codeString = codeToString(function(){
    var foo = 'bar';
    function stuff () {
        console.log('The thing is "10"');
    }
});

// codeString will be:
//    "var foo = 'bar';
//    function stuff () {
//        console.log('The thing is \"10\"');
//    }"

Advanced usage.

Sometimes you want to interpolate strings / numbers / etc into your generated code.
You can't just use normal scoping rules, because this code won't be executed in the current scope.
So instead you can do a little templating magic.

To interpolate:

- Name some positional arguments in the function you pass to codeToString: codeToString(function (arg1, arg2) { ...
- Insert them where you want them in your code by wrapping in dollars: $arg1$
- Pass the values of those arguments as additional arguments to codeToString itself. codeToString(function (arg1, arg2) { ... }, "one", 2)

var codeString = codeToString(function (foo, bar) {
    var thing = $bar$;
    console.log('$foo$');
    console.log(thing);
}, "hi", 5);

// codeString will be:
//    "var thing = 5;
//    console.log('hi');
//    console.log(thing)"

Don't try to be too clever with this, and if you're passing strings, you'll want to wrap them in quotes inside the code block, as shown about for "hi" -> '$foo$'

Source Code of codeToString():

var min = function (arr) {return Math.min.apply(Math, arr);};

var REGEXES = {
    functionOpening: /^function\s*\((.*)\)[^{]{/
};


module.exports = function (fn/*, interpolateArgs... */) {
    var str = fn.toString();
    var interpolateArgs = Array.prototype.slice.call(arguments, 1);
    var argNames;
    if (interpolateArgs.length) {argNames = getArgumentNames(str);}
    str = removeFunctionWrapper(str);
    str = dedent(str);
    if (argNames && argNames.length) {str = interpolate(str, argNames, interpolateArgs);}
    return str;
};

function getArgumentNames (str) {
    var argStr = str.match(REGEXES.functionOpening);
    return argStr[1].split(',').map(function (s) { return s.trim(); });
}

function removeFunctionWrapper (str) {
    var closingBraceIdx, finalNewlineIdx, lastLine;

    // remove opening function bit
    str = str.replace(REGEXES.functionOpening, '');

    // remove closing function brace
    closingBraceIdx = str.lastIndexOf('}');
    if (closingBraceIdx > 0) {str = str.slice(0, closingBraceIdx - 1);}

    // If there was no code on opening wrapper line, remove it
    str = str.replace(/^[^\S\n]*\n/, '');

    // If there was no code on final line, remove it
    finalNewlineIdx = str.lastIndexOf('\n');
    lastLine = str.slice(finalNewlineIdx);
    if (lastLine.trim() === '') str = str.slice(0, finalNewlineIdx);

    return str;
}

// Reset indent on the code to minimum possible
function dedent (str) {
    var lines = str.split('\n');
    var indent = min(lines.map(function (line) {return line.match(/^\s*/)[0].length;}));
    lines = lines.map(function (line) {return line.slice(indent);});
    return lines.join('\n');
}

function interpolate (str, argNames, args) {
    argNames.forEach(function (name, i) {
        var regex = new RegExp('\\$' + name + '\\$', 'g');
        str = str.replace(regex, args[i]);
    });
    return str;
}

Tests:

var test = require('tape');
var codeToString = require('./codeToString');

test('it serializes multiline code', function (t) {
    var serialized = codeToString(function () {
        console.log('hi');
        console.log('there');
    });

    var expected = [
        "console.log('hi');",
        "console.log('there');"
    ].join('\n');

    t.equal(serialized, expected);
    t.end();
});

test('it serializes inline code', function (t) {
    var serialized = codeToString(function () { console.log('hi'); console.log('there'); });

    var expected = [
        "console.log('hi'); console.log('there');"
    ].join('\n');

    t.equal(serialized, expected);
    t.end();
});

test('it even interpolates things', function (t) {
    var serialized = codeToString(function (foo, bar) {
        console.log('$foo$');
        console.log($bar$);
    }, "hi", 5);

    var expected = [
        "console.log('hi');",
        "console.log(5);"
    ].join('\n');

    t.equal(serialized, expected);
    t.end();
});

JavaScript Code Execution Visualization

latentflip.com/loupe
github.com/latentflip/loupe
pythontutor.com/javascript.html#mode=edit

JavaScript Turing Machine

All it needs are some instructions, the initial state of the tape as a list, an end state and a start state. It will return either the final tape state or false if the end state is never reached. ‘B’ is considered a blank state and the tape behaves as infinite in both directions.

function tm (I, tape, end, state, i, cell, current) {
    i = 0;
    while (state != end) {
        cell = tape[i];
        current = (cell) ? I[state][cell] : I[state].B;
        if (!current) {return false;}
        tape.splice(i, 1, current.w);
        i += current.m;
        state = current.n;
    }
    return tape;
}

// For testing purposes, run in Node.js as command:
// node turing-140.js machine-140.json 111 q5
// Instructions tape endstate.

console.log(
    tm(
           JSON.parse(
               require('fs').readFileSync(process.argv[2], 'utf-8')
           )
         , process.argv[3].split("")
         , process.argv[4]
         , "q0"
    ).join("")
);

For testing use a simple multiply program that basically turns 111 into 1110111. So this is what the algorithm’s implementation ends up looking like

{
    "q0": {"1": {"w": "B", "m": 1, "n": "q1"}},
    "q1": {"1": {"w": "1", "m": 1, "n": "q1"},
        "0": {"w": "0", "m": 1, "n": "q2"},
        "B": {"w": "0", "m": 1, "n": "q2"}},
    "q2": {"1": {"w": "1", "m": 1, "n": "q2"},
        "B": {"w": "1", "m": -1, "n": "q3"}},
    "q3": {"1": {"w": "1", "m": -1, "n": "q3"},
        "0": {"w": "0", "m": -1, "n": "q3"},
        "B": {"w": "1", "m": 1, "n": "q4"}},
    "q4": {"1": {"w": "B", "m": 1, "n": "q1"},
        "0": {"w": "0", "m": 1, "n": "q5"}}
}

There’s a smaller solution of Turing Machine in a less verbose language:

function(a,b,c,d,e){for(e=0;d<c;)with(a[d][b[e]||"B"])b[e]=w,e+=m,d=n;return b}

пятница, 1 апреля 2016 г.

JavaScript Observer, Iterator, Generator, Promise and Observable Patterns

Итератор перебирает подряд набор значений и по очереди выдает элементы iterator.next(), как array.shift()
Генератор по очереди принимает элементы generator.next(1), как array.push(1)
Промис по очереди выполняет цепочку зависимых друг от друга асинхронных событий
Обозреватель добавляет к объекту к объекту функцию события и вызывает её, когда это событие генерируется в коде.
Обозреваемое добавляет к последовательности элементов или асинхронных событий итератор, который по порядку перебирает элементы и обозреватель, который вызывает для перебираемых элементов функции события.
Декоратор обертывает одну функцию в другую функцию decorate(base, wrapper).
Фабрика конструирует объекты по заданным параметрам.
Фасад скрывает сложную функциональность, выдавая вместо неё простую функцию.
Прокси оборачивает функцию в декоратор.

Observer Pattern

// Observer

function Observer () {
    this.events = {};
}

Observer.prototype = {
      addEvent: function (name, func) {if (this.events[name]) {this.events[name].push(func);} else {this.events[name] = [func];}}
    , removeEvent: function (name) {if (this.events[name]) {delete this.events[name];}}
    , dispatchEvent: function (name, args) {if (this.events[name]) {this.events[name].forEach(function (func) {func.apply(null, args);});}}
};

// Observer Test

var observer = new Observer();
observer.addEvent('one', function (a) {console.log('1: ' + a);});
observer.addEvent('one', function (b) {console.log('2: ' + b);});
observer.addEvent('two', function (c) {console.log('3: ' + c);});
observer.dispatchEvent('one', ['one']);
observer.dispatchEvent('two', ['two']);
observer.removeEvent('one');
observer.dispatchEvent('one', ['one']);

Iterator Pattern

// Iterator

function Iterator (items) {
    var i = 0;
    return {
        next: function () {
            var done = (i >= items.length)
                , value = !done ? items[i++] : undefined;
            return {value: value, done: done};
        }
    };
}

// Iterator Test

var iterator = new Iterator([1, 2, 3]);
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: undefined, done: true}

Generator Pattern

// Generator

function Generator (items) {
    var i = 0;
    return {
        next: function (value) {
            items = items.slice();
            var done = (i >= items.length)
            if (!done) {items[i] = value; i++;}
            return {value: items, done: done};
        }
    };
}

// Generator Test

var generator = new Generator(new Array(3));
console.log(generator.next(1)); // {value: [1, undefined, undefined], done: false}
console.log(generator.next(2)); // {value: [1, 2, undefined], done: false}
console.log(generator.next(3)); // {value: [1, 2, 3], done: false}
console.log(generator.next(4)); // {value: [1, 2, 3], done: true}

Promise Pattern

// Promise

function Promise () {
    this.promises = [];
}

Promise.prototype = {
      then: function (success, error) {
        if (Object.prototype.toString.call(success) !== '[object Function]') {success = function () {};}
        if (Object.prototype.toString.call(error) !== '[object Function]') {error = function () {};}
        this.promises.push({success: success, error: error});
        return this;
      }
    , success: function () {
        if (this.promises.length) {
            var args = Array.prototype.slice.call(arguments);
            args.unshift(this);
            this.promises.shift().success.apply(null, args);
        }
      }
    , error: function () {
        if (this.promises.length) {
            this.promises.shift().error.apply(null, arguments);
        }
      }
};

// Promise Test

function timeout1 (seconds, error) {
    var promise = new Promise();
    setTimeout(function () {
        if (seconds > 2) {
            promise.error(seconds);
        } else {
            console.log('Success 1: ' + seconds);
            promise.success(seconds);
        }
    }, seconds * 1000);
    return promise;
}

function timeout2 (promise, seconds) {
    setTimeout(function () {
        if (seconds > 2) {
            promise.error(seconds);
        } else {
            console.log('Success 2: ' + seconds);
            promise.success(seconds);
        }
    }, seconds * 1000);
    return promise;
}

function timeout3 (promise, seconds) {
    setTimeout(function () {
        if (seconds > 1) {
            promise.error(seconds);
        } else {
            console.log('Success 3: ' + seconds);
            promise.success(seconds);
        }
    }, seconds * 1000);
    return promise;
}

function done (promise, seconds) {
    console.log('Done: ' + seconds);
}


function timeout1error (seconds) {console.log('Error 1 seconds: ' + seconds);}
function timeout2error (seconds) {console.log('Error 2 seconds: ' + seconds);}
function timeout3error (seconds) {console.log('Error 3 seconds: ' + seconds);}

timeout1(2).then(timeout2, timeout1error).then(timeout3, timeout2error).then(done, timeout3error);

Observable Pattern = Interator + Promise for async + Observer

Перебираем с помощью итератора все элементы массива или цепочки промисов и для каждого из них тригерим подставленные функции события (успешно, неуспешно, завершено).

// Observable

var Observable = {
    from: function (items) {
        var iterator = new Iterator(items);
        return {subscribe: function (next, error, complete) {
            var item = iterator.next();
            while (!item.done) {
                next(item.value);
                item = iterator.next();
            }
            complete();
        }};
    }
};

// Observable Test

Observable.from(['Adria', 'Jen', 'Sergi']).subscribe(
      function onNext (value) {console.log('Next: ' + value);}
    , function onError (error) {console.log('Error: ' + error);}
    , function onCompleted () {console.log('Completed');}
);

вторник, 29 марта 2016 г.

Error handling in JavaScript

Modern Chrome and Opera fully support the HTML 5 draft spec for ErrorEvent and window.onerror. In both of these browsers you can either use window.onerror, or bind to the 'error' event properly:

// Only Chrome & Opera pass the error object.
window.onerror = function (message, file, line, col, error) {
    console.log(message, "from", error.stack);
    // You can send data to your server
    // sendError(data);
};

// Only Chrome & Opera have an error attribute on the event.
window.addEventListener("error", function (e) {
    console.log(e.error.message, "from", e.error.stack);
    // You can send data to your server
    // sendError(data);
})

Unfortunately Firefox, Safari and IE are still around and we have to support them too. As the stacktrace is not available in window.onerror we have to do a little bit more work.

It turns out that the only thing we can do to get stacktraces from errors is to wrap all of our code in a try{ }catch(e){ } block and then look at e.stack. We can make the process somewhat easier with a function called wrap that takes a function and returns a new function with good error handling.

function wrap(func) {
    // Ensure we only wrap the function once.
    if (!func._wrapped) {
        func._wrapped = function () {
            try{
                func.apply(this, arguments);
            } catch(e) {
                console.log(e.message, "from", e.stack);
                // You can send data to your server
                // sendError(data);
                throw e;
            }
        }
    }
    return func._wrapped;
};

This works. Any function that you wrap manually will have good error handling, but it turns out that we can actually do it for you automatically in most cases.

By changing the global definition of addEventListener so that it automatically wraps the callback we can automatically insert try{ }catch(e){ } around most code. This lets existing code continue to work, but adds high-quality exception tracking.

var addEventListener = window.EventTarget.prototype.addEventListener;
window.EventTarget.prototype.addEventListener = function (event, callback, bubble) {
    addEventListener.call(this, event, wrap(callback), bubble);
}

We also need to make sure that removeEventListener keeps working. At the moment it won't because the argument to addEventListener is changed. Again we only need to fix this on the prototype object:

var removeEventListener = window.EventTarget.prototype.removeEventListener;
window.EventTarget.prototype.removeEventListener = function (event, callback, bubble) {
    removeEventListener.call(this, event, callback._wrapped || callback, bubble);
}

Transmit error data to your backend

You can send error data using image tag as follows

function sendError(data) {
    var img = newImage(),
        src = 'http://yourserver.com/jserror&data=' + encodeURIComponent(JSON.stringify(data));

    img.crossOrigin = 'anonymous';
    img.onload = function success() {
        console.log('success', data);
    };
    img.onerror = img.onabort = function failure() {
        console.error('failure', data);
    };
    img.src = src;
}

среда, 16 марта 2016 г.

Support ECMAScript 5, 6, 7 Shims and Shams

ECMAScript 5 (есть в es5-shim)

Object
- Object.keys

Array
- Array.isArray
- [].indexOf
- [].lastIndexOf
- [].every
- [].some
- [].forEach
- [].map
- [].filter
- [].reduce
- [].reduceRight

String
- "".trim
- "".split - crossbrowser (нет в es5-shim)

Function
- (function(){}).bind

Date
- Date.now
- (new Date()).toISOString
- (new Date()).toJSON
- (new Date()).parse -> return NaN for invalid dates

JSON (нет в es5-shim)
- JSON.parse
- JSON.stringify

ECMAScript 6 (есть в es6-shim)

Object
- Object.assign
- Object.is
Только, если есть поддержка ES5
- Object.getPrototypeOf
- Object.getOwnPropertyDescriptor
- Object.getOwnPropertyNames
- Object.seal
- Object.freeze
- Object.preventExtensions
- Object.isSealed
- Object.isExtensible
- Object.keys

String
- String.raw
- String.fromCodePoint
- "".codePointAt
- "".repeat
- "".startsWith
- "".endsWith
- "".includes

Number
- Number.isFinite
- Number.isInteger
- Number.isSafeInteger
- Number.isNaN
- Number.EPSILON
- Number.MIN_SAFE_INTEGER
- Number.MAX_SAFE_INTEGER
- Number('0o1')
- Number('0b1')

Array
- Array.from
- Array.of
- [].copyWithin
- [].find
- [].fineIndex
- [].fill
- [].keys
- [].values
- [].entries

RegExp
- (//).flags

Math
- Math.clz32
- Math.imul
- Math.sign
- Math.log10
- Math.log2
- Math.log1p
- Math.expm1
- Math.cosh
- Math.sinh
- Math.acosh
- Math.asinh
- Math.atanh
- Math.trunc
- Math.fround
- Math.cbrt
- Math.hypot

Map
Set
Reflect
Promise

ECMAScript 7 (есть в es7-shim)

Object
- Object.values
- Object.entries
- Object.getOwnPropertyDescriptors

Array
- [].includes

String
- "".padStart
- "".padEnd
- "".trimLeft
- "".trimRight
- "".at

Map
- (new Map()).toJSON

Set
- (new Set()).toJSON

Последовательность включения новых функций в проект:
- es5-shim.min.js
- es5-sham.min.js
- json3.min.js
- es6-shim.min.js
- es6-sham.min.js
- es7-shim.min.js
...other-libs.js...

понедельник, 14 марта 2016 г.

JavaScript Decorator

function decorate (baseFunction, decoratorFunction) {
    return function () {decoratorFunction(baseFunction, arguments);};
}

function a (a) {
    console.log(a);
    return 'OK';
}

function b (baseFunction, baseFunctionArguments) {
    console.log('start');
    baseFunction.apply(null, baseFunctionArguments);
    console.log('b');
    console.log('stop');
}

a = decorate(a, b);

a('a');

function trace (baseFunction, baseFunctionArguments) {
    var result = baseFunction.apply(null, baseFunctionArguments);
    console.log('TRACE: ' + baseFunction.name + ' (' + Array.prototype.slice.call(baseFunctionArguments).join(', ') + ') => ' + result);
    return result;
}

var c = decorate(a, trace);

c('a', 'b', 1, 2, {});