Найти - Пользователи
Полная версия: WIP PyKSP
Начало » Python проекты » WIP PyKSP
1 2 3
Levitanus
Небольшой disclaimer:
Я не претендую на звание программиста, т.к. по образованию вообще пианист.
Просто, в процессе работы по смежной специальности, постепенно потребовалось некое программирование на быдлосредствах для чайников-музыкантов, которые хотят улучшить workflow в своем уютном закутке мира digital audio.
В настоящее время это потихоньку выливается в скромный сайд-проект под лейблом Siberian Samples (можно поискать на рутреккере), а также небольшой вклад в сообщество пользователей Cocos Reaper и NI Kontakt. Дошло дело даже до того, что в течении года я мимикрировал под кодера в более-менее состоявшейся команде Keep Forest. Правда оказалось, что профессиональная работа в программировании требует все-таки гораздо большего опыта.

Данный проект, по сути, является средством самообучения, на примере Python (в последствии для основной части деятельности планирую мигрировать в плюсы). Хочу освоится с построением архитектуры, ООП, ведением нормального репозитория и т.д. Короче, немного прокачать скиллы.

Тем не менее, учитывая в принципе неплохой стаж быдлокодера (порядка 5 лет), возможно, блог проекта будет полезен начинающим программерам, столкнувшимся с питоном. А также, поскольку я, либо в силу неграмотности, либо в силу скромности публикованных решений такого рода, либо в силу абсурдности идеи, не нашел ничего похожего на свой подход к реалиации компилятора, возможно проект будет интересен и более опытным людям

Вообще интересно. Очень часто видел фразу “каждый программист написал хоть один компилятор”, но, когда я спрашиваю совета по реализации того или иного функционала у друзей, которые не первый год живут на окладе программистов, в ответ обычно слышу что-то вроде: “знаешь, я не системщик, даже не знаю, что и сказать”.

Теперь о проекте:
Это реализация компилятора для быдло-языка, посредством предоставления библиотеки классов, использующихся для генерации кода. Суть идеи в том, что языки пишутся не год и не два, и написание собственного языка в принципе, занятие настолько же бестолковое, как и написание собственного rich text редактора. Другое дело, если в кач-ве языка будет использоваться нормальный язык, а в кач-ве средств кодогенерации будет использоваться открытая библиотека, которая может быть в любой момент любым ее пользователем расширена для своих нужд.
На самом деле, в третий раз объяснять, что, зачем и почему неохота, поэтому тех, кого я немного аинтриговал, прошу под кат:
http://pyksp-blog.readthedocs.io/ru/latest/part0_why.html

Честно говоря, мне часто нужна помощь, которую я не знаю где искать, поэтому надеюсь на сообщество
Реализация из части 1 морально устарела, хоть и написана неделю назад. С тех пор я столкнулся с парой крупных архитектурных проблем, прочитал пару умных книжек, и написал большой модуль, который скоро опишу в части 2. Не знаю, может через день, может через пару.
doza_and
Levitanus
Честно говоря, мне часто нужна помощь, которую я не знаю где искать
Из поста непонятно в чем вам нужна помощь.

Задача трансляции с одного языка на другой ну просто мегастандартная в IT области.
Вполне возможно что питон вам подходит в качестве входного языка если его допилить.

С чего надо начать? Да с описания входного языка (или библиотеки). Его синтаксиса и семантики. Берете в качестве прототипа http://hise.audio/manual/Scripting.php И допиливаете. Главное входной язык, как уж там генерировать код это дело техники.

Не являясь профессионалом в вашей области я вообще не понял что и как должно работать.
При таком описании задачи трудно ждать помощи. Приведите примеры кода которые будут решать ряд наиболее распространенных задач. Сравните с тем как это делается в HISE. Опишите предполагаемую технологию работы.
Levitanus
doza_and
Из поста непонятно в чем вам нужна помощь.
На конкретно данный момент - ни в чем
Но часто возникает какой-то формообразующий вопрос, который либо требует системного подхода более опытного программиста, либо гуглится очень с трудом

