Skip to content

Instantly share code, notes, and snippets.

@RReverser
Last active September 6, 2015 11:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RReverser/359ae1f7aa901e86156f to your computer and use it in GitHub Desktop.
Save RReverser/359ae1f7aa901e86156f to your computer and use it in GitHub Desktop.
jsunderhood tutorial
#!/usr/bin/env node
var fs = require('fs');
var acorn = require('./parser');
var transform = require('./transform');
var escodegen = require('escodegen');
var filename = process.argv[2];
if (!filename) {
console.log('Usage: transpile filename.js');
process.exit(1);
}
// Читаем файл в строку
var code = fs.readFileSync(filename, 'utf-8');
// Парсим вместе с нашим плагином
var ast = acorn.parse(code, {
sourceFile: filename,
locations: true,
plugins: {
slice: true
}
});
// Транспайлим
transform(ast);
// Генерируем полученный код
code = escodegen.generate(ast);
// Сохраняем
fs.writeFileSync(filename.replace(/\.js$/, '.out.js'), code);
var acorn = require('acorn');
// Хранилище для типов синтаксических элементов (tokens)
var tt = acorn.tokTypes;
// Добавляем свой плагин в jQuery-style :)
acorn.plugins.slice = function (parser) {
// Инджектимся в метод parseMaybeConditional чтобы
// добавить синтаксис на уровне тернарного оператора
parser.extend('parseMaybeConditional', function (prev) {
return function () {
// Сохраняем начальные позиции, чтобы было откуда начать нашу ноду
var startPos = this.start, startLoc = this.startLoc;
// Вызываем обычный обработчик, чтобы он считал тернарный оператор
// или выражение попроще
var inner = prev.apply(this, arguments);
// Проверяем, является ли следующий элемент двоеточием
// и, если да, то сразу его "съедаем"
if (this.eat(tt.colon)) {
// Он таки является двоеточием, значит это и будет наш "range expression"
// Создаем для него ноду из самого начала
var node = this.startNodeAt(startPos, startLoc);
// Кладём в эту ноду прочитанное выражение как начало
node.from = inner;
// Аналогично читаем правую часть
node.to = prev.apply(this, arguments);
// Завершаем ноду с собственным типом и возвращаем её.
return this.finishNode(node, 'RangeExpression');
} else {
// Если не нашли сразу двоеточия, значит не наш парень,
// и можно сразу вернуть прочитанное выражение
return inner;
}
};
});
};
module.exports = acorn;
var types = require('ast-types');
var n = types.namedTypes;
var b = types.builders;
// Регистрируем наш кастомный тип с его полями
// (https://github.com/benjamn/ast-types#custom-ast-node-types)
var def = types.Type.def;
def('RangeExpression')
.bases('Expression')
.build('from', 'to')
.field('from', def('Expression'))
.field('to', def('Expression'));
types.finalize();
module.exports = function transform(ast) {
// Совершаем обход дерева для транспайлинга
// наших нод в настоящий JS
types.visit(ast, {
// Нас интересуют только обращения к свойствам
visitMemberExpression: function (path) {
// Сначала обходим всех детей
this.traverse(path);
var node = path.node;
var range = node.property;
// Если свойство - не наш range, то оно нас особо не интересует, выходим
if (!n.RangeExpression.check(range)) return;
// А иначе конвертируем a[b:c] в a.slice(b,c), создавая соответствующие ноды
// Сначала создаем ноду для a.slice
var method = b.memberExpression(
node.object, // в качестве обьекта выступает оригинальный обьект
b.identifier('slice'), // а в качестве свойства - идентификатор "slice"
false // указываем что это non-computed свойство - именно a.slice а не a[slice]
);
// А теперь создаём вызов этого метода с нашими началом/концом в качестве параметров
var call = b.callExpression(
method, // сама функция, которая будет вызываться - a.slice
[ range.from, range.to ] // аргументы - параметры нашего Range Expression
);
// И заменяем оригинальную ноду этим вызовом
path.replace(call);
}
});
};
@dotcypress
Copy link

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment