5.8 Математические функции — Math

Предыдущая  Содержание  Следующая V*D*V

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

Эти функции работают в значительной степени подобно унарным операторам. Можно было бы определить новый тип узла для каждой функции и собрать большую часть функциональности из классов Minus и Name, но есть более простой способ. Мы расширим struct Name в struct Math следующим образом:

 

struct Math { struct Name _;

    double (* funct) (double);

};

 

#define funct(tree) (((struct Math *) left(tree)) -> funct)

 

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

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

 

#include <math.h>

 

void initMath (void) {

    static const struct Math functions [] = {

        { &_Math, "sqrt", MATH, sqrt },

        ...

        0 };

 

    const struct Math * mp;

 

    for (mp = functions; mp -> _.name; ++ mp)

        install(mp);

}

 

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

 

factor : NUMBER

    | - factor

    | ...

    | MATH ( sum )

 

MATH является общим маркером для всех функций, добавляемых initMath(). Это превращается в следующее дополнение к factor() в распознавателе:

 

static void * factor (void) {

    void * result;

    ...

    switch (token) {

    case MATH: {

        const struct Name * fp = symbol;

 

        if (scan(0) != ’(’)

            error("expecting (");

        scan(0);

        result = new(Math, fp, sum());

        if (token != ’)’)

            error("expecting )");

        break;

    }

 

symbol сначала содержит элемент таблицы символов для функции подобной sin(). Мы сохраняем этот указатель и строим дерево выражений для аргумента функции вызовом sum(). Затем используем Math, описание типа для функции, и предоставляем new() построить следующий узел для дерева выражений:

 

OOC_Mathematical_Functions-Math

 

Левой стороне бинарного узла предоставляем указывать на элемент таблицы символов для этой функции и подключаем дерево аргументов справа. Бинарный узел имеет Math в качестве описания типа, то есть для обработки и удаления узла будут вызываться методы doMath() и freeMath() соответственно.

Узел Math по-прежнему конструируется с помощью mkBin(), так как эту функцию не заботит, какие указатели введены в качестве потомков. freeMath(), тем не менее, может удалить только поддерево правой стороны:

 

static void freeMath (void * tree)

{

    delete(right(tree));

    free(tree);

}

 

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

 

#include <errno.h>

 

static double doMath (const void * tree) {

    double result = exec(right(tree));

 

    errno = 0;

    result = funct(tree)(result);

    if (errno)

        error("error in %s: %s",

            ((struct Math *) left(tree)) -> _.name,

            strerror(errno));

    return result;

}

 

Единственная проблема заключается отлове числовых ошибок мониторингом переменной errno, задекларированной в заголовочном файле errno.h ANSI-C. Это завершает реализацию математических функций калькулятора.

 

Предыдущая  Содержание  Следующая