последнее, над чем пришлось подумать:

есть зачаток реалиации абстрактного синтаксического дерева. Суть в том, что при операциях с определенными объектами, они могут формировать AST узлы, которые потом одним вызовом разворачиваются в конечную строку, типа так:
 print((var0 + 6 + 8 + var1)())  # output code: var0 + 14 + var1
Сейчас я придумываю, как все эти генерируемые объекты собирать в один список с “корневыми узлами” AST, который при компиляции будет последовательно раборачиваться в филаньный код.

Есть заготовка для того, чтобы получать тело функции без необходимости ее парсинга.
Механика должна получиться следующая:
Объявляем функцию, декорированную моим классом Callable с содержанием, допустим:
 @Callable
def function()
    var0 + 6 + 8 + var1
    var2.assign(5+var1+10)
# output code: 
# var0 + 14 + var1
# var2 := 5+var1+10

И этот класс должен все генерируемые AST объекты перенаправлять в узел AST (грубо говоря, в текущую строку)

Так вот, самым очевидным решением мне сейчас кажется, сделать для выхода синглтон, который будет отслеживать текущее положение в массиве с корнями AST (грубо говоря, директивами). Но чет я боюсь, а не свяжу ли я таким образом себе руки, и не сделать ли решение с назначением выхода более “объектным”?

По такой схеме остальные классы будут реализованы примерно так:
 class Var:
    def assign(arg):
        SingletoneOutput.send_ast(str(self.value) + ' := ' + arg)

Но не лучше ли как-нибудь рекурсивно передавать output в кач-ве аргумента?

Пока что остановился на следующей реализации:
 class IOutput:
    '''
    Singletone Interface for centralized comunication
    between objects.
    Originally designed for AST collection, but can be
    instantiated by:
        INewOutput = IOutput
    and be used with other purpose
    '''
    _default = list()
    _output = _default
    def __new__(cls):
        return IOutput
    class IsSetError(Exception):
        pass
    @staticmethod
    def _raise_set_err():
        msg = "Output can't be set now. "\
            "If You want to have set"\
            " functionality in some cases, use try-except block"
        raise IOutput.IsSetError(msg)
    @staticmethod
    def set(output):
        '''
        redirect output of all put(data) calls to another list
        have to be released by release() method after all
        '''
        if IOutput._output is not IOutput._default:
            IOutput._raise_set_err()
        IOutput._output = output
    @staticmethod
    def get():
        '''
        get result list
        '''
        return IOutput._output
    @staticmethod
    def put(data):
        '''
        put data to current output list
        '''
        IOutput._output.append(data)
    @staticmethod
    def release():
        '''
        set output list to default
        '''
        IOutput._output = IOutput._default
    @staticmethod
    def refresh():
        '''
        Method for resetting interface variables to defaults.
        Used for clearing data with multiple scrits compilation.
        '''
        IOutput._default.clear()
        IOutput.release()

А в, условной функции (класс функции пока не готов, только базовый Callable)
 class Callable:
    '''
    base decorator class for all callable objects
    Usage:
    @CallableChild       # child class
    def func(inline=[True|False], *args, **kwargs):
        body
    child class have to have call() method overriden for
    assignin behaviour for calling obj without inlining of it's body
    '''
    class MissingCallError(Exception):
        pass
    def __init__(self, func):
        self.func = func
    def __call__(self, *args, inline=False, **kwargs):
        if inline is True:
            ''' здесь переназначаем IOutput на список объекта класса-потомка'''
            return self.func(*args, **kwargs)
            ''' здесь забираем вывод от всех AST методов и inline функций
            в теле '''
        if inline is False:
            return self.call(*args, **kwargs)
    def call(self, *args, **kwargs):
        '''
        this method is mandatory because of inline argument
        mandatory. If you don't want inline definition, place
            return self.func(*args, **kwargs)
        inside the call() method
        '''
        msg = 'call(self, *args, **kwargs) method '\
            'has to be overriden with inheritance of '\
            'Callable class'
        raise Callable.MissingCallError(msg)

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

