Подготовка переводчика для обработки ошибок


Это продолжение вопроса, который я выложил ранее в отношении моего переводчика.

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

func function_name param1 param2 param3 . / param3 * param1 param2 

или такой

func to_radians deg . * deg / 3.14159 180

а также такой

func sind theta . sin to_radians theta

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

Кроме того, он может читать из исходного кода.

Вот пример такого кода:

; Variables
def PI * 4 atan 1

; Functions
func to_rad deg . * deg / PI 180

func sind theta . sin to_rad theta

; Run the program
sin to_rad 45
sind 45

Который потом будет выводить

0.707107

0.707107

Если вы никогда не запрограммирован с помощью Lisp-подобного языка программирования, например схема -- вы бы заметили, что есть много сходства с тем, что я сделал. На самом деле, он был вдохновлен теми языков программирования.


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

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

У меня есть предчувствие, что я должен разделить код на несколько файлов. Но это только одна, и даже то, что мне нужна помощь о том, как я должен подойти к нему.

Каково ваше предложение?

#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <sstream>
#include <cmath>
#include <cstdlib> // mmocny: I needed to add this to use atof
#include <functional>

using namespace std;

//----------------------------------

class Variable
{
public:
    Variable(const string& name, double val)
        : name_(name), val_(val) // mmocny: Use initializer lists
    {
    }

    // mmocny: get_* syntax is less common in C++ than in java etc.
    const string& name() const { return name_; } // mmocny: Don't mark as inline (they already are, and its premature optimization)
    double val() const { return val_; } // mmocny: Again, don't mark as inline
private:
    string name_; // mmocny: suggest renaming name_ or _name: Easier to spot member variables in method code, and no naming conflicts with methods
    double val_;
};

//----------------------------------

class Func
{
public:
    Func(const string& name, const vector<string>& params, const string& instr)
        : name_(name), params_(params), instr_(instr)
    {
    }

    const string& name() const { return name_; }
    const vector<string>& params() const { return params_; }
    const string& body() const { return instr_; }
private:
    string name_;
    vector<string> params_;
    string instr_;
};

//----------------------------------

// mmocny: Replace print_* methods with operator<< so that other (non cout) streams can be used.
// mmocny: Alternatively, define to_string()/str() methods which can also be piped out to different streams
std::ostream & operator<<(std::ostream & out, Variable const & v)
{
    return out << v.name() << ", " << v.val() << endl;
}

std::ostream & operator<<(std::ostream & out, Func const & f)
{
    out << "Name: " << f.name() << endl
        << "Params: " << endl;
    for (vector<string>::const_iterator it = f.params().begin(), end = f.params().end(); it != end; ++it)
    {
        out << "    " << *it << endl;
    }
    cout << endl;
    cout << "Body: " << f.body();
}

std::ostream & operator<<(std::ostream & out, vector<Variable> const & v)
{
    for (vector<Variable>::const_iterator it = v.begin(), end = v.end(); it != end; ++it ) // mmocny: Use iterators rather than index access
    {
        out << *it << endl;
    }
    return out;
}

std::ostream & operator<<(std::ostream & out, vector<Func> const & v)
{
    for (vector<Func>::const_iterator it = v.begin(), end = v.end(); it != end; ++it)
    {
        out << *it << endl;
    }
    return out;
}

//----------------------------------

class Interpreter
{
public:
    const vector<Variable>& variables() const { return variables_; }
    const vector<Func>& functions() const { return functions_; }

    // mmocny: replace istringstream with istream
    // mmocny: you only need to predeclare this one function

    double operate(const string& op, istream& in, vector<Variable>& v);
    double operate(const string& op, istream& in);

    double get_func_variable(const string& op, istream& in, vector<Variable> v)
    {
        // mmocny: instead of using a vector<Variable> you should be using a map/unordered_map<string,double> and doing a key lookup here
        for (int size = v.size(), i = size - 1; i >= 0; i--)
        {   
            if (op == v[i].name())
                return v[i].val();
        }

        for (int size = functions_.size(), i = 0; i < size; i++)
        {
            if (op == functions_[i].name())
            {
                vector<Variable> copy = v;
                vector<Variable> params;

                for (int size_p = functions_[i].params().size(), j = 0; j < size_p; j++)
                {
                    string temp;
                    in >> temp;
                    params.push_back(Variable(functions_[i].params()[j], operate(temp, in, v)));
                }

                /*for (vector<string>::const_iterator it = functions_[i].params().begin(),
                     end = functions_[i].params().end();
                     it != end; ++it)
                {
                    string temp;
                    in >> temp;
                    params.push_back(Variable(*it, operate(temp, in, v)));
                }*/

                for (int size_p = params.size(), j = 0; j < size_p; j++)
                {
                    copy.push_back(params[j]);
                }

                /*for (vector<Variable>::const_iterator it = params.begin(), end =  params.end();
                     it != end; ++it)
                {
                    copy.push_back(*it);
                }*/

                istringstream iss(functions_[i].body());
                string temp;
                iss >> temp;
                return operate(temp, iss, copy);
            }
        }

        // mmocny: what do you do if you don't find the variable?
        int char_to_int = op[0];
        cout << char_to_int << endl;
        cout << "'" << op << "' is not recognized." << endl;
        throw std::exception(); // mmocny: You should do something better than throw a generic exception()
    }

    double get_func_variable(const string& op, istream& in)
    {       
        return get_func_variable(op, in, variables_);
    }

    //----------------------------------

    bool is_number(const string& op)
    {
        // mmocny: someone else already mentioned: what if op is empty?
        int char_to_int = op[0];
        // mmocny: couple notes here:
        // 1) chars are actually numbers you can reference directly, and not need "magic" constants
        // 2) functions in the form "if (...) return true; else return false;" should just be reduced to "return (...);"
        // 3) is_number functionality already exists in libc as isdigit()
        // 4) long term, you are probably going to want to improve this function.. what about negative numbers? numbers in the form .02? etc..
        //return (char_to_int >= '0' && char_to_int <= '9');
        return isdigit(char_to_int);
    }

    //----------------------------------

    template< class Operator >
    double perform_action(istream& in, Operator op, vector<Variable>& v)
    {
        string left;
        in >> left;

        double result = operate(left, in, v); // mmocny: This is a big one: for correctness, you must calculate result of left BEFORE you read right

        string right;
        in >> right;

        return op(result, operate(right, in, v));
    }

    template< class Operator >
    double perform_action(istream& in, Operator op)
    {
        return perform_action(in, op, variables_);
    }

    //----------------------------------

    void define_new_var(istream& in)
    {
        string name;
        in >> name;

        string temp;
        in >> temp;

        double value = operate(temp, in);

        variables_.push_back(Variable(name, value));
    }

    //----------------------------------

    void define_new_func(istream& in)
    {
        string name;
        in >> name;

        string temp;
        vector<string> params;
        do
        {
            in >> temp;
            if (temp == ".")
                break;

            params.push_back(temp);

        } while (temp != ".");

        string body = "";
        while (in >> temp)
        {
            body += temp + " ";
        }

        Func fu(name, params, body);
        functions_.push_back(fu);
    }

private:
    vector<Variable> variables_;
    vector<Func> functions_;
};

//----------------------------------

double Interpreter::operate(const string& op, istream& in, vector<Variable>& v)
{
    double value;
    if (op == "+")
        value = perform_action(in, plus<double>(), v);
    else if (op == "-")
        value = perform_action(in, minus<double>(), v);
    else if(op == "*")
        value = perform_action(in, multiplies<double>(), v);
    else if (op == "/")
        value = perform_action(in, divides<double>(), v);
    /*else if (op == "%")
        value = perform_action(in, modulus<double>());*/
    else if (op == "sin")
    {
        string temp;
        in >> temp;
        value = sin(operate(temp, in, v));
    }
    else if (op == "cos")
    {
        string temp;
        in >> temp;
        value = cos(operate(temp, in, v));
    }
    else if (op == "tan")
    {
        string temp;
        in >> temp;
        value = tan(operate(temp, in, v));
    }
    else if (op == "asin")
    {
        string temp;
        in >> temp;
        value = asin(operate(temp, in, v));
    }
    else if (op == "acos")
    {
        string temp;
        in >> temp;
        value = acos(operate(temp, in, v));
    }
    else if (op == "atan")
    {
        string temp;
        in >> temp;
        value = atan(operate(temp, in, v));
    }
    else if (is_number(op))
        value = atof(op.c_str()); // mmocny: consider using boost::lexical_cast<>, or strtod (maybe)
    else
        value = get_func_variable(op, in, v);

    return value;
}

double Interpreter::operate(const string& op, istream& in)
{
    return operate(op, in, variables_);
}

//----------------------------------

