Внутреннее имя и манглинг имен

Именование объектов основано на правилах и выбирается из расчёта удобства для программиста. Название переменной или функции должно нести смысл, понятные параметры и тип возвращаемого значения, которые упрощают понимание алгоритма при последующем чтении исходного текста программы.

Но обычные имена мало о чём говорят компьютеру. Поэтому при компиляции программы, имена объектов преобразуются для удобства машинной обработки и к ним добавляется служебная информация.

Манглинг нативных имен переменных и функций происходит в соотвествии с правилами компилятора языка реалиазции, а все остальные объекты именются следующим образом:

Внутреннее имя объекта

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

Внутреннее имя формируется во время анализа синтаксического дерева программы и включает в себя имя объекта и его квалификатор в соотвествии с алгоритмом разрешения имен. У статических объектов, типов и классов, внутренее имя объекта включает в себя полную иерархию области имен.

Кроме этого, все квалификаторы объектов переносятся в конец имени, что и является отличительным признаком внутреннего имени (последний символ должен быть “$” или “:”).

Примеры внутренних имен:


# Глобальный статический объект (глобальная видимость)
::var ::= 0; # ::var::

# Локальный объект модуля (видимость модуль/пакет)
$var := 0;  # var$
var := 0;  # var$

# Статический объект модуля (видимость модуль/пакет)
@::var ::= 0; # var::

# Глобальные тип и конструктор класса (глобальная видимость)
:: :type := :Type;     # ::type:::
:: :cls := Class(){};  # ::cls:::

# Тип или конструктор класса модуля (видимость модуль/пакет)
:type := :Type;     # type::: 
:cls := Class(){};  # cls:::
@:: :type := :Type;     # type:::
@:: :cls := Class(){};  # cls:::

ns {
    $var := 0;      # var$
    var ::= 0;      # var$
    @::var ::= 0;   # ns::var:: - статический объект

    :type := :Type;     # ns::type:::
    :cls := Class(){};  # ns::cls:::

    @::func1() ::= {};   # ns::func1::
    func2() ::= {};      # ns::func2::

    # Не именованный блок кода
    {  
        # Локальные имена перекрывают объявления выше
        $var := 0;  # var$
        var = 0;    # var$

        # Статический объект соответствует области имен
        @::var ::= 0; # ns::var::

        # Типы данных всегда статические
        :type := :Type;     # ns::1::type:::
        :cls := Class(){};  # ns::1::cls:::

        @::func3() := {};    # ns::func3::
        # Локальная функция с ограниченной областью видимости ?????
        func4() := {};       # ns::1::func4$
    }
};

# Не именованный блок кода 
{
    $var := 0;      # var$
    var  := 0;      # var$
    @::var = 0;     # 1::var:: - статический объект

    :type := :Type;     # 1::type:::
    :cls := Class(){};  # 1::cls:::
};

Манглинг внутренних имен

Внутреннее имя уникально только в рамках одного модуля, но внутренние имена в разных модулях могут пересекаться.

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

Внутреннее имя объекта после манглинга является корректным идентификатором для языков C/C++, что позволяет обращатся к объектам NewLang из внешних библиотек и приложений на языке реализации (C/C++) по семантически похожему имени (в отличии от манглинга имен в С++).

Манглинг внутреннего имени заключается в замене каждого символа двоеточия на символ $, а у имени типа квалификатор заменяется тремя символами ‘$’, т.е. ’type $$$’.

При манглинге, к внутренним именам объектов добавляется специальный префикс, который кодирует имя компилируемого модуля. Имя текущего модуля находится в переменной препроцессора "@$$" и по умолчанию, содержит имя файла модуля (у главного модуля приложения имя модуля отсутствует).

Имя модуля может быть однократно переопределено самым первым оператором модуля или заменено во время импорта модуля с помощью системного параметра __package__.

    # Задать имя модуля
    @$$ = "\dir\file";

    # Импорт модуля с переопределением имени
    \dir\file(__package__="\new\module\name");

Тоже само с использвоанием DSL:

@package \dir\name;  # @$$ = "\dir\file";

@import \dir\name as \name; # \dir\file(__package__="\name");

При манглинге имя модуля по умолчанию установливается как имя файла отностительно каталога главного файла программы. Так как имя модуля, согласно правилам синтаксиса, может содерждать только строчные буквы, цифры и подчерк (кроме первого и последнего символа), то разделитель каталогов (обратный слеш) заменяется на символ $, а начало и конец имени модуля отмечаются комбинациями символов “_$” и “$_” соответственно.