При отсутствии в нем прямого доступа к памяти, и отсутствию динамической памяти вообще, я вижу вполне легкое решение для стека (локальных переменных функций и т.д.). А вот то, что в C называется куча - очень смутно.
Получается, что на этапе инициализации нужно выделить некоторое кол-во массивов под динамическую память, написать функции malloc, free, defragment, и потом использовать их в классах комплексных динамических типов данных библиотеки. Я даже примерно представляю, как должны выглядеть эти функции, но боюсь родить неэффективный велосипед.

doza_and
Да с описания входного языка
Это в целом есть. Дело по большей части за реализацией.
doza_and
Классический компилятор - парсит вход, и строит AST. Потом проводит преобразования AST а затем генерирует текст на выходном языке.
Мне не очень понятны проблемы с формированием AST. В питоне оно получится автоматически при интерпретации кода (питон строит свой AST к которому вы кстати имеете полный программный доступ). Вам надо просто операции доопределить (__add__ и тому подобное) между своими и встроенными объектами для сложения вычитания и т.п. Дальше питон все сам сделает.
Levitanus
var0 + 6 + 8 + var1->var0 + 14 + var1
Такое преобразование исходного кода в выходной код один в один делается в sympy можете там посмотреть как это делать. Более того, то что вы пока пишете реализуется просто в лоб средствами sympy.
Зачем вам в этом случае вообще строить AST?
Levitanus
Но не лучше ли как-нибудь рекурсивно передавать output в кач-ве аргумента?
С Синглтоном мне не нравится решение - в самом деле свяжет вам руки. Передавать output нужно если в нем есть конфигурационная информация нужная для формирования вывода. У вас похоже ее нет. Достаточно просто возвращать результат
Не
 class Var:
    def assign(arg):
        SingletoneOutput.send_ast(str(self.value) + ' := ' + arg)
а
 class Var:
    def assign(arg):
        return self.value + ' := ' + arg
    def assign1(arg):
        return f"{self.value}:={arg}"
Я бы предпочел вариант с форматированием. Надо будет определить __str__ или __repr__ для ваших объектов.
Кстати существуют более продвинутые способы форматирования вывода http://www.makotemplates.org/ которыми грех не воспользоваться.

Levitanus
Из насущного непонятного мне процесса, который в долгосрочной перспективе хотелось бы реализовать - динамическая память на синтаксисе KSP.
Это для меня темный лес. Синтаксис KSP я не знаю. В чем тут проблема вообще непонятно.
Levitanus
А вот то, что в C называется куча - очень смутно.
Опять задам вопрос какую роль эта концепция играет в вашем языке? Если не можете сказать, то думаю оно вам не нужно.

Если вам нужна помощь попробуйте писать проще. Преобразовать строку вида v1+3+4+v2 в строку v1+7+v2 это понятная задача. Остальное темный лес.

Помните задача- трансляции простая. Незачем тут лес городить.
Levitanus
doza_and
Я бы предпочел вариант с форматированием.
безусловно. Но соль в том, что он будет не строку слать, а AST-объект.

doza_and
Классический компилятор - парсит вход, и строит AST.
И потом польователь должен либо допилить парсер, либо просить об этом разработчика. Мне кажется, не стоит.
К тому же, это скорее не транслятор, а препроцессор. Конечно, разница невелика, но большая часть задач будут решаться “на берегу”, а не в виде транслированного кода.

Я как раз и занялся настоящим генератором, потому что дважды пытался вкиниться в уже существующий компилятор “SublimeKSP”, и оба раза натыкался либо на очень трудно-преодолимые препятствия в архитектуре, либо на проблемы производительности, в случае дубликации существующих решений. Да, те попытки можно было бы оптимизировать, но чтобы превратить тот компилятор в нормальную среду разработки, потребовалась бы вечность.

