Last active
September 6, 2015 11:53
-
-
Save RReverser/359ae1f7aa901e86156f to your computer and use it in GitHub Desktop.
jsunderhood tutorial
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
}); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
👍