Так как локальной переменной с одним подчерком в качестве имени быть не может, то начало строки на “_$” служит отличительным признаком манглинга внутреннего имени объекта.

@$$ = "\dir\file";

# Глобальный статический объект
::var ::= 0; # _$dir_file$_$$var$$

# Локальный объект модуля
$var := 0;  # _$dir_file$_var$$

# Статический объект модуля
@::var ::= 0; # _$dir_file$_var$$

# Глобальные тип и конструктор класса
:: :type := :Type;     # _$dir_file$_$$type$$$
:: :cls := Class(){};  # _$dir_file$_$$cls$$$

# Синоним типа или конструктор класса модуля
:type := :Type;     # _$dir_file$_type$$$
:cls := Class(){};  # _$dir_file$_cls$$$

ns {
    $var := 0;  # var$
    var ::= 0;  # var$
    @::var ::= 0; # _$dir_file$_ns$$var$$ - статический объект

    :type := :Type;     # _$dir_file$_ns$$type$$$
    :cls := Class(){};  # _$dir_file$_ns$$cls$$$

    @::func() ::= {};      # _$dir_file$_ns$$func$$
    func() ::= {};      # _$dir_file$_ns$$func$

    # Не именованный блок кода
    {  
        # Локальные имена перекрывают обяъявления выше
        $var := 0;  # _$dir_file$_var$
        var := 0;  # _$dir_file$_var$

        # Статический объект соответствует области имен
        @::var ::= 0; # _$dir_file$_ns$$var$$

        # Типы данных и функции всегда статические
        :type := :Type;     # _$dir_file$_ns$$type$$$
        :cls := Class(){};  # _$dir_file$_ns$$cls$$$

        @::func() := {};      # _$dir_file$_ns$$func$$
        func() := {};      # _$dir_file$_ns$$1$$func$ ????????
    }
};

# Не именованный блок кода 
{
    $var := 0;      # _$dir_file$_var$
    var ::= 0;      # _$dir_file$_var$
    @::var = 0;     # _$dir_file$_var$$ - статический объект

    :type := :Type;     # _$dir_file$_type$$$
    :cls := Class(){};  # _$dir_file$_cls$$$
};

Манглинг имен блока кода главного модуля программы:

{
    $var := 0;      # var$
    var ::= 0;      # var$
    @::var = 0;     # _$$_var$$ - статический объект

    :type := :Type;     # _$$_type$$$
    :cls := Class(){};  # _$$_cls$$$
};

Встроенный блок кода и вызов функций

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

Это допускается как для обычных переменных, так и для имен функций, реализованных в NewLang. И для того, что бы компилятор C/C++ понимал к каким объектам происодит обращение, при раскрытии встроенного блока кода перед ним добавляются сигнатуры переменных и функций, которые используются в тексте встроенного кода на C\C++.

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

Каждая функция NewLang имеет две сигнатуры, первая (основная) принимает все аргументы в виде словаря, а вторая (дополнительная) с развернутым списком аргументов, чтобы её можно было вызывать как обычную C++ функцию и проверка типов могла происходить непосредственно С/С++ компилятором.

Манглинг второго (дополнительного) имени функции заключается в добавлении в конец имени двух символов “_$”.

    
    # Определение функции в NewLang
    ::func_embed(int64_t arg_long, uint8_t arg_byte = 10): Int64 ::= {
        @return arg_long + arg_byte;
    }

    # С/С++ прототип для первого (основоного) имени
    # extern "C" _$$_func_embed$( Obj &args );

    # С/С++ прототип для второго (дополнительного) имени
    # extern "C" _$$_func_embed$_$(int64_t arg_long, uint8_t arg_byte = 10);
    
    # Тогда при раскрытии блока с расширенным синтаксисом 
    # $func_embed заменяется на _$$_func_embed$_$, а компилятор С/С++ 
    # будет в состоянии проверить её аргументы
    {%
        int64_t value = $func_embed(10, 20);
    %}

Хотя может имеет смысл сделать inline шаблон для подобного вызова, а не создавать кополнительное имя?

./object.h:732:25: error: deduced conflicting types ('int' vs 'const char *') for initializer list element type   
            auto list = {args...};   
                        ^~~~~
test/object_test.cpp:131:21: note: in instantiation of function template specialization 'newlang::Obj::operator()<int, const char *>' requested here   
    str3 = (*format)(-1, "222");   
                    ^