Наследуем тип .NET от JavaScript объекта с перегрузками и приватными методами01.01.2015 05:18
Да, именно так и никаких уловок. Эта идея мою голову посетила около двух месяцев назад в процессе обдумывания статьи об Алгоритмах и решениях. Типы .NET в том движке использовать легко, а можно ли наоборот…Немного о движке
В предыдущей статье я старался меньше писать об особенностях и больше о том, что может быть полезно в отрыве от cкриптования вообще, дабы статья была меньше похожа на саморекламу. Буквально, с первых комментариев я понял, что это было ошибкой.СкоростьОн быстр. Очень быстр. За прошедший месяц с небольшим было перелопачено много кода и разобрано множество частных случаев и сделаны выводы. К примеру, в зависимости от того, как написана функция и какие языковые средства используются, её вызов может происходить по одному из более чем 10 сценариев от эквивалента инлайна до честного выделения памяти под каждую переменную и аргумент и полную инициализацию контекста.Простота интеграцииВся тяжелая «кухня» по обеспечению доступа к типам платформы скрыта за одной скромной функцией
JSObject TypeProxy.Proxy (object)
Вы просто отдаёте практически любой объект и результат назначаете переменной
var script = new Script (» megaObject.alert ('Hello from javascript') »);
script.Context.DefineVariable («megaObject»)
.Assign (TypeProxy.Proxy (new { alert = new Action(x => MessageBox.Show (x)) });
script.Invoke ();
Для типов, реализующих интерфейс IList есть специальная обёртка NiL.JS.Core.TypeProxing.NativeList, маскирующая такой объект под родной массив js.Можно зарегистрировать конструктор типа и создавать объекты уже во время выполнения сценария. Добавится переменная с именем типа.
script.Context.AttachModule (typeof (System.Windows.Forms.Form));
Если лень добавлять типы по одному, можно добавить целое пространство имён
Context.GlobalContext.DefineVariable
(«forms») // имя переменной, через которую будет доступно пространство имён.
.Assign (new NamespaceProvider
(«System.Windows.Forms»)); // пространство имён, к которому будет осуществляться доступ.
Не только выполнениеКаждый узел синтаксического дерева доступен извне сборки. Вы можете реализовать свою виртуальную машину, транслятор в другой язык, статический анализатор (если будет недостаточно интегрированного) или ещё что-то, на что способна ваша фантазия. Каждое использование всех переменных хранит ссылку на соответствующий дескриптор, который может рассказать некоторую информацию о ней. Например, показать все места использования, которые «выжили» после оптимизации. А пару недель назад был реализован, так называемый, Visitor с помощью которого всё перечисленное сделать ещё проще.
Для того, чтобы наследовать тип в платформе .NET нужна сборка, лежащая на диске, в которой хранится информация о типе (метаданные). Но пока JS файл лежит на диске, там никаких типов нет. Они там появятся только во время выполнения, когда логика этого сценария разделит функции на, собственно, функции и конструкторы. Решение этой загвоздки нашлось почти сразу — я добавил в глобальный контекст функцию, которая принимает на вход конструктор («registerClass»). Таким образом, я, как бы, прошу мне показать, для каких js-функций стоит генерировать метаданные. Однако, это требует холостого запуска.После того, как выполнение закончилось, с помощью System.Reflection.Emit создаётся сохраняемая сборка в которой объявляется по одному классу для каждого зарегистрированного конструктора плюс ещё один статический, который будет хранить код javascript и запускать его при первом обращении. На этом этапе запуск registerClass () уже используется для того, чтобы поставить в соответствие типы в сборке с типами в сценарии. Все типы-обёртки унаследованы от одного типа в котором описаны базовые механизмы взаимодействия. Состав типов формируется на основе того, что было найдено в прототипе конструктора.
function ctor () {
}
ctor.prototype // есть у каждой функции кроме некоторых встроенных
Таким образом, если добавить неперечисляемое свойство в прототип, оно не попадёт в метаданные и станет «приватным».Отлично, теперь можно создавать объекты JS как объекты .NET, но где обещанные наследование и перегрузки?!
Это решилось проще. Для того, чтобы понять, какие методы перегружены, в конструкторе базового типа выполняется проход по всем функциям в типе (понимаю, не совсем удачное место, но для прототипа реализации этого достаточно) и проверяется предок типа их объявившего. Все переопределённые методы становятся объявленными в типе-наследнике. Все методы, которые найдутся такой проверкой, добавляются в JS реализацию типа. Всё.Теперь даже новые добавленные функции будут доступны в js, им не требуется быть перегруженными.
Код получился не большим, вы можете посмотреть его на GitHub
P.S. Это решение, по крайней мере, на данном этапе, не пригодно для серьёзного использования. Это эксперимент.
© Habrahabr.ru