Introducing CppShell

write in front

bajdcc/CppShell

Recently, I made another wheel on a whim. In fact, what inspired me is the pipeline idea in bajdcc/jMiniLang . Java runs too slow, so I used C to implement it.

As the title picture shows, it is very, very simple to use.

  1. range generates a finite/infinite sequence, `range 0` generates an infinite sequence of natural numbers, `range 1 10` generates 1 to 10
  2. take N, which means to extract the first N rows from a finite/infinite sequence
  3. last N, which means to take the reciprocal N rows from the finite sequence, of course, if the sequence is infinite, then GG
  4. load FILENAME, load file
  5. save FILENAME, save to file

Design ideas

The first is, of course, parsing the command line input, and then the processing and output.

1. Processing commands

If the command is such as `load 1.txt | uppercase | save 2.txt`, separated by "|", after separation, a single program command line is obtained, and then separated by spaces.

It is expressed by the formula:

  1. User input command_string
  2. applications = command_string.split('|')

For each app in the applications, app_args = app.split(' '), then app_name = app_args[0], delete app_args[0], and get the following parameter arguments The following task is to create an application based on app_name and arguments.

2. Create an application

Let's take `range 1 100 | save 2.txt` as an example, which means "generate a sequence from 1 to 100 and save it to a file".

So two programs range and save must be generated, so what is the relationship between the two?

Think about it: generate a sequence, save the sequence to a file. That is: take the sequence as the input and the file as the output. Got: range => save. That is, the input of save is the output of range, which is a gradually dependent relationship from front to back. In other words, the latter calls the former.

The decorator pattern is used here. Since the latter calls the former, the latter can wrap the former. Application creation involves the factory pattern, nothing more than fancy C++ tricks.

3. Application program interface design

In fact, it is the design of the "stream" interface. Referring to many flow designs, here I only implement the simplest:

  1. bool available() const, returns whether the stream is available/to the end
  2. char next(), reads the current character and prepares the next character

The application only needs to overload these two interfaces.

Code

1. Shell

void CShell::exec(const std::string& cmd)
{
    auto s = std::split(cmd, '|');
    std::vector<app_t> cmder;
    std::vector<std::string> names;
    std::vector<std::vector<std::string>> arg;
    for (auto& str : s)
    {
        str = std::trim(str);
        auto part = std::split(str, ' ');
        if (part.empty())
            return error("empty argument");
        names.push_back(part[0]);
        auto apt = CApp::get_type_by_name(part[0]);
        if (apt == app_none)
            return error("invalid application: " + str);
        part.erase(part.begin());
        cmder.push_back(apt);
        arg.push_back(part); // application parameters
    }
    auto inner = CApp::create(app_null); // innermost program
    std::shared_ptr<CApp> app;
    for (uint32_t i = 0; i < cmder.size(); i++)
    {
        app = CApp::create(cmder[i]); // Factory pattern to create application 
        if (app->set_arg(arg[i]) != 0)
             return error(names[i] + " : " + app- >get_err());
        app->set_inner_app(inner); // wrapping in decoration mode
        inner = app;
    }
    while (app->available()) // Officially working!
    {
        auto c = app->next();
        if (c != '\0')
            std::cout << c;
    }
}

void CShell::error(const std::string& str)
{
    std::cerr << str << std::endl;
}

2. App

enum app_t
{
    app__begin,
    app_none,
    app_null,
    app_pipe,
    app_range,
    app_take,
    app_last,
    app_load,
    app_save,
    app__end
};

class CApp
{
public:
    CApp();
    virtual ~CApp();

    static std::shared_ptr<CApp> create(app_t type);
    static app_t get_type_by_name(const std::string &name);

    int set_arg(std::vector<std::string> arg);
    virtual int init() = 0;

    void set_inner_app(std::shared_ptr<CApp> app);

    std::string get_err() const;

    virtual bool available() const = 0;
    virtual char next() = 0;

protected:
    std::vector<std::string> args;
    std::string error;
    std::shared_ptr<CApp> inner;
};

// create
std::shared_ptr<CApp> CApp::create(app_t type)
{
    switch (type)
    {
    case app_none:
        break;
    case app_null:
        return std::make_shared<CAppNull>();
    case app_pipe:
        return std::make_shared<CAppPipe>();
    case app_range:
        return std::make_shared<CAppRange>();
    case app_take:
        return std::make_shared<CAppTake>();
    case app_last:
        return std::make_shared<CAppLast>();
    case app_load:
        return std::make_shared<CAppLoad>();
    case app_save:
        return std::make_shared<CAppSave>();
    default:
        break;
    }
    assert(!"invalid type");
    return nullptr;
}

3. AppLoad

Take this as an example

int CAppTake::init() // initialization
{
    if (args.size() == 1) // has one argument
    {
        start = 1; // start of counting 
        end = atoi(args[0].c_str()); // end of counting
    }
    else
    {
        error = "invalid argument size";
        return -1;
    }
    return 0;
}

bool CAppTake::available() const
{
    return start <= end || !data.empty();
}

char CAppTake::next()
{
    if (data.empty())
    {
        if (!available()) // previous stream has aborted 
            return '\0';
         while (inner->available()) // previous stream has data
        {
            auto c = inner->next();
            data.push(c);
            if (c == '\n') // read a line into data 
                break ;
        }
        start++; // count up by one 
        if (data.empty()) // no more data 
            return '\0';
    }
    auto ch = data.front(); // output a line of data read
    data.pop();
    return ch;
}

Staged summary

In short, it is quite pleasant to do this wheel ~ because it is not out of the comfort zone. . Just study it.

Well, in fact, I wrote this thing because the awk, sed, grep and other search and replacement in bash are too complicated, it is better to do it yourself.

Backed up by https://zhuanlan.zhihu.com/p/26591115 .

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325155650&siteId=291194637