介绍CppShell

写在前面

bajdcc/CppShell

最近心血来潮又造了个轮子,其实启发我的是bajdcc/jMiniLang中的管道思想,java运行着太慢,因而用C艹实现一把。

如题图所示,使用非常非常简单。

  1. range生成有限/无穷数列,`range 0`生成自然数无穷数列,`range 1 10`生成1到10
  2. take N,表示从有限/无穷数列中摘取前N行
  3. last N,表示从有限数列中取倒数N行,当然了如果数列是无穷的,那么GG
  4. load FILENAME,加载文件
  5. save FILENAME,保存至文件

设计思路

首先当然是解析命令行输入啦,然后则是处理与输出。

一、处理命令

假如命令是诸如`load 1.txt | uppercase | save 2.txt`,以“|”作分隔,分隔后得到单一程序命令行,再以空格作分隔。

用式子来表示是:

  1. 用户输入command_string
  2. applications = command_string.split('|')

对applications中每一app,app_args = app.split(' '),然后app_name = app_args[0],删除app_args[0],得到后面的参数arguments下面的任务是,根据app_name和arguments来创建应用程序。

二、创建应用程序

我们以`range 1 100 | save 2.txt`为例,意义为“生成1到100的数列,然后保存至文件”。

所以必须生成两个程序range和save,那么两者是什么关系呢?

思考一下:生成数列,将数列保存至文件。即:以数列作输入,文件为输出。得到:range => save。即save的输入是range的输出,是从前向后逐渐依赖的关系,换句话说,后者调用前者。

这里用到了装饰者模式,既然后者调用前者,那么后者将前者包裹起来即可。应用程序的创建涉及工厂模式,没什么大过花哨的C++技巧。

三、应用程序接口设计

其实也就是“流”接口的设计。参考众多流设计,这里我只实现最简单的:

  1. bool available() const,返回流是否可用/到末尾
  2. char next(),读取当前字符,并准备下个字符

应用程序只要重载这两个接口即可。

代码实现

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); // 应用程序参数
    }
    auto inner = CApp::create(app_null); // 最里层程序
    std::shared_ptr<CApp> app;
    for (uint32_t i = 0; i < cmder.size(); i++)
    {
        app = CApp::create(cmder[i]); // 工厂模式创建应用程序
        if (app->set_arg(arg[i]) != 0)
            return error(names[i] + ": " + app->get_err());
        app->set_inner_app(inner); // 装饰模式进行包装
        inner = app;
    }
    while (app->available()) // 正式工作!
    {
        auto c = app->next();
        if (c != '')
            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;
};

// 创建
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

就举这一个例子吧

int CAppTake::init() // 初始化
{
    if (args.size() == 1) // 有一个参数
    {
        start = 1; // 计数开始
        end = atoi(args[0].c_str()); // 计数结束
    }
    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()) // 上一流已经中止
            return '';
        while (inner->available()) // 上一流有数据
        {
            auto c = inner->next();
            data.push(c);
            if (c == '
') // 读取一行到data中
                break;
        }
        start++; // 计数加一
        if (data.empty()) // 没有数据了
            return '';
    }
    auto ch = data.front(); // 输出读取的一行数据
    data.pop();
    return ch;
}

阶段性总结

总之,做这个轮子还是挺愉悦的~因为并未脱离舒适区。。就当复习吧。

好吧,其实写这玩意是因为bash中的awk、sed、grep等查找替换太复杂了,还不如自己做个。

https://zhuanlan.zhihu.com/p/26591115备份。

原文地址:https://www.cnblogs.com/bajdcc/p/8972973.html