Как создать REPL для CAS на чистом C? [закрыто]

Вопрос или проблема

Я хочу сделать CAS на чистом C23. В качестве начала он будет работать в терминале, как любой CAS, где вы вводите данные, а он выдает результат.

Я искал в интернете способы реализации этого, и самое беспокоящее меня это: как использовать внутреннюю функцию? Вот два способа, которые я нашел:

Идея №1:

Согласно моей “наивной” идее, подтвержденной просмотром исходного кода gap, текст ввода можно использовать для создания .c файла, который мы вручную компилируем и затем выполняем. Например, с входными данными:

P := (2x+3)(x+4); P.simplify().solve();

Мы парсим это, затем создаем файл .c:

/* необходимые заголовки и глобальные переменные */

int main() {
    return solve(simplify("(2x+3)(x+4)"));
} /* Я знаю, что это должно быть более сложно, но по крайней мере, это идея */

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

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


Идея №2:

Создать хэш-таблицу для функций и создать typedef для функций с n-аргументами. Пример:

typedef void (*GenericVoidFunc)();
typedef Obj (*FuncPtr_1Arg)(Obj arg1);
typedef Obj (*FuncPtr_2Arg)(Obj arg1, Obj args2);
    ...
/* Используя "Obj" здесь как непрозрачный тип */

typedef struct {
    size_t number_of_arguments;
    GenericVoidFunc p_func;
} PublicFunction;

PublicFunction table_of_functions[] = {
    { 1, (GenericVoidFunc) &solve }
};

Затем мы будем вызывать функцию вот так:

PublicFunction function = /* получить значение из таблицы*/;
switch (function.number_of_arguments) {
    case 1:
        Obj arg1 = /* получить значение ввода */;
        Obj result = ((FuncPtr_1Arg) function.p_func)(arg1);

    ...
}

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

Здесь нет потенциальных несовместимостей, как в предыдущем, и это работает в том же процессе, что и основная программа. Но, по моему мнению, это все равно избыточно, потому что это требует создания некой динамической типизации в C, что возможно (например, смотрите cello), но выходит за рамки моей программы.


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

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

Редактировать:

Я искренне не понимаю, как я мог бы быть более “сфокусированным”.

Я хочу написать CAS, который будет принимать ввод с командной строки, как sage. Я хочу знать, исходя из этого ввода, как я могу использовать функцию, которая определена в моем собственном коде. Например, если в моем коде есть функция solve(), которую я сделал публичной, пользователь должен иметь возможность написать solve("..."), и это внутренне вызовет мою функцию (с некоторым предварительным парсингом). Я показал 2 процесса, которые могли бы быть возможными как основа для размышлений, но любые советы по этому поводу будут признательны.

Ответ или решение

Создание REPL (Read-Eval-Print Loop) для компьютерной алгебраической системы (CAS) на чистом C — задача, требующая тщательного планирования и понимания. В данной статье мы рассмотрим концептуальные подходы, касающиеся реализации такого REPL, перечислим потенциальные проблемы и предложим решения. Давай рассмотрим это более детально.

1. Основные компоненты REPL

REPL состоит из четырёх основных компонентов:

  • Чтение (Read): получение ввода от пользователя.
  • Оценка (Eval): обработка введённого выражения и его выполнение.
  • Вывод (Print): отображение результатов выполнения.
  • Цикл (Loop): повторение процесса, чтобы обеспечить возможность ввода пользователем новых выражений.

2. Подходы к реализации

Подход 1: Динамическая компиляция

Как уже указано, можно создать .c файл из пользовательского ввода, который затем компилируется и выполняется. Этот подход безопасен в плане типов, но имеет свои недостатки:

  • Проблемы совместимости: Разные компиляторы могут работать по-разному. Вам придется учесть разницу в версиях и компиляторах.
  • Задержки: Компиляция на лету требует времени, и пользователям придётся ждать, пока выполняется их код.
  • Ресурсоёмкость: Каждый раз создаётся новый процесс, что может быть неэффективно.

Подход 2: Использование таблиц функций

В вашем втором подходе предполагается использование хеш-таблицы (или массива) для хранения функций. Это, безусловно, более эффективный способ, но требует некоторой динамической типизации, что является непростой задачей в языке C.

Ваша таблица функций будет выглядеть следующим образом:

typedef struct {
    size_t number_of_arguments;
    void (*p_func)(); // указатель на функцию
} PublicFunction;

PublicFunction function_registry[] = {
    { 1, (void (*)()) &solve },
    // Добавьте другие функции
};

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

3. Парсинг пользовательского ввода

Парсинг выражений можно реализовать несколькими способами:

  • Собственный парсер: Напишите парсер, который будет анализировать пользовательский ввод и определять, какая функция должна быть вызвана вместе с аргументами.
  • Использование библиотеки: Можно воспользоваться уже существующими библиотеками (например, Flex и Bison) для генерации лексеров и парсеров.

4. Пример реализации REPL

Вот простой пример, который демонстрирует основные компоненты REPL на C:

#include <stdio.h>
#include <string.h>

typedef struct {
    size_t number_of_arguments;
    void (*p_func)();
} PublicFunction;

void solve(const char *input) {
    printf("Solving: %s\n", input);
}

PublicFunction function_registry[] = {
    { 1, (void (*)()) &solve }
};

void process_input(const char *input) {
    // Простейший парсер, извлечь имя функции и аргументы
    char func_name[64];
    sscanf(input, "%63s", func_name);
    if (strcmp(func_name, "solve") == 0) {
        char argument[256];
        sscanf(input + strlen(func_name) + 1, "%255s", argument);
        // Вызов функции
        ((void (*)(const char*))function_registry[0].p_func)(argument);
    } else {
        printf("Unknown function: %s\n", func_name);
    }
}

int main() {
    char input[512];
    while (1) {
        printf("Enter command: ");
        fgets(input, sizeof(input), stdin);
        input[strcspn(input, "\n")] = 0; // Убираем символ новой строки
        process_input(input);
    }
    return 0;
}

5. Заключение

Разработка REPL для CAS в языке C — замечательная возможность создать мощный инструмент для математических вычислений. Хотя с этим процессом связаны определённые сложности, использование эффективных методов управления функциями и парсинга входных данных может значительно упростить задачу. Полученные навыки могут оказать invaluable в процессе дальнейшей разработки и оптимизации вашего проекта.

Оцените материал
Добавить комментарий

Капча загружается...