OneScript icon indicating copy to clipboard operation
OneScript copied to clipboard

Регистрация функции, вызываемой автоматически при инициализации неизвестного типа/обращению к неизвестному модулю

Open leemuar opened this issue 7 years ago • 32 comments

Хотелось бы иметь автозагрузчик сценариев.

Для этого достаточно возможности указать какую функцию нужно вызвать, если тип не определен.

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

// эта процедура будет подключать сценарии к системе типов
Процедура ПриПодключенииТипа(ИмяТипа)
   ПодключитьСценарий("lib/" + ИмяТипа + ".os", ИмяТипа);
КонецПроцедуры

// устанавливаем процедуру, которая будет подключать сценарии
ПодключитьОбработчикЗагрузкиТипов("ПриПодключенииТипа", ЭтотОбъект);

// Тип ЗагрузчикМодулей здесь не определен
// Будет вызвана зарегистрированная ранее процедура, которая попробует подключить сценарий
Загрузчик = Новый ЗагрузчикМодулей;

leemuar avatar Oct 16 '17 10:10 leemuar

Идея интересная, но хотелось бы узнать, что натолкнуло на нее? Лишние строчки кода для ПодключитьСценарий/ЗагрузитьСценарий? Неинтуитивный порядок загрузки? Проблема с подключением собственных библиотек?

nixel2007 avatar Oct 16 '17 12:10 nixel2007

Задача ясна, а вот цель непонятна. Какую задачу вы хотите решить?

EvilBeaver avatar Oct 16 '17 13:10 EvilBeaver

Да, цель непонятна. Хотелось бы еще увидеть структуру проекта и клиентский код, который будет использовать указанную реализацию.

artbear avatar Oct 16 '17 13:10 artbear

Похоже на #257

leemuar avatar Oct 27 '17 14:10 leemuar

Я хочу решить сразу несколько проблем:

  1. Не хочу писать везде ЗагрузитьСценарий() или Использовать при работе с собственными и чужими библиотеками, не входящими в стандартную
  • Во-первых мне лень.
  • во-вторых не хочу распространять по скриптам знание о том, что библиотеки лежат по определенным путям в файловой системе. Хочу, чтобы знание о том где брать модуль было сосредоточено только в одном объекте.
  1. Меня не устраивают принципы работы opm. Я уже столкнулся с кучей проблем с пересечением имен, неправильной работой с зависимостями, боязнью развивать стандартную библиотеку из-за страха внести breaking change и другими. Глобальные пакеты приводят к проблемам. Хочу чтобы зависимости устанавливались, подключались и обновлялись локально для проекта, из директорию vendor/.

  2. opm сосредоточен в одних руках и не дает возможности устанавливать пакеты, не входящие в стандартные. Чтобы попасть в стандартные нужно тоже связываться с владельцами opm и его хаба и зависеть от их решения. Я хочу создать более либеральный менеджер зависимостей, работа которого не будет зависеть от третьих лиц. Загрузил пакет на гитхаб - его сразу можно скачать/обновить.

В основном идея идет от менеджера зависимостей PHP Composer, который в свою очередь вдохновлен npm (node.js) и bundler (ruby).

main.os

/vendor
    /leemuar
        /стек.os
        /queue.os
    /silverbulleters
        /src
            /deployka.os
// main.os

// загрузит /vendor/leemuar/стек.os
Cтек = Новый leemuar_Стек;
// загрузит /vendor/silverbulleters/src/deployka.os
Деплойка = Новый silverbulleters_deployka;

Можно было бы реализовать все это через функцию-хелпер, но это во-первых дополнительные js-стайл сложности, во-вторых в oscript нет возможности добавлять глобальные функции.

Мне хотелось бы, чтобы была возможность регистрировать стек методов для подключения новых типов и символов (модулей). Это позволит гибко настраивать работу приложений. Ближайший аналог желаемого - http://php.net/manual/en/function.spl-autoload-register.php

leemuar avatar Nov 06 '17 13:11 leemuar

  1. Подключение всего можно сделать в одной точке входа уже сейчас. Для этого надо просто создать головной скрипт и подключить там через Использовать весь подчиненный каталог.
  2. Работа с локальными зависимостями в опм тоже уже есть - это параметр -l при установке. Он ставит все зависимости в каталог oscript_modules, по аналогии с npm.

nixel2007 avatar Nov 06 '17 13:11 nixel2007

Переопоеделение путей поиска при подключении библиотек по именам тоже уже есть - для этого служит файл oscript.cfg рядом с точкой входа в приложение и/или скриптами, где идёт подключение.

nixel2007 avatar Nov 06 '17 13:11 nixel2007

Предлагаю все-таки недостатки устранить в рамках одного инструмента - opm. Тем более что в нем есть часть желаемого

EvilBeaver avatar Nov 06 '17 13:11 EvilBeaver

ок, а возможность добавления автозагрузчика-то добавлять будете? Я бы тогда в opm его использование добавил

leemuar avatar Nov 06 '17 14:11 leemuar

Авозагрузчика чего? Я пропустил возможно...

EvilBeaver avatar Nov 06 '17 14:11 EvilBeaver

@EvilBeaver ну собственно то о чем в первом сообщении этого ишуза говориться: автозагрузчик типов и модулей

leemuar avatar Nov 06 '17 14:11 leemuar

Я предлагаю следующее:

в движке есть стек, хранящий функции вызова. Когда с помощью оператора "Новый" инициализируется неизвестный движку тип - вызываются процедуры из этого стека по очереди. Если после их вызова новый тип все еще не определен - вызывать исключение. То же самое при обращении к неизвестному символу (для модулей).

В языке нужны функции, позволяющие добавить и удалить методы из этого стека.. Или глобальный объект-менеджер:

Процедура ОбработчикЗагрузки() КонецПроцедуры
Процедура ЕщеОдинОбработчикЗагрузки() КонецПроцедуры

МенеджерАвтозагрузки.ДобавитьОбработчик(ЭтотОбъект, "ОбработчикЗагрузки");
МенеджерАвтозагрузки.ДобавитьОбработчик(ЭтотОбъект, "ЕщеОдинОбработчикЗагрузки");

// первым будет вызываться последний добавленный обработчик
// сначала ЕщеОдинОбработчик()
// потом ОбработчикЗагрузки()
МойОбъект = Новый Мой_Супер_Объект;

leemuar avatar Nov 06 '17 15:11 leemuar

Идея понятна. Но на уровне движка потребует серьезной допилки. И потом, нужен ведь не хендлер, а скорее сама возможность дополнения глобального контекста, разве нет?

EvilBeaver avatar Nov 06 '17 17:11 EvilBeaver

@EvilBeaver возможность дополнения глобального контекста (по крайней мере для типов) уже есть в движке: ПодключитьСценарий(). #Использовать уже умеет создавать глобальные переменные (модули)), этого не достаточно?

Нужна только возможность "подписаться" на событие движка "тип не определен" и "символ (модуль) не определен", я думал в обработчике события использовать ПодключитьСценарий() и то что вызывает инструкция #Использовать для подключения глобальных переменных

leemuar avatar Nov 06 '17 18:11 leemuar

Я так понял, что сейчас в языке нет возможности определять глобальные переменные, хотя инструкция #Использовать это умеет. т.е. в движке уже заложена возможность дополнять глоб контекст, просто нет в самом языке способов это сделать

Могу предложить чтобы не добавлять доп. конструкции в язык для дополнения глоб. контекста сделать так: добавить подписку на событие "символ не найден", обработчик которого должен вернуть объект или неопределено. Если обработчик вернет объект - он будет движком зарегистрирован в глоб контексте под указанным именем как модуль. Если обработчик вернет "неопределено" - ничего не делать.

Процедура ОбработчикЗагрузкиМодуля(ИмяМодуля) Экспорт
    Если ФайлСуществует("src/" + ИмяМодуля + ".os")
        Возврат ЗагрузитьСценарий("src/" + ИмяМодуля + ".os");
    Иначе
        // файла нет, подключать нечего
        Возврат Неопределено;
    КонецЕсли;