void run_code(Interpreter& interpret, const string& op, istream& in)
{
    if (op == "def")
        interpret.define_new_var(in);
    else if (op == "func")
        interpret.define_new_func(in);
    else if (op[0] == ';' || op.empty())
        return;
    else
        cout << endl << interpret.operate(op, in) << endl;
}

//----------------------------------

bool is_all_blank(const string& line)
{
    for (int i = 0; i < line.size(); i++)
    {
        if (line[i] != ' ')
            return false;
    }

    return true;
}

//----------------------------------

int main()
{
    cout << endl << "LePN Programming Language" << endl;

    Interpreter interpret;

    while (cin)
    {
        cout << endl << "> ";

        string temp;
        getline(cin, temp);

        if (temp.empty()) // mmocny: This also handles the case of CTRL+D
            continue;

        istringstream iss(temp);
        string op;
        iss >> op;

        if (op == "quit")
            break;
        else if (op == "show_vars")
            std::cout << interpret.variables() << std::endl;
        else if (op == "show_func")
            std::cout << interpret.functions() << std::endl;
        else if (op == "open")
        {
            string filename;
            if (iss)
            {
                iss >> filename;
            }
            else
            {
                cin >> filename;
            }

            ifstream file(filename.c_str());

            while (file && !file.eof())
            {
                string line;
                getline(file, line);

                istringstream temp_stream(line);
                if (!temp_stream || is_all_blank(line))
                {
                    continue;
                }

                temp_stream >> op;
                int char_to_int = op[0];
                run_code(interpret, op, temp_stream);
            }
        }
        else
            run_code(interpret, op, iss);
    }
}


335
4
задан 4 мая 2011 в 06:05 Источник Поделиться
Комментарии
2 ответа

Если вы собираетесь серьезно на этот проект, вот некоторые вопросы, которые стоит учесть:


  1. Вы используете контроль версий? У всех нас есть свои предпочтения, но главное, что ты через что-то. Это дает вам возможность нажмите "Отменить" в случае, если вы сделать большую бяку.

  2. Есть ли модульные тесты? Если вы в настоящее время не модульного тестирования я бы настоятельно рекомендуем вам начать делать это сейчас, прежде чем ваш переводчик больше. Мне нравится использовать тест Google, но есть много других с тестирования++ рамок, чтобы выбрать из. Модульное тестирование дает возможность лучше контролировать энтропии в ваш проект и это становится более важным, как ваш проект растет.

  3. Код хорошо разделяются на различные соответствующие исходные файлы? Просто вы не представляете, как вещи каждое предложение в один гигантский абзац в эссе, вы не хотите, чтобы вещи всех классов, функций и переменных в одном источнике. В C++ принято помещать каждый класс в своем собственном заголовке(.H) и реализацию(.cpp файл).

Теперь несколько конкретных замечаний относительно вашей представленный код:


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

  • Определение типа передоза СТД::вектор использования, чтобы сделать его более контейнер-агностик. Если вы решили изменить СТД::вектор в СТД::МАП , позже вам не придется менять его везде. Также рекомендуется использовать с std::back_insert_iterator , а не напрямую СТД::вектор::push_back , чтобы сделать его еще более контейнер-агностик.

  • Эта функция может быть выражена более прямо:

    bool is_all_blank(const string& line)
    {
    // Shouldn't it check for other possible whitespace too? Like tab for example
    return line.find_first_not_of( " \t" ) == string::npos;
    }

  • Пока просматривал код, я замечаю, что она действительно без каких-либо замечаний. Вы можете посмотреть в починке.

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

4
ответ дан 5 мая 2011 в 12:05 Источник Поделиться


  1. Некоторые предыдущие рекомендации пока не решен.

  2. двойной get_func_variable(константные строки& ОП, на iStream& в, вектор в)


    • вы генерировать исключение в конце функции, которая является хорошо, но где вы улавливаете это?

    • как насчет с std::find_if для этого:

      for (int size = v.size(), i = size - 1; i >= 0; i--)
      {
      if (op == v[i].name())
      return v[i].val();
      }


  3. Для функции

    void define_new_var(istream& in)

    и

    void define_new_func(istream& in)

    проверьте значение переменной из функции существует(используя std::find_if, написать общую функцию).


  4. Я думаю, что архитектура будет расширяться и, возможно, имеет смысл выполнить команду образец и шаблон фабрики:

    double Interpreter::operate(const string& op, istream& in, vector<Variable>& v)

  5. Использовать с std::find_if для поиска некоторых объектов в вектор или список.

2
ответ дан 4 мая 2011 в 08:05 Источник Поделиться