xaizek / zograscope (License: AGPLv3 only) (since 2018-12-07)
Mainly a syntax-aware diff that also provides a number of additional tools.
<root> / src / tooling / Finder.cpp (6f691ed88269cfe3070ddaeafe33d6b6577a36d6) (6,873B) (mode 100644) [raw]
// Copyright (C) 2018 xaizek <xaizek@posteo.net>
//
// This file is part of zograscope.
//
// zograscope is free software: you can redistribute it and/or modify
// it under the terms of version 3 of the GNU Affero General Public License as
// published by the Free Software Foundation.
//
// zograscope is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with zograscope.  If not, see <http://www.gnu.org/licenses/>.

#include "Finder.hpp"

#include <boost/range/adaptor/reversed.hpp>
#include <boost/optional.hpp>

#include "pmr/monolithic.hpp"

#include <iostream>
#include <string>

#include "utils/optional.hpp"
#include "ColorScheme.hpp"
#include "Grepper.hpp"
#include "Matcher.hpp"
#include "TermHighlighter.hpp"
#include "Traverser.hpp"
#include "common.hpp"
#include "mtypes.hpp"

namespace {

// Strong typing of string with value for output stream overload.
struct AutoNL { const std::string &data; };

// Formatted value printer.
inline std::ostream &
operator<<(std::ostream &os, const AutoNL &val)
{
    return os << (val.data.find('\n') != std::string::npos ? "\n" : "")
              << val.data;
}

}

Finder::Finder(Environment &env, bool countOnly)
    : env(env), countOnly(countOnly)
{
    auto convert = [](const std::string &str) {
        if (str == "decl") {
            return MType::Declaration;
        } else if (str == "stmt") {
            return MType::Statement;
        } else if (str == "func") {
            return MType::Function;
        } else if (str == "call") {
            return MType::Call;
        } else if (str == "param") {
            return MType::Parameter;
        } else if (str == "comm") {
            return MType::Comment;
        } else if (str == "dir") {
            return MType::Directive;
        } else if (str == "block") {
            return MType::Block;
        } else {
            throw std::runtime_error("Unknown type: " + str);
        }
    };

    std::vector<std::string> mpatterns, gpatterns;
    enum { PATHS, MPATTERNS, GPATTERNS } stage = PATHS;
    for (const std::string &arg : env.getCommonArgs().pos) {
        switch (stage) {
            case PATHS:
                if (arg == ":") {
                    stage = MPATTERNS;
                } else if (arg == "::") {
                    stage = GPATTERNS;
                } else {
                    paths.push_back(arg);
                }
                break;
            case MPATTERNS:
                if (arg == ":") {
                    stage = GPATTERNS;
                } else {
                    mpatterns.push_back(arg);
                }
                break;
            case GPATTERNS:
                gpatterns.push_back(arg);
                break;
        }
    }

    if (paths.empty()) {
        paths.push_back(".");
    }
    if (mpatterns.empty() && gpatterns.empty()) {
        throw std::runtime_error("Expected at least one matcher of either "
                                 "type");
    }

    Matcher *last = nullptr;
    for (const std::string &mpattern : boost::adaptors::reverse(mpatterns)) {
        matchers.push_front(Matcher(convert(mpattern), last));
        last = &matchers.front();
    }

    grepper = Grepper(gpatterns);
}

Finder::~Finder() = default;

bool
Finder::search()
{
    bool found = Traverser(paths, env.getCommonArgs().lang, env.getConfig(),
                           [this](const std::string &path) {
                               return process(path);
                           }).search();
    report();
    return found;
}

bool
Finder::process(const std::string &path)
{
    static ColorScheme cs;

    cpp17::pmr::monolithic mr;
    if (optional_t<Tree> &&t = buildTreeFromFile(env, path, &mr)) {
        auto timer = env.getTimeKeeper().measure("looking: " + path);

        Tree tree = *t;
        Language &lang = *tree.getLanguage();
        const bool countOnly = this->countOnly;
        const bool noMatchers = matchers.empty();

        auto grepHandler = [&](const std::vector<Node *> &match) {
            if (!noMatchers || countOnly) {
                return;
            }

            Node fakeRoot;
            fakeRoot.children.assign(match.cbegin(), match.cend());

            const Node *node = match.front();
            std::cout << (cs[ColorGroup::Path] << path) << ':'
                      << (cs[ColorGroup::LineNoPart] << node->line) << ':'
                      << (cs[ColorGroup::ColNoPart] << node->col) << ": "
                      << AutoNL { TermHighlighter(fakeRoot, lang, true,
                                                  node->line).print() }
                      << '\n';
        };

        if (noMatchers) {
            return grepper.grep(tree.getRoot(), grepHandler);
        }

        auto matchHandler = [&](Node *node) {
            if (!grepper.grep(node, grepHandler) || countOnly) {
                return;
            }

            std::cout << (cs[ColorGroup::Path] << path) << ':'
                      << (cs[ColorGroup::LineNoPart] << node->line) << ':'
                      << (cs[ColorGroup::ColNoPart] << node->col) << ": "
                      << AutoNL { TermHighlighter(*node, lang, true,
                                                  node->line).print() }
                      << '\n';
        };

        return matchers.front().match(tree.getRoot(), lang, matchHandler);
    }
    return false;
}

void
Finder::report()
{
    if (!countOnly) {
        return;
    }

    int lastSeen = 1;
    int lastMatched = 1;
    bool first = true;

    auto div = [](int a, int b) {
        if (a == 0 || b == 0) {
            return 0.0f;
        }
        return static_cast<float>(a)/b;
    };

    for (Matcher &matcher : matchers) {
        std::cout << "--> " << matcher.getMType() << '\n';
        std::cout << "seen        = " << matcher.getSeen() << '\n';
        std::cout << "matched     = " << matcher.getMatched() << '\n';

        if (!first) {
            const int matched = matcher.getMatched();
            std::cout << "per seen    = " << div(matched, lastSeen) << '\n';
            std::cout << "per matched = " << div(matched, lastMatched) << '\n';
            // TODO: consider also printing min and max
            // TODO: add average absolute deviation and dispersion too?
        }

        lastSeen = matcher.getSeen();
        lastMatched = matcher.getMatched();
        first = false;
    }

    if (!grepper.empty()) {
        std::cout << "++> Token match\n";
        std::cout << "seen    = " << grepper.getSeen() << '\n';
        std::cout << "matched = " << grepper.getMatched() << '\n';
    }
}
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/zograscope

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/zograscope

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master