КонецПроцедуры


ДобавитьОбработчикАвтозагрузкиМодулей(ЭтотОбъект, "ОбработчикЗагрузкиМодуля");

// переменная ЮнитТестирование тут не определена
ЮнитТестирование.ПроверитьРавно();

// будет вызвана ОбработчикЗагрузкиМодуля с параметром "ЮнитТестирование"
// если она вернет Неопределено - вызовется исключение
// если вернет объект - движок зарегистрирует глобальную переменную с переданным в параметре именем (ЮнитТестирование). в котором будет храниться возвращенный объект

На данный момент будет достаточно хотя бы подписаться на событие "Тип не определен", чтобы в нем можно было использовать ПодключитьСценарий().

leemuar avatar Nov 06 '17 18:11 leemuar

Я так понял, что сейчас в языке нет возможности определять глобальные переменные, хотя инструкция #Использовать это умеет. т.е. в движке уже заложена возможность дополнять глоб контекст, просто нет в самом языке способов это сделать

В языке есть методы ДобавитьКласс и ДобавитьМодуль. Они вызываются в package-loader

nixel2007 avatar Nov 06 '17 19:11 nixel2007

@nixel2007 Спасибо! Тогда вообще никаких проблем, нужны только подписки на события "тип не определен" и "символ не определен" - в обработчиках уже средствами языка подключим, используя эти функции.

leemuar avatar Nov 06 '17 19:11 leemuar

насчет глобальных: такая возможность закладывалась, но сейчас Использовать этого не умеет из-за особенностей реализации "памяти" виртуальной машины. Расширять гл. контекст можно только свойствами, но не методами.

Неглобально - как описал @nixel2007 через кастомный загрузчик. А документация на методы - есть. Вот она: http://oscript.io/docs/page/package-loader

EvilBeaver avatar Nov 07 '17 09:11 EvilBeaver

EvilBeaver проблема в том, что ошибка неизвестного символа генерируется во время компиляции модуля. А обработчик должен работать во время выполнения, т.е. когда компиляция уже завершена. Получается проблема курицы и яйца.

leemuar avatar Nov 14 '17 16:11 leemuar

Получается проблема курицы и яйца.

Я правильно понял, что вот из-за этого мы ставим wontfix и закрываем?

EvilBeaver avatar Dec 11 '17 10:12 EvilBeaver

Получается проблема курицы и яйца. Я правильно понял, что вот из-за этого мы ставим wontfix и закрываем?

имхо проблемы курицы и яйца на текущий момент нет, все однозначно.

До тех пор, пока в языке не было возможности создавать пользовательские типы - проверка во время компиляции была полностью оправдана. Как только в движке появилась возможность динамически создавать типы во время выполнения (ПодключитьСценарий()) - имхо проверка типов теперь тоже должна быть перенесена в рантайм.

Я бы не закрывал и оставил на потом в низком приоритете

leemuar avatar Dec 12 '17 07:12 leemuar

@leemuar не вижу связи между "проверкой типов" и ошибкой "Символ не определен" на этапе компиляции. Разверни мысль, если не сложно?

EvilBeaver avatar Dec 12 '17 14:12 EvilBeaver

@EvilBeaver

не вижу связи между "проверкой типов" и ошибкой "Символ не определен" на этапе компиляции

Да, ты прав, связи нет. Я ошибся и всех запутал. Для такого кода:

а = Новый МойОбъект;

выдается не ошибка компиляции "Символ не определен", а рантайм ошибка "Конструктор не обнаружен".

leemuar avatar Jan 23 '18 14:01 leemuar

Если Конструктор не обнаружен - значит такого типа не зарегистрировано. метод "ПодключитьСценарий" регистрирует тип и он потом доступен. Я по-прежнему не понял вопроса...

EvilBeaver avatar Jan 24 '18 08:01 EvilBeaver

Прочитал и захотел высказаться. С толкнулся с проблемой, следующего характера, невозможно добавить глобальный класс Как это делается в пакедж-лоадере через функцию "ДобавитьКласс", необходимо было для динамической подгрузки используемых классов в gitsync при подключении плагина. Сейчас решил кастылем: Автоматическим формированием файла package-loader.os в определенном каталоге. ( что не позволяет переопределить сам каталог и прочие не удобства) В том числе сразу про контролировать перечень загруженных классов/модулей, их потом приходить читать в файле lib.config (а если его нет? )

//////////////////////////////////////////////////////
// ДАННЫЙ ФАЙЛ ФОРМИРУЕТСЯ АВТОМАТИЧЕСКИ            //
// ВСЕ ВНЕСЕННЫЕ ИЗМЕНЕНИЯ В РУЧНУЮ БУДУТ ПОТЕРЯНЫ  //
//////////////////////////////////////////////////////

///////////////////////////////////
// ИМПОРТ БИБЛИОТЕКИ ПЛАГИНА "preinstalled_plugins_gitsync"
#Использовать "./preinstalled_plugins_gitsync"
///////////////////////////////////

Процедура ПриЗагрузкеБиблиотеки(Путь, СтандартнаяОбработка, Отказ)

	СтандартнаяОбработка = Ложь;

КонецПроцедуры

khorevaa avatar Jan 24 '18 11:01 khorevaa

Если Конструктор не обнаружен - значит такого типа не зарегистрировано. метод "ПодключитьСценарий" регистрирует тип и он потом доступен. Я по-прежнему не понял вопроса...

@EvilBeaver совершенно верно. Однако в рантайме нельзя подключить модуль:

ДобавитьКласс("Мой.os", "МойМодуль");
МойМодуль.Вперед()

приводит к ошибке компиляции "Неизвестный символ". Об этом и пишет @khorevaa : неудобно подключать модуль через #использовать, хотелось бы подключать модули в рантайме напрямую вызовом спецфункции.

Ведь такая конструкция работает без ошибок компиляции:

МойМодуль = ЗагрузитьСценарий("Мой.os");
МойМодуль.Вперед();

Это напрямую влияет на это ишуз: если проверка модулей происходит на этапе компиляции, значит добавить собственный обработчик на oscript для этого события будет нельзя. А хочется, чтобы было можно.

leemuar avatar Jan 24 '18 15:01 leemuar

@khorevaa я не понял вопроса, извини.

@leemuar смотри: package-loader - это класс движка, имеющий собственный модуль. Как в 1С: документ это платформенный класс, но его "модуль документа" это скрипт, который "видит" метод Записать(), свойства Номер, коллекцию Табличных частей и т.п.

Также и loader. Это платформенный класс с модулем. Метод ДобавитьКласс и ДобавитьМодуль - это методы платформенного класса инкапсулированные внутри него и видимые только из модуля этого класса.

EvilBeaver avatar Jan 24 '18 18:01 EvilBeaver

Загружать "Модули" в рантайме нельзя, т.е. это изменит глобальный контекст. Метод "ЗагрузитьСценарий" не создает "модуль" в глобальном контексте, он создает обычный объект, который возвращает тебе же.

EvilBeaver avatar Jan 24 '18 18:01 EvilBeaver

Загружать "Модули" в рантайме нельзя, т.е. это изменит глобальный контекст

@EvilBeaver понятно что нельзя сейчас. И о причинах ты писал - особенности реализации памяти. Мы с @khorevaa хотели бы, чтобы стало можно когда-нибудь

leemuar avatar Jan 24 '18 19:01 leemuar

У меня вот мозг почему-то хочет воспринимать классы как обработки. Может быть это и удовлетворит всех? Т.е. создание обработки из файла скрипта по аналогии с 1С. Это же будет функционально тот же класс

ps Или ЗагрузитьСценарий() сейчас так и работает?

tsukanov-as avatar Jan 24 '18 19:01 tsukanov-as