doza_and
Более того, то что вы пока пишете реализуется просто в лоб средствами sympy.
Возможно. Почитаю сегодня вечером доки, спасибо!
Но, судя по его quick-start guide, ключевое слово, возможно. Взять хотя бы пример (псевдокод):
# дано:
obj1 = [int(value = 3)]
obj2 = [obj1(5), int(2)]
# в KSP

Obj_list: Obj_data_alloc: init_int:
[type, idx, size] [type, idx, size] [3]
[obj1, 0, 1] [int, 0, 1] [5]
[obj1, 1, 1] [int, 1, 1] [2]
[obj2, 2, 2] [obj1, 1, 1]
[int, 2, 1]
И реализация простой задачи, вроде итерации по объектам с вытаскиванием параметра будет выглядеть примерно так:
 for obj in objects:
    some_var = obj.attribute
i := OBJECTS_START
while i < OBJECTS_COUNT:
select Obj_data_alloc[ Obj_list[i][idx] ][type]:
case int:
some_var := Obj_data_alloc[ Obj_list[i][idx] ][idx]

ну а, к тому же, разные объекты с разными аргументами, в разных местах должны давать не просто свое имя, а вести себя по-разному для маскимально эфективной КПД строчки результирующего кода

doza_and
Достаточно просто возвращать результат
С одной стороны, возможно. С другой - в зависимости от ситуации и типа объекта воникает много всяких но и если, вплоть до того, что я почти уверен, что будут случаи, когда хотелось бы из функции получать какой-то интерпретируемый питоном результат, а не строку кода для вывода.

Вообще, учитывая то, что я явно не даю достаточно исходных данных, давайте подождем день-два? Я чуть попишу, уложу код и мысли, и возможно, это убережет нас от мегапостов

Levitanus
Levitanus
И реализация простой задачи, вроде итерации по объектам с вытаскиванием параметра будет выглядеть примерно так:
вру.
Ща будет лютый псевдо-код:
i := OBJECTS_START
while i < OBJECTS_COUNT:
stack_push(Obj_data_alloc[ Obj_list[i][idx] ][type]) #это сокращение от конструкции
call get_object_type
some_var := stack_pull()

function get_object_type
select stack_pull()
case int:
stack_push(Obj_data_alloc[ Obj_list[i][idx] ][idx])
call get_int_val
case obj:
stack_push(Obj_data_alloc[ Obj_list[i][idx] ][idx])
call get_object_type()

function get_int_val
stack_push(init_int[stack_pull()])

а, i тоже надо будет пушить. Ну не суть, логика примерно такая
Rodegast
Я не понял зачем ты этот язык делаешь и во что ты хочешь его компилировать, но для музыки есть Csound который можно подключить к python-у.
Levitanus
Rodegast
Я не понял зачем ты этот язык делаешь
для самообразования
Rodegast
во что ты хочешь его компилировать
ближайшая аналогия - скрипты 1С
Rodegast
для музыки есть Csound который можно подключить к python-у.
да в принципе, есть даже и менее харконые решения из уже готовых библиотек, и еще менее хардкордные
Но всегда можно спуститься ниже)
Levitanus
doza_and
питон строит свой AST к которому вы кстати имеете полный программный доступ
как-то пропустил я это предложение
И как к нему получить программный доступ? Или Вы имеете ввиду листинг code object-ов?
doza_and
Levitanus
И как к нему получить программный доступ?
https://docs.python.org/3/library/ast.html
Levitanus
для самообразования
Я за то чтобы даже для самообразования делать полезные вещи. Поэтому и предлагаю сначала провести сравнение с аналогами и выделить вашу основную идею.
Levitanus
хотелось бы из функции получать какой-то интерпретируемый питоном результат, а не строку кода для вывода.
Не противоречит возврату результата. Питон динамический язык. тип может определяться после возврата данных и может меняться.

Levitanus
давайте подождем день-два? Я чуть попишу, уложу код и мысли
Да это хорошее предложение. Лучше так и сделать.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB