Успадковуємо тип .NET від JavaScript об'єкта з перевантаженнями і приватними методами

Так, саме так і ніяких хитрощів. Ця ідея мою голову відвідала близько двох місяців тому в процесі обмірковування статті про Алгоритмах і рішеннях. Типи .NET в тому движку використовувати легко, а можна навпаки…

Трохи про движкуУ попередній статті я намагався менше писати про особливості і більше про те, що може бути корисно у відриві від скриптования взагалі, щоб стаття була менше схожа на саморекламу. Буквально з перших коментарів я зрозумів, що це було помилкою.
Швидкість
Він швидкий. Дуже швидкий. За минулий місяць з невеликим було перелопачено багато коду та розібрано безліч приватних випадків і зроблені висновки. До прикладу, в залежності від того, як написана функція і які мовні засоби використовуються, її виклик може відбуватися по одному з більш ніж 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<string>(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. Це рішення, принаймні, на даному етапі, не придатне для серйозного використання. Це експеримент.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.