<commit_before>// SA:MP Profiler plugin
//
// Copyright (c) 2011 Zeex
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iterator>
#include <list>
#include <map>
#include <string>

#include "amxnamefinder.h"
#include "amxprofiler.h"
#include "jump.h"
#include "plugin.h"

extern void *pAMXFunctions;

// Symbolic info, used for getting function names
static std::map<AMX*, AMX_DBG> debugInfo;

// List of scripts to be profiled, read from .cfg
static std::list<std::string> profiledScripts;

// Both x86 and x86-64 are Little Endian...
static void *AMXAPI DummyAmxAlign(void *v) { return v; }

static uint32_t amx_Exec_addr;
static unsigned char amx_Exec_code[5];

static int AMXAPI Exec(AMX *amx, cell *retval, int index) {
    memcpy(reinterpret_cast<void*>(::amx_Exec_addr), ::amx_Exec_code, 5);

    int error = AMX_ERR_NONE;

    // Check if this script has a profiler attached to it
    AmxProfiler *prof = AmxProfiler::Get(amx);
    if (prof != 0) {
        error =  prof->Exec(retval, index);
    } else {
        error = amx_Exec(amx, retval, index);
    }

    SetJump(reinterpret_cast<void*>(::amx_Exec_addr), (void*)::Exec, ::amx_Exec_code);

    return error;
}

PLUGIN_EXPORT unsigned int PLUGIN_CALL Supports() {
    return SUPPORTS_VERSION | SUPPORTS_AMX_NATIVES;
}

PLUGIN_EXPORT bool PLUGIN_CALL Load(void **ppData) {
	pAMXFunctions = ppData[PLUGIN_DATA_AMX_EXPORTS];

    // The server does not export amx_Align* for some reason.
    // They are used in amxdbg.c and amxaux.c, so they must be callable.
    ((void**)pAMXFunctions)[PLUGIN_AMX_EXPORT_Align16] = (void*)DummyAmxAlign; // amx_Align16
    ((void**)pAMXFunctions)[PLUGIN_AMX_EXPORT_Align32] = (void*)DummyAmxAlign; // amx_Align32
    ((void**)pAMXFunctions)[PLUGIN_AMX_EXPORT_Align64] = (void*)DummyAmxAlign; // amx_Align64

    // Hook amx_Exec
    ::amx_Exec_addr = reinterpret_cast<uint32_t>(((void**)pAMXFunctions)[PLUGIN_AMX_EXPORT_Exec]);
    SetJump(reinterpret_cast<void*>(::amx_Exec_addr), (void*)::Exec, ::amx_Exec_code);

    // Get the names of scripts to be profiled
    std::ifstream config("plugins/profiler.cfg");
    std::copy(std::istream_iterator<std::string>(config),
              std::istream_iterator<std::string>(),
              std::back_inserter(::profiledScripts));

    // Add SA:MP default directories to the .amx finder sarch path
    AmxNameFinder *nameFinder = AmxNameFinder::GetInstance();
    nameFinder->AddSearchDir("gamemodes");
    nameFinder->AddSearchDir("filterscripts");
    nameFinder->UpdateCache();

    return true;
}

PLUGIN_EXPORT void PLUGIN_CALL Unload() {
    return;
}

PLUGIN_EXPORT int PLUGIN_CALL AmxLoad(AMX *amx) {
	// Get the path to the loaded .amx
    AmxNameFinder *nameFinder = AmxNameFinder::GetInstance();
    nameFinder->UpdateCache();

    // If the name is established, load symbolic info
    std::string filename = nameFinder->GetAmxName(amx);
    if (!filename.empty()) {
        std::replace(filename.begin(), filename.end(), '\\', '/');
        if (std::find(::profiledScripts.begin(),
                      ::profiledScripts.end(), filename) != ::profiledScripts.end())
        {
            FILE *fp = fopen(filename.c_str(), "rb");
            if (fp != 0) {
                AMX_DBG amxdbg;
                int error = dbg_LoadInfo(&amxdbg, fp);
                if (error == AMX_ERR_NONE) {
                    AmxProfiler::Attach(amx, amxdbg);
                    ::debugInfo[amx] = amxdbg;
                    fclose(fp);
                    return AMX_ERR_NONE;
                }
                fclose(fp);
            }
        }
    }

    // No symbolic info loaded
    AmxProfiler::Attach(amx);
    return AMX_ERR_NONE;
}

PLUGIN_EXPORT int PLUGIN_CALL AmxUnload(AMX *amx) {
    // Get an instance of AmxProfiler attached to the unloading AMX
    AmxProfiler *prof = AmxProfiler::Get(amx);

    // Detach profiler
    if (prof != 0) {
        // Before doing that, print stats to <amx_file_path>.prof
        std::string name = AmxNameFinder::GetInstance()->GetAmxName(amx);
        if (!name.empty()) {
            prof->PrintStats(name + std::string(".prof"));
        }
        AmxProfiler::Detach(amx);
    }

    // Free debug info
    std::map<AMX*, AMX_DBG>::iterator it = ::debugInfo.find(amx);
    if (it != ::debugInfo.end()) {
        dbg_FreeInfo(&it->second);
        ::debugInfo.erase(it);
    }

    return AMX_ERR_NONE;
}
<commit_msg>Add WantsProfiler() function<commit_after>// SA:MP Profiler plugin
//
// Copyright (c) 2011 Zeex
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iterator>
#include <list>
#include <map>
#include <string>

#include "amxnamefinder.h"
#include "amxprofiler.h"
#include "jump.h"
#include "plugin.h"

extern void *pAMXFunctions;

// Symbolic info, used for getting function names
static std::map<AMX*, AMX_DBG> debugInfo;

// Both x86 and x86-64 are Little Endian...
static void *AMXAPI DummyAmxAlign(void *v) { return v; }

static uint32_t amx_Exec_addr;
static unsigned char amx_Exec_code[5];

static int AMXAPI Exec(AMX *amx, cell *retval, int index) {
    memcpy(reinterpret_cast<void*>(::amx_Exec_addr), ::amx_Exec_code, 5);

    int error = AMX_ERR_NONE;

    // Check if this script has a profiler attached to it
    AmxProfiler *prof = AmxProfiler::Get(amx);
    if (prof != 0) {
        error =  prof->Exec(retval, index);
    } else {
        error = amx_Exec(amx, retval, index);
    }

    SetJump(reinterpret_cast<void*>(::amx_Exec_addr), (void*)::Exec, ::amx_Exec_code);

    return error;
}

// Returns true if the .amx should be profiled
static bool WantsProfiler(const std::string &amxName) {
    std::ifstream config("plugins/profiler.cfg");

	std::istream_iterator<std::string> begin(config);
	std::istream_iterator<std::string> end;

	if (std::find(begin, end, amxName) != end) {
		return true;
	}

	return false;
}

PLUGIN_EXPORT unsigned int PLUGIN_CALL Supports() {
    return SUPPORTS_VERSION | SUPPORTS_AMX_NATIVES;
}

PLUGIN_EXPORT bool PLUGIN_CALL Load(void **ppData) {
	pAMXFunctions = ppData[PLUGIN_DATA_AMX_EXPORTS];

    // The server does not export amx_Align* for some reason.
    // They are used in amxdbg.c and amxaux.c, so they must be callable.
    ((void**)pAMXFunctions)[PLUGIN_AMX_EXPORT_Align16] = (void*)DummyAmxAlign; // amx_Align16
    ((void**)pAMXFunctions)[PLUGIN_AMX_EXPORT_Align32] = (void*)DummyAmxAlign; // amx_Align32
    ((void**)pAMXFunctions)[PLUGIN_AMX_EXPORT_Align64] = (void*)DummyAmxAlign; // amx_Align64

    // Hook amx_Exec
    ::amx_Exec_addr = reinterpret_cast<uint32_t>(((void**)pAMXFunctions)[PLUGIN_AMX_EXPORT_Exec]);
    SetJump(reinterpret_cast<void*>(::amx_Exec_addr), (void*)::Exec, ::amx_Exec_code);

    // Add SA:MP default directories to the .amx finder sarch path
    AmxNameFinder *nameFinder = AmxNameFinder::GetInstance();
    nameFinder->AddSearchDir("gamemodes");
    nameFinder->AddSearchDir("filterscripts");
    nameFinder->UpdateCache();

    return true;
}

PLUGIN_EXPORT void PLUGIN_CALL Unload() {
    return;
}

PLUGIN_EXPORT int PLUGIN_CALL AmxLoad(AMX *amx) {
	// Get the path to the loaded .amx
    AmxNameFinder *nameFinder = AmxNameFinder::GetInstance();
    nameFinder->UpdateCache();

    // If the name is established, load symbolic info
    std::string filename = nameFinder->GetAmxName(amx);
    if (!filename.empty()) {
        std::replace(filename.begin(), filename.end(), '\\', '/');
        if (WantsProfiler(filename)) {
            FILE *fp = fopen(filename.c_str(), "rb");
            if (fp != 0) {
                AMX_DBG amxdbg;
                int error = dbg_LoadInfo(&amxdbg, fp);
                if (error == AMX_ERR_NONE) {
                    AmxProfiler::Attach(amx, amxdbg);
                    ::debugInfo[amx] = amxdbg;
                    fclose(fp);
                    return AMX_ERR_NONE;
                }
                fclose(fp);
            }
        }
    }

    // No symbolic info loaded
    AmxProfiler::Attach(amx);
    return AMX_ERR_NONE;
}

PLUGIN_EXPORT int PLUGIN_CALL AmxUnload(AMX *amx) {
    // Get an instance of AmxProfiler attached to the unloading AMX
    AmxProfiler *prof = AmxProfiler::Get(amx);

    // Detach profiler
    if (prof != 0) {
        // Before doing that, print stats to <amx_file_path>.prof
        std::string name = AmxNameFinder::GetInstance()->GetAmxName(amx);
        if (!name.empty()) {
            prof->PrintStats(name + std::string(".prof"));
        }
        AmxProfiler::Detach(amx);
    }

    // Free debug info
    std::map<AMX*, AMX_DBG>::iterator it = ::debugInfo.find(amx);
    if (it != ::debugInfo.end()) {
        dbg_FreeInfo(&it->second);
        ::debugInfo.erase(it);
    }

    return AMX_ERR_NONE;
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2008-2009, Thomas Jaeger <ThJaeger@gmail.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include "win.h"
#include "actiondb.h"
#include "main.h"
#include <iomanip>
#include <glibmm/i18n.h>
#include <sys/time.h>

Stats::Stats() {
	Gtk::Button *button_matrix;
	widgets->get_widget("button_matrix", button_matrix);
	widgets->get_widget("treeview_recent", recent_view);
	widgets->get_widget("treeview_ranking", ranking_view);

	button_matrix->signal_clicked().connect(sigc::mem_fun(*this, &Stats::on_pdf));

	recent_store = Gtk::ListStore::create(cols);
	recent_view->set_model(recent_store);
	recent_view->append_column(_("Stroke"), cols.stroke);
	recent_view->append_column(_("Name"), cols.name);
	recent_view->append_column(_("Score"), cols.score);
	recent_view->signal_cursor_changed().connect(sigc::mem_fun(*this, &Stats::on_cursor_changed));

	ranking_view->set_model(Gtk::ListStore::create(cols));
	ranking_view->append_column(_("Stroke"), cols.stroke);
	if (verbosity >= 4)
		ranking_view->append_column("Debug", cols.debug);
	ranking_view->append_column(_("Name"), cols.name);
	ranking_view->append_column(_("Score"), cols.score);
}

void Stats::on_cursor_changed() {
	Gtk::TreePath path;
	Gtk::TreeViewColumn *col;
	recent_view->get_cursor(path, col);
	Gtk::TreeRow row(*recent_store->get_iter(path));

	Glib::RefPtr<Gtk::ListStore> ranking_store = row[cols.child];
	ranking_view->set_model(ranking_store);
}

class Feedback {
	boost::shared_ptr<Gtk::Window> icon;
	boost::shared_ptr<Gtk::Window> text;
public:
	Feedback(RStroke s, Glib::ustring t, int x, int y) {
		x += (prefs.left_handed.get() ? 1 : -1)*3*STROKE_SIZE / 2;
		int w,h;
		if (s) {
			icon.reset(new Gtk::Window(Gtk::WINDOW_POPUP));
			icon->set_type_hint(Gdk::WINDOW_TYPE_HINT_TOOLTIP);
			icon->get_style_context()->add_class(GTK_STYLE_CLASS_TOOLTIP);
			Glib::RefPtr<Gdk::Visual> visual = icon->get_screen()->get_rgba_visual();
			gtk_widget_set_visual((GtkWidget *)icon->gobj(), visual->gobj());

			WIDGET(Gtk::Image, image, s->draw(STROKE_SIZE));
			icon->set_accept_focus(false);
			icon->add(image);
			image.set_padding(2,2);
			image.show();
			icon->get_size(w,h);
			icon->move(x - w/2, y - h/2);
			y += h/2;
		}

		if (t != "") {
			text.reset(new Gtk::Window(Gtk::WINDOW_POPUP));
			text->set_type_hint(Gdk::WINDOW_TYPE_HINT_TOOLTIP);
			text->get_style_context()->add_class(GTK_STYLE_CLASS_TOOLTIP);
			Glib::RefPtr<Gdk::Visual> visual = text->get_screen()->get_rgba_visual();
			gtk_widget_set_visual((GtkWidget *)text->gobj(), visual->gobj());

			text->set_accept_focus(false);
			WIDGET(Gtk::Label, label, t);
			text->add(label);
			label.set_padding(4,4);
			label.show();
			text->get_size(w,h);
			text->move(x - w/2, y + h/2);
		}
		if (text) {
			text->show();
			text->get_window()->input_shape_combine_region(Cairo::Region::create(), 0, 0);
		}
		if (icon) {
			icon->show();
			icon->get_window()->input_shape_combine_region(Cairo::Region::create(), 0, 0);
		}
	}
};

void Ranking::queue_show(RRanking r, RTriple e) {
	r->x = (int)e->x;
	r->y = (int)e->y;
	Glib::signal_idle().connect(sigc::bind(sigc::ptr_fun(&Ranking::show), r));
}

bool delete_me(boost::shared_ptr<Feedback>) {
	return false;
}

bool Ranking::show(RRanking r) {
	if (prefs.tray_feedback.get())
		win->set_icon(r->stroke, !r->best_stroke);
	if (prefs.feedback.get() && r->best_stroke) {
		if (prefs.advanced_popups.get() || !(r->best_stroke->button || r->best_stroke->timeout)) {
			boost::shared_ptr<Feedback> popup(new Feedback(r->best_stroke, r->name, r->x, r->y));
			Glib::signal_timeout().connect(sigc::bind(sigc::ptr_fun(&delete_me), popup), 600);
		}
	}
	Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(*win->stats, &Stats::on_stroke), r), 200);
	return false;
}

Glib::ustring format_float(float x) {
	return Glib::ustring::format(std::fixed, std::setprecision(2), x);
}

Glib::RefPtr<Gdk::Pixbuf> Stroke::drawDebug(RStroke a, RStroke b, int size) {
	// TODO: This is copy'n'paste from win.cc
	Glib::RefPtr<Gdk::Pixbuf> pb = drawEmpty_(size);
	if (!a || !b || !a->stroke || !b->stroke)
		return pb;
	int w = size;
	int h = size;
	int stride = pb->get_rowstride();
	guint8 *row = pb->get_pixels();
	// This is all pretty messed up
	// http://www.archivum.info/gtkmm-list@gnome.org/2007-05/msg00112.html
	Cairo::RefPtr<Cairo::ImageSurface> surface = Cairo::ImageSurface::create(row, Cairo::FORMAT_ARGB32, w, h, stride);
	const Cairo::RefPtr<Cairo::Context> ctx = Cairo::Context::create(surface);

	for (unsigned int s = 0; s+1 < a->size(); s++)
		for (unsigned int t = 0; t+1 < b->size(); t++) {
			double col = 1.0 - stroke_angle_difference(a->stroke.get(), b->stroke.get(), s, t);
			ctx->set_source_rgba(col,col,col,1.0);
			ctx->rectangle(a->time(s)*size, (1.0-b->time(t+1))*size,
					(a->time(s+1)-a->time(s))*size, (b->time(t+1)-b->time(t))*size);
			ctx->fill();
		}
	int path_x[a->size() + b->size()];
	int path_y[a->size() + b->size()];
	stroke_compare(a->stroke.get(), b->stroke.get(), path_x, path_y);
	ctx->set_source_rgba(1,0,0,1);
	ctx->set_line_width(2);
	ctx->move_to(size, 0);
	for (int i = 0;; i++) {
		ctx->line_to(a->time(path_x[i])*size, (1.0-b->time(path_y[i]))*size);
		if (!path_x[i] && !path_y[i])
			break;
	}
	ctx->stroke();

	for (int i = 0; i < w; i++) {
		guint8 *px = row;
		for (int j = 0; j < h; j++) {
			guint8 a = px[3];
			guint8 r = px[2];
			guint8 g = px[1];
			guint8 b = px[0];
			if (a) {
				px[0] = ((((guint)r) << 8) - r) / a;
				px[1] = ((((guint)g) << 8) - g) / a;
				px[2] = ((((guint)b) << 8) - b) / a;
			}
			px += 4;
		}
		row += stride;
	}
	return pb;
}

bool Stats::on_stroke(RRanking r) {
	Gtk::TreeModel::Row row = *(recent_store->prepend());
	row[cols.stroke] = r->stroke->draw(STROKE_SIZE);
	row[cols.name] = r->name;
	row[cols.score] = format_float(r->score*100) + "%";
	Glib::RefPtr<Gtk::ListStore> ranking_store = Gtk::ListStore::create(cols);
	row[cols.child] = ranking_store;

	Gtk::TreePath path = recent_store->get_path(row);
	recent_view->scroll_to_row(path);

	Gtk::TreeModel::Children ch = recent_store->children();
	if (ch.size() > 8) {
		Gtk::TreeIter last = ch.end();
		last--;
		recent_store->erase(last);

	}

	for (std::multimap<double, std::pair<std::string, RStroke> >::iterator i = r->r.begin(); i != r->r.end(); i++) {
		Gtk::TreeModel::Row row2 = *(ranking_store->prepend());
		row2[cols.stroke] = i->second.second->draw(STROKE_SIZE);
		if (verbosity >= 4)
			row2[cols.debug] = Stroke::drawDebug(r->stroke, i->second.second, STROKE_SIZE);
		row2[cols.name] = i->second.first;
		row2[cols.score] = format_float(i->first * 100) + "%";
	}
	return false;
}

void Stats::on_pdf() {
	struct timeval tv1, tv2;
	if (verbosity >= 1)
		gettimeofday(&tv1, 0);
	const int S = 32;
	const int B = 1;
	std::list<RStroke> strokes;
	actions.get_root()->all_strokes(strokes);
	const int n = strokes.size();
	Cairo::RefPtr<Cairo::PdfSurface> surface = Cairo::PdfSurface::create("/tmp/strokes.pdf", (n+1)*S, (n+1)*S);
	const Cairo::RefPtr<Cairo::Context> ctx = Cairo::Context::create(surface);
	int k = 1;
	for (std::list<RStroke>::iterator i = strokes.begin(); i != strokes.end(); i++, k++) {
		(*i)->draw(surface, k*S+B, B, S-2*B, S-2*B);
		(*i)->draw(surface, B, k*S+B, S-2*B, S-2*B);

		ctx->set_source_rgba(0,0,0,1);
		ctx->set_line_width(1);
		ctx->move_to(k*S, B);
		ctx->line_to(k*S, (n+1)*S-B);
		ctx->move_to(B, k*S);
		ctx->line_to((n+1)*S-B, k*S);
		ctx->stroke();

		int l = 1;
		for (std::list<RStroke>::iterator j = strokes.begin(); j != strokes.end(); j++, l++) {
			double score;
		        int match = Stroke::compare(*i, *j, score);
			if (match < 0)
				continue;
			if (match) {
				ctx->save();
				ctx->set_source_rgba(0,0,1,score-0.6);
				ctx->rectangle(l*S, k*S, S, S);
				ctx->fill();
				ctx->restore();
			}
			Glib::ustring str = format_float(score);
			Cairo::TextExtents te;
			ctx->get_text_extents(str, te);
			ctx->move_to(l*S+S/2 - te.x_bearing - te.width/2, k*S+S/2 - te.y_bearing - te.height/2);
			ctx->show_text(str);
		}
	}
	if (verbosity >= 1) {
		gettimeofday(&tv2, 0);
		printf("creating table took %ld us\n", (tv2.tv_sec - tv1.tv_sec)*1000000 + tv2.tv_usec - tv1.tv_usec);
	}
	if (!fork()) {
		execlp("xdg-open", "xdg-open", "/tmp/strokes.pdf", NULL);
		exit(EXIT_FAILURE);
	}
}

<commit_msg>Do tooltips right<commit_after>/*
 * Copyright (c) 2008-2009, Thomas Jaeger <ThJaeger@gmail.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include "win.h"
#include "actiondb.h"
#include "main.h"
#include <iomanip>
#include <glibmm/i18n.h>
#include <sys/time.h>

Stats::Stats() {
	Gtk::Button *button_matrix;
	widgets->get_widget("button_matrix", button_matrix);
	widgets->get_widget("treeview_recent", recent_view);
	widgets->get_widget("treeview_ranking", ranking_view);

	button_matrix->signal_clicked().connect(sigc::mem_fun(*this, &Stats::on_pdf));

	recent_store = Gtk::ListStore::create(cols);
	recent_view->set_model(recent_store);
	recent_view->append_column(_("Stroke"), cols.stroke);
	recent_view->append_column(_("Name"), cols.name);
	recent_view->append_column(_("Score"), cols.score);
	recent_view->signal_cursor_changed().connect(sigc::mem_fun(*this, &Stats::on_cursor_changed));

	ranking_view->set_model(Gtk::ListStore::create(cols));
	ranking_view->append_column(_("Stroke"), cols.stroke);
	if (verbosity >= 4)
		ranking_view->append_column("Debug", cols.debug);
	ranking_view->append_column(_("Name"), cols.name);
	ranking_view->append_column(_("Score"), cols.score);
}

void Stats::on_cursor_changed() {
	Gtk::TreePath path;
	Gtk::TreeViewColumn *col;
	recent_view->get_cursor(path, col);
	Gtk::TreeRow row(*recent_store->get_iter(path));

	Glib::RefPtr<Gtk::ListStore> ranking_store = row[cols.child];
	ranking_view->set_model(ranking_store);
}

class Tooltip : public Gtk::Window {
public:
	Tooltip(Gtk::Widget &widget) : Gtk::Window(Gtk::WINDOW_POPUP) {
		Glib::RefPtr<Gdk::Visual> visual = get_screen()->get_rgba_visual();
		if (visual)
			gtk_widget_set_visual(GTK_WIDGET(gobj()), visual->gobj());
		set_type_hint(Gdk::WINDOW_TYPE_HINT_TOOLTIP);
		set_app_paintable();
		set_resizable(false);
		set_accept_focus(false);
		get_style_context()->add_class(GTK_STYLE_CLASS_TOOLTIP);
		signal_draw().connect(sigc::mem_fun(*this, &Tooltip::on_early_draw), false);
		add(widget);
		widget.show();
	}

private:
	bool on_early_draw(const Cairo::RefPtr<Cairo::Context> &cr) {
		if (is_composited()) {
			cr->save();
			cr->set_source_rgba(0, 0, 0, 0);
			cr->set_operator(Cairo::OPERATOR_SOURCE);
			cr->paint();
			cr->restore();
		}
		int w = get_allocated_width();
		int h = get_allocated_height();
		get_style_context()->render_background(cr, 0, 0, w, h);
		get_style_context()->render_frame(cr, 0, 0, w, h);
		return false;
	}
};

class Feedback {
	boost::shared_ptr<Gtk::Window> icon;
	boost::shared_ptr<Gtk::Window> text;
public:
	Feedback(RStroke s, Glib::ustring t, int x, int y) {
		x += (prefs.left_handed.get() ? 1 : -1)*3*STROKE_SIZE / 2;
		int w,h;
		if (s) {
			WIDGET(Gtk::Image, image, s->draw(STROKE_SIZE));
			image.set_padding(2,2);
			icon.reset(new Tooltip(image));
			icon->get_size(w,h);
			icon->move(x - w/2, y - h/2);
			y += h/2;
		}

		if (t != "") {
			WIDGET(Gtk::Label, label, t);
			label.set_padding(4,4);
			text.reset(new Tooltip(label));
			text->get_size(w,h);
			text->move(x - w/2, y + h/2);
		}
		if (text) {
			text->show();
			text->get_window()->input_shape_combine_region(Cairo::Region::create(), 0, 0);
		}
		if (icon) {
			icon->show();
			icon->get_window()->input_shape_combine_region(Cairo::Region::create(), 0, 0);
		}
	}
};

void Ranking::queue_show(RRanking r, RTriple e) {
	r->x = (int)e->x;
	r->y = (int)e->y;
	Glib::signal_idle().connect(sigc::bind(sigc::ptr_fun(&Ranking::show), r));
}

bool delete_me(boost::shared_ptr<Feedback>) {
	return false;
}

bool Ranking::show(RRanking r) {
	if (prefs.tray_feedback.get())
		win->set_icon(r->stroke, !r->best_stroke);
	if (prefs.feedback.get() && r->best_stroke) {
		if (prefs.advanced_popups.get() || !(r->best_stroke->button || r->best_stroke->timeout)) {
			boost::shared_ptr<Feedback> popup(new Feedback(r->best_stroke, r->name, r->x, r->y));
			Glib::signal_timeout().connect(sigc::bind(sigc::ptr_fun(&delete_me), popup), 600);
		}
	}
	Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(*win->stats, &Stats::on_stroke), r), 200);
	return false;
}

Glib::ustring format_float(float x) {
	return Glib::ustring::format(std::fixed, std::setprecision(2), x);
}

Glib::RefPtr<Gdk::Pixbuf> Stroke::drawDebug(RStroke a, RStroke b, int size) {
	// TODO: This is copy'n'paste from win.cc
	Glib::RefPtr<Gdk::Pixbuf> pb = drawEmpty_(size);
	if (!a || !b || !a->stroke || !b->stroke)
		return pb;
	int w = size;
	int h = size;
	int stride = pb->get_rowstride();
	guint8 *row = pb->get_pixels();
	// This is all pretty messed up
	// http://www.archivum.info/gtkmm-list@gnome.org/2007-05/msg00112.html
	Cairo::RefPtr<Cairo::ImageSurface> surface = Cairo::ImageSurface::create(row, Cairo::FORMAT_ARGB32, w, h, stride);
	const Cairo::RefPtr<Cairo::Context> ctx = Cairo::Context::create(surface);

	for (unsigned int s = 0; s+1 < a->size(); s++)
		for (unsigned int t = 0; t+1 < b->size(); t++) {
			double col = 1.0 - stroke_angle_difference(a->stroke.get(), b->stroke.get(), s, t);
			ctx->set_source_rgba(col,col,col,1.0);
			ctx->rectangle(a->time(s)*size, (1.0-b->time(t+1))*size,
					(a->time(s+1)-a->time(s))*size, (b->time(t+1)-b->time(t))*size);
			ctx->fill();
		}
	int path_x[a->size() + b->size()];
	int path_y[a->size() + b->size()];
	stroke_compare(a->stroke.get(), b->stroke.get(), path_x, path_y);
	ctx->set_source_rgba(1,0,0,1);
	ctx->set_line_width(2);
	ctx->move_to(size, 0);
	for (int i = 0;; i++) {
		ctx->line_to(a->time(path_x[i])*size, (1.0-b->time(path_y[i]))*size);
		if (!path_x[i] && !path_y[i])
			break;
	}
	ctx->stroke();

	for (int i = 0; i < w; i++) {
		guint8 *px = row;
		for (int j = 0; j < h; j++) {
			guint8 a = px[3];
			guint8 r = px[2];
			guint8 g = px[1];
			guint8 b = px[0];
			if (a) {
				px[0] = ((((guint)r) << 8) - r) / a;
				px[1] = ((((guint)g) << 8) - g) / a;
				px[2] = ((((guint)b) << 8) - b) / a;
			}
			px += 4;
		}
		row += stride;
	}
	return pb;
}

bool Stats::on_stroke(RRanking r) {
	Gtk::TreeModel::Row row = *(recent_store->prepend());
	row[cols.stroke] = r->stroke->draw(STROKE_SIZE);
	row[cols.name] = r->name;
	row[cols.score] = format_float(r->score*100) + "%";
	Glib::RefPtr<Gtk::ListStore> ranking_store = Gtk::ListStore::create(cols);
	row[cols.child] = ranking_store;

	Gtk::TreePath path = recent_store->get_path(row);
	recent_view->scroll_to_row(path);

	Gtk::TreeModel::Children ch = recent_store->children();
	if (ch.size() > 8) {
		Gtk::TreeIter last = ch.end();
		last--;
		recent_store->erase(last);

	}

	for (std::multimap<double, std::pair<std::string, RStroke> >::iterator i = r->r.begin(); i != r->r.end(); i++) {
		Gtk::TreeModel::Row row2 = *(ranking_store->prepend());
		row2[cols.stroke] = i->second.second->draw(STROKE_SIZE);
		if (verbosity >= 4)
			row2[cols.debug] = Stroke::drawDebug(r->stroke, i->second.second, STROKE_SIZE);
		row2[cols.name] = i->second.first;
		row2[cols.score] = format_float(i->first * 100) + "%";
	}
	return false;
}

void Stats::on_pdf() {
	struct timeval tv1, tv2;
	if (verbosity >= 1)
		gettimeofday(&tv1, 0);
	const int S = 32;
	const int B = 1;
	std::list<RStroke> strokes;
	actions.get_root()->all_strokes(strokes);
	const int n = strokes.size();
	Cairo::RefPtr<Cairo::PdfSurface> surface = Cairo::PdfSurface::create("/tmp/strokes.pdf", (n+1)*S, (n+1)*S);
	const Cairo::RefPtr<Cairo::Context> ctx = Cairo::Context::create(surface);
	int k = 1;
	for (std::list<RStroke>::iterator i = strokes.begin(); i != strokes.end(); i++, k++) {
		(*i)->draw(surface, k*S+B, B, S-2*B, S-2*B);
		(*i)->draw(surface, B, k*S+B, S-2*B, S-2*B);

		ctx->set_source_rgba(0,0,0,1);
		ctx->set_line_width(1);
		ctx->move_to(k*S, B);
		ctx->line_to(k*S, (n+1)*S-B);
		ctx->move_to(B, k*S);
		ctx->line_to((n+1)*S-B, k*S);
		ctx->stroke();

		int l = 1;
		for (std::list<RStroke>::iterator j = strokes.begin(); j != strokes.end(); j++, l++) {
			double score;
		        int match = Stroke::compare(*i, *j, score);
			if (match < 0)
				continue;
			if (match) {
				ctx->save();
				ctx->set_source_rgba(0,0,1,score-0.6);
				ctx->rectangle(l*S, k*S, S, S);
				ctx->fill();
				ctx->restore();
			}
			Glib::ustring str = format_float(score);
			Cairo::TextExtents te;
			ctx->get_text_extents(str, te);
			ctx->move_to(l*S+S/2 - te.x_bearing - te.width/2, k*S+S/2 - te.y_bearing - te.height/2);
			ctx->show_text(str);
		}
	}
	if (verbosity >= 1) {
		gettimeofday(&tv2, 0);
		printf("creating table took %ld us\n", (tv2.tv_sec - tv1.tv_sec)*1000000 + tv2.tv_usec - tv1.tv_usec);
	}
	if (!fork()) {
		execlp("xdg-open", "xdg-open", "/tmp/strokes.pdf", NULL);
		exit(EXIT_FAILURE);
	}
}

<|endoftext|>


<commit_before>#include <string>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <sstream>
#include "json11.hpp"
#include <cassert>
#include <list>
#include <set>
#include <unordered_map>

using namespace json11;
using std::string;

// Check that Json has the properties we want.
#include <type_traits>
#define CHECK_TRAIT(x) static_assert(std::x::value, #x)
CHECK_TRAIT(is_nothrow_constructible<Json>);
CHECK_TRAIT(is_nothrow_default_constructible<Json>);
CHECK_TRAIT(is_copy_constructible<Json>);
CHECK_TRAIT(is_nothrow_move_constructible<Json>);
CHECK_TRAIT(is_copy_assignable<Json>);
CHECK_TRAIT(is_nothrow_move_assignable<Json>);
CHECK_TRAIT(is_nothrow_destructible<Json>);

void parse_from_stdin() {
    string buf;
    string line;
    while (std::getline(std::cin, line)) {
        buf += line + "\n";
    }

    string err;
    auto json = Json::parse(buf, err);
    if (!err.empty()) {
        printf("Failed: %s\n", err.c_str());
    } else {
        printf("Result: %s\n", json.dump().c_str());
    }
}

int main(int argc, char **argv) {
    if (argc == 2 && argv[1] == string("--stdin")) {
        parse_from_stdin();
        return 0;
    }

    const string simple_test =
        R"({"k1":"v1", "k2":42, "k3":["a",123,true,false,null]})";

    string err;
    auto json = Json::parse(simple_test, err);

    std::cout << "k1: " << json["k1"].string_value() << "\n";
    std::cout << "k3: " << json["k3"].dump() << "\n";

    for (auto &k : json["k3"].array_items()) {
        std::cout << "    - " << k.dump() << "\n";
    }

    const string comment_test = R"({
      // comment /* with nested comment */
      "a": 1,
      // comment
      // continued
      "b": "text",
      /* multi
         line
         comment */
      // and single-line comment
      "c": [1, 2, 3]
    })";

    string err_comment;
    auto json_comment = Json::parse(
      comment_test, err_comment, /*detect_comments=*/ true);
    if (!err_comment.empty()) {
        printf("Failed: %s\n", err_comment.c_str());
    } else {
        printf("Result: %s\n", json_comment.dump().c_str());
    }

    string failing_comment_test = R"({
      /* bad comment
      "a": 1,
    })";

    string err_failing_comment;
    auto json_failing_comment = Json::parse(
      failing_comment_test, err_failing_comment, /*detect_comments=*/ true);
    if (!err_failing_comment.empty()) {
        printf("Failed: %s\n", err_failing_comment.c_str());
    } else {
        printf("Result: %s\n", json_failing_comment.dump().c_str());
    }

    failing_comment_test = R"({
      / / bad comment })";

    json_failing_comment = Json::parse(
      failing_comment_test, err_failing_comment, /*detect_comments=*/ true);
    if (!err_failing_comment.empty()) {
        printf("Failed: %s\n", err_failing_comment.c_str());
    } else {
        printf("Result: %s\n", json_failing_comment.dump().c_str());
    }

    failing_comment_test = R"({
          "a": 1,
        }/)";

    json_failing_comment = Json::parse(
      failing_comment_test, err_failing_comment, /*detect_comments=*/ true);
    if (!err_failing_comment.empty()) {
        printf("Failed: %s\n", err_failing_comment.c_str());
    } else {
        printf("Result: %s\n", json_failing_comment.dump().c_str());
    }

    std::list<int> l1 { 1, 2, 3 };
    std::vector<int> l2 { 1, 2, 3 };
    std::set<int> l3 { 1, 2, 3 };
    assert(Json(l1) == Json(l2));
    assert(Json(l2) == Json(l3));

    std::map<string, string> m1 { { "k1", "v1" }, { "k2", "v2" } };
    std::unordered_map<string, string> m2 { { "k1", "v1" }, { "k2", "v2" } };
    assert(Json(m1) == Json(m2));

    // Json literals
    Json obj = Json::object({
        { "k1", "v1" },
        { "k2", 42.0 },
        { "k3", Json::array({ "a", 123.0, true, false, nullptr }) },
    });

    std::cout << "obj: " << obj.dump() << "\n";

    assert(Json("a").number_value() == 0);
    assert(Json("a").string_value() == "a");
    assert(Json().number_value() == 0);

    assert(obj == json);
    assert(Json(42) == Json(42.0));
    assert(Json(42) != Json(42.1));

    const string unicode_escape_test =
        R"([ "blah\ud83d\udca9blah\ud83dblah\udca9blah\u0000blah\u1234" ])";

    const char utf8[] = "blah" "\xf0\x9f\x92\xa9" "blah" "\xed\xa0\xbd" "blah"
                        "\xed\xb2\xa9" "blah" "\0" "blah" "\xe1\x88\xb4";

    Json uni = Json::parse(unicode_escape_test, err);
    assert(uni[0].string_value().size() == (sizeof utf8) - 1);
    assert(std::memcmp(uni[0].string_value().data(), utf8, sizeof utf8) == 0);

    // Demonstrates the behavior change in Xcode 7 / Clang 3.7 described
    // here: https://llvm.org/bugs/show_bug.cgi?id=23812
    Json nested_array = Json::array { Json::array { 1, 2, 3 } };
    assert(nested_array.is_array());
    assert(nested_array.array_items().size() == 1);
    assert(nested_array.array_items()[0].is_array());
    assert(nested_array.array_items()[0].array_items().size() == 3);

    Json my_json = Json::object {
        { "key1", "value1" },
        { "key2", false },
        { "key3", Json::array { 1, 2, 3 } },
    };
    std::string json_str = my_json.dump();
    printf("%s\n", json_str.c_str());

    class Point {
    public:
        int x;
        int y;
        Point (int x, int y) : x(x), y(y) {}
        Json to_json() const { return Json::array { x, y }; }
    };

    std::vector<Point> points = { { 1, 2 }, { 10, 20 }, { 100, 200 } };
    std::string points_json = Json(points).dump();
    printf("%s\n", points_json.c_str());
}
<commit_msg>fix test where the trailing / was not reached due to a previous error<commit_after>#include <string>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <sstream>
#include "json11.hpp"
#include <cassert>
#include <list>
#include <set>
#include <unordered_map>

using namespace json11;
using std::string;

// Check that Json has the properties we want.
#include <type_traits>
#define CHECK_TRAIT(x) static_assert(std::x::value, #x)
CHECK_TRAIT(is_nothrow_constructible<Json>);
CHECK_TRAIT(is_nothrow_default_constructible<Json>);
CHECK_TRAIT(is_copy_constructible<Json>);
CHECK_TRAIT(is_nothrow_move_constructible<Json>);
CHECK_TRAIT(is_copy_assignable<Json>);
CHECK_TRAIT(is_nothrow_move_assignable<Json>);
CHECK_TRAIT(is_nothrow_destructible<Json>);

void parse_from_stdin() {
    string buf;
    string line;
    while (std::getline(std::cin, line)) {
        buf += line + "\n";
    }

    string err;
    auto json = Json::parse(buf, err);
    if (!err.empty()) {
        printf("Failed: %s\n", err.c_str());
    } else {
        printf("Result: %s\n", json.dump().c_str());
    }
}

int main(int argc, char **argv) {
    if (argc == 2 && argv[1] == string("--stdin")) {
        parse_from_stdin();
        return 0;
    }

    const string simple_test =
        R"({"k1":"v1", "k2":42, "k3":["a",123,true,false,null]})";

    string err;
    auto json = Json::parse(simple_test, err);

    std::cout << "k1: " << json["k1"].string_value() << "\n";
    std::cout << "k3: " << json["k3"].dump() << "\n";

    for (auto &k : json["k3"].array_items()) {
        std::cout << "    - " << k.dump() << "\n";
    }

    const string comment_test = R"({
      // comment /* with nested comment */
      "a": 1,
      // comment
      // continued
      "b": "text",
      /* multi
         line
         comment */
      // and single-line comment
      "c": [1, 2, 3]
    })";

    string err_comment;
    auto json_comment = Json::parse(
      comment_test, err_comment, /*detect_comments=*/ true);
    if (!err_comment.empty()) {
        printf("Failed: %s\n", err_comment.c_str());
    } else {
        printf("Result: %s\n", json_comment.dump().c_str());
    }

    string failing_comment_test = R"({
      /* bad comment
      "a": 1,
    })";

    string err_failing_comment;
    auto json_failing_comment = Json::parse(
      failing_comment_test, err_failing_comment, /*detect_comments=*/ true);
    if (!err_failing_comment.empty()) {
        printf("Failed: %s\n", err_failing_comment.c_str());
    } else {
        printf("Result: %s\n", json_failing_comment.dump().c_str());
    }

    failing_comment_test = R"({
      / / bad comment })";

    json_failing_comment = Json::parse(
      failing_comment_test, err_failing_comment, /*detect_comments=*/ true);
    if (!err_failing_comment.empty()) {
        printf("Failed: %s\n", err_failing_comment.c_str());
    } else {
        printf("Result: %s\n", json_failing_comment.dump().c_str());
    }

    failing_comment_test = R"({
          "a": 1
        }/)";

    json_failing_comment = Json::parse(
      failing_comment_test, err_failing_comment, /*detect_comments=*/ true);
    if (!err_failing_comment.empty()) {
        printf("Failed: %s\n", err_failing_comment.c_str());
    } else {
        printf("Result: %s\n", json_failing_comment.dump().c_str());
    }

    std::list<int> l1 { 1, 2, 3 };
    std::vector<int> l2 { 1, 2, 3 };
    std::set<int> l3 { 1, 2, 3 };
    assert(Json(l1) == Json(l2));
    assert(Json(l2) == Json(l3));

    std::map<string, string> m1 { { "k1", "v1" }, { "k2", "v2" } };
    std::unordered_map<string, string> m2 { { "k1", "v1" }, { "k2", "v2" } };
    assert(Json(m1) == Json(m2));

    // Json literals
    Json obj = Json::object({
        { "k1", "v1" },
        { "k2", 42.0 },
        { "k3", Json::array({ "a", 123.0, true, false, nullptr }) },
    });

    std::cout << "obj: " << obj.dump() << "\n";

    assert(Json("a").number_value() == 0);
    assert(Json("a").string_value() == "a");
    assert(Json().number_value() == 0);

    assert(obj == json);
    assert(Json(42) == Json(42.0));
    assert(Json(42) != Json(42.1));

    const string unicode_escape_test =
        R"([ "blah\ud83d\udca9blah\ud83dblah\udca9blah\u0000blah\u1234" ])";

    const char utf8[] = "blah" "\xf0\x9f\x92\xa9" "blah" "\xed\xa0\xbd" "blah"
                        "\xed\xb2\xa9" "blah" "\0" "blah" "\xe1\x88\xb4";

    Json uni = Json::parse(unicode_escape_test, err);
    assert(uni[0].string_value().size() == (sizeof utf8) - 1);
    assert(std::memcmp(uni[0].string_value().data(), utf8, sizeof utf8) == 0);

    // Demonstrates the behavior change in Xcode 7 / Clang 3.7 described
    // here: https://llvm.org/bugs/show_bug.cgi?id=23812
    Json nested_array = Json::array { Json::array { 1, 2, 3 } };
    assert(nested_array.is_array());
    assert(nested_array.array_items().size() == 1);
    assert(nested_array.array_items()[0].is_array());
    assert(nested_array.array_items()[0].array_items().size() == 3);

    Json my_json = Json::object {
        { "key1", "value1" },
        { "key2", false },
        { "key3", Json::array { 1, 2, 3 } },
    };
    std::string json_str = my_json.dump();
    printf("%s\n", json_str.c_str());

    class Point {
    public:
        int x;
        int y;
        Point (int x, int y) : x(x), y(y) {}
        Json to_json() const { return Json::array { x, y }; }
    };

    std::vector<Point> points = { { 1, 2 }, { 10, 20 }, { 100, 200 } };
    std::string points_json = Json(points).dump();
    printf("%s\n", points_json.c_str());
}
<|endoftext|>


<commit_before>#include <fstream>
#include <iostream>
#include <sstream>

#include "dictionary.h"

using namespace std;

bool pred(const std::string& a, const std::string& b) { return a > b; }

int main(int argc, char* argv[])
{
    // TODO : test new functions!

    /*
    if (argc < 2)
    {
        cout << "enter name of osgt file as argument\n";
        return 1;
    }

    string file1 = argv[1];

    ifstream fs;
    fs.open(file1.c_str());

    */

    Dictionary dictionary;
    vector<string> vec;

    vec.push_back("234");
    vec.push_back("nananana");
    vec.push_back("nanana");
    vec.push_back("nanana na");
    vec.push_back("nananana");
    vec.push_back("nanana");
    vec.push_back("nanana na");
    vec.push_back("nananana");
    vec.push_back("nanana");
    vec.push_back("nanana na");
    vec.push_back("aardvark");
    vec.push_back("ZZ Top");
    vec.push_back("123");

    dictionary.add(vec);

    Dictionary dict2;
    dict2.add("strawberry fields forever");
    dict2.add("Abba");

    dictionary = combine(dictionary, dict2);

    cout << "nananana appears " << dictionary.getVal("nananana") << " times\n";

    // vector<string> sorted = dictionary.getWords([](std::string a, std::string b) { return a > b; });
    vector<string> sorted = dictionary.asVector(pred);
    for (auto i : sorted)
    {
        cout << i << endl;
    }

    cout << endl << endl << endl;

    Dictionary::const_iterator it = dictionary.begin();

    while (it != dictionary.end()) cout << it++->first << endl;

}
<commit_msg>additional tests<commit_after>#include <fstream>
#include <iostream>
#include <sstream>

#include "dictionary.h"

using namespace std;

bool pred(const std::string& a, const std::string& b) { return a > b; }

int main(int argc, char* argv[])
{
    // TODO : test new functions!

    /*
    if (argc < 2)
    {
        cout << "enter name of osgt file as argument\n";
        return 1;
    }

    string file1 = argv[1];

    ifstream fs;
    fs.open(file1.c_str());

    */

    Dictionary dictionary;
    vector<string> vec;

    vec.push_back("234");
    vec.push_back("nananana");
    vec.push_back("nanana");
    vec.push_back("nanana na");
    vec.push_back("nananana");
    vec.push_back("nanana");
    vec.push_back("nanana na");
    vec.push_back("nananana");
    vec.push_back("nanana");
    vec.push_back("nanana na");
    vec.push_back("aardvark");
    vec.push_back("ZZ Top");
    vec.push_back("123");

    dictionary.add(vec);

    Dictionary dict2;
    dict2.add("strawberry fields forever");
    dict2.add("Abba");

    dictionary = combine(dictionary, dict2);

    cout << "nananana appears " << dictionary.getVal("nananana") << " times\n";

    // vector<string> sorted = dictionary.getWords([](std::string a, std::string b) { return a > b; });
    vector<string> sorted = dictionary.asVector(pred);
    for (auto i : sorted)
    {
        cout << i << endl;
    }

    cout << endl << endl << endl;

    Dictionary::const_iterator it = dictionary.begin();

    while (it != dictionary.end()) cout << it++->first << endl;

    cout << endl << endl;

    Dictionary dict3;
    dict3.add("nananana");
    dict3.add("here I am");
    dict3.add("rock you like a hurricane");

    Dictionary dict4 = exclusive(dictionary, dict3);

    for (auto i : dict4) cout << i.first << endl;

}
<|endoftext|>


<commit_before>#include "blackhole/record.hpp"

#include <thread>

#include <iostream>

namespace blackhole {

struct record_t::inner_t {
    std::reference_wrapper<const string_view> message;
    std::reference_wrapper<const string_view> formatted;

    int severity;
    time_point timestamp;

    pid_t pid;
    std::thread::id tid;

    std::reference_wrapper<const attribute_pack> attributes;
};

record_t::record_t(int severity, const string_view& message, const attribute_pack& attributes) {
    static_assert(sizeof(inner_t) <= sizeof(record_t), "padding or alignment violation");

    auto& inner = this->inner();
    inner.message = message;
    inner.formatted = message;

    inner.severity = severity;
    inner.timestamp = time_point();

    inner.pid = ::getpid();
    inner.tid = std::this_thread::get_id();

    inner.attributes = attributes;
}

auto record_t::message() const noexcept -> const string_view& {
    return inner().message.get();
}

auto record_t::severity() const noexcept -> int {
    return inner().severity;
}

auto record_t::timestamp() const noexcept -> time_point {
    return inner().timestamp;
}

auto record_t::pid() const noexcept -> std::uint64_t {
    return static_cast<std::uint64_t>(inner().pid);
}

auto record_t::tid() const noexcept -> std::thread::id {
    return inner().tid;
}

auto record_t::formatted() const noexcept -> const string_view& {
    return inner().formatted;
}

auto record_t::attributes() const noexcept -> const attribute_pack& {
    return inner().attributes.get();
}

auto record_t::activate(const string_view& formatted) noexcept -> void {
    if (formatted.data() != nullptr) {
        inner().formatted = formatted;
    }

    inner().timestamp = clock_type::now();
}

auto record_t::inner() noexcept -> inner_t& {
    return reinterpret_cast<inner_t&>(storage);
}

auto record_t::inner() const noexcept -> const inner_t& {
    return reinterpret_cast<const inner_t&>(storage);
}

}  // namespace blackhole
<commit_msg>chore(): add a note<commit_after>#include "blackhole/record.hpp"

#include <thread>

#include <iostream>

namespace blackhole {

struct record_t::inner_t {
    std::reference_wrapper<const string_view> message;
    std::reference_wrapper<const string_view> formatted;

    int severity;
    time_point timestamp;

    pid_t pid;
    std::thread::id tid;

    std::reference_wrapper<const attribute_pack> attributes;
};

record_t::record_t(int severity, const string_view& message, const attribute_pack& attributes) {
    static_assert(sizeof(inner_t) <= sizeof(record_t), "padding or alignment violation");

    auto& inner = this->inner();
    inner.message = message;
    inner.formatted = message;

    inner.severity = severity;
    inner.timestamp = time_point();

    inner.pid = ::getpid();
    inner.tid = std::this_thread::get_id();

    inner.attributes = attributes;
}

auto record_t::message() const noexcept -> const string_view& {
    return inner().message.get();
}

auto record_t::severity() const noexcept -> int {
    return inner().severity;
}

auto record_t::timestamp() const noexcept -> time_point {
    return inner().timestamp;
}

auto record_t::pid() const noexcept -> std::uint64_t {
    return static_cast<std::uint64_t>(inner().pid);
}

auto record_t::tid() const noexcept -> std::thread::id {
    return inner().tid;
}

auto record_t::formatted() const noexcept -> const string_view& {
    return inner().formatted;
}

auto record_t::attributes() const noexcept -> const attribute_pack& {
    return inner().attributes.get();
}

auto record_t::activate(const string_view& formatted) noexcept -> void {
    if (formatted.data() != nullptr) {
        inner().formatted = formatted;
    }

    // TODO: I can use coarse clock in linux.
    inner().timestamp = clock_type::now();
}

auto record_t::inner() noexcept -> inner_t& {
    return reinterpret_cast<inner_t&>(storage);
}

auto record_t::inner() const noexcept -> const inner_t& {
    return reinterpret_cast<const inner_t&>(storage);
}

}  // namespace blackhole
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2015 - 2022, Intel Corporation
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "config.h"

#include "record.hpp"

#include <map>
#include <utility>

#include "geopm/Exception.hpp"
#include "geopm_hint.h"

namespace geopm
{
    std::string event_name(int event_type)
    {
        static const std::map<int, std::string> event_names {
            {EVENT_REGION_ENTRY, "REGION_ENTRY"},
            {EVENT_REGION_EXIT, "REGION_EXIT"},
            {EVENT_EPOCH_COUNT, "EPOCH_COUNT"},
        };
        auto it = event_names.find(event_type);
        if (it == event_names.end()) {
            throw geopm::Exception("unsupported event type: " + std::to_string(event_type),
                                   GEOPM_ERROR_INVALID, __FILE__, __LINE__);
        }
        return it->second;
    }

    int event_type(const std::string &event_name)
    {
        static const std::map<std::string, int> event_types {
            {"REGION_ENTRY", EVENT_REGION_ENTRY},
            {"REGION_EXIT", EVENT_REGION_EXIT},
            {"EPOCH_COUNT", EVENT_EPOCH_COUNT},
        };
        auto it = event_types.find(event_name);
        if (it == event_types.end()) {
            throw geopm::Exception("invalid event type string: " + event_name,
                                   GEOPM_ERROR_INVALID, __FILE__, __LINE__);
        }
        return it->second;
    }

    std::string hint_name(uint64_t hint)
    {
        static const std::string hint_names[] = {
            "UNSET",
            "UNKNOWN",
            "COMPUTE",
            "MEMORY",
            "NETWORK",
            "IO",
            "SERIAL",
            "PARALLEL",
            "IGNORE",
            "INACTIVE",
        };
        static_assert(sizeof(hint_names) / sizeof(hint_names[0]) ==
                      GEOPM_SENTINEL_REGION_HINT);
        check_hint(hint);

        return hint_names[hint];
    }

    uint64_t hint_type(const std::string &hint_name)
    {
        static const std::pair<std::string, geopm_region_hint_e>
            hint_mapping[] = {
                std::make_pair("UNSET", GEOPM_REGION_HINT_UNSET),
                std::make_pair("UNKNOWN", GEOPM_REGION_HINT_UNKNOWN),
                std::make_pair("COMPUTE", GEOPM_REGION_HINT_COMPUTE),
                std::make_pair("MEMORY", GEOPM_REGION_HINT_MEMORY),
                std::make_pair("NETWORK", GEOPM_REGION_HINT_NETWORK),
                std::make_pair("IO", GEOPM_REGION_HINT_IO),
                std::make_pair("SERIAL", GEOPM_REGION_HINT_SERIAL),
                std::make_pair("PARALLEL", GEOPM_REGION_HINT_PARALLEL),
                std::make_pair("IGNORE", GEOPM_REGION_HINT_IGNORE),
                std::make_pair("INACTIVE", GEOPM_REGION_HINT_INACTIVE),
            };
        static constexpr unsigned int HINT_MAP_SIZE = sizeof(hint_mapping) /
                                                      sizeof(hint_mapping[0]);
        static_assert(HINT_MAP_SIZE == GEOPM_SENTINEL_REGION_HINT);
        static const std::map<std::string, uint64_t>
            result_map(hint_mapping, hint_mapping + HINT_MAP_SIZE);

        auto it = result_map.find(hint_name);
        if (it == result_map.end()) {
            throw Exception("hint_type():  Unknown hint name: " + hint_name + "\n",
                            GEOPM_ERROR_INVALID, __FILE__, __LINE__);
        }
        return it->second;
    }
}
<commit_msg>Add message to static_asserts in record.cpp<commit_after>/*
 * Copyright (c) 2015 - 2022, Intel Corporation
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "config.h"

#include "record.hpp"

#include <map>
#include <utility>

#include "geopm/Exception.hpp"
#include "geopm_hint.h"

namespace geopm
{
    std::string event_name(int event_type)
    {
        static const std::map<int, std::string> event_names {
            {EVENT_REGION_ENTRY, "REGION_ENTRY"},
            {EVENT_REGION_EXIT, "REGION_EXIT"},
            {EVENT_EPOCH_COUNT, "EPOCH_COUNT"},
        };
        auto it = event_names.find(event_type);
        if (it == event_names.end()) {
            throw geopm::Exception("unsupported event type: " + std::to_string(event_type),
                                   GEOPM_ERROR_INVALID, __FILE__, __LINE__);
        }
        return it->second;
    }

    int event_type(const std::string &event_name)
    {
        static const std::map<std::string, int> event_types {
            {"REGION_ENTRY", EVENT_REGION_ENTRY},
            {"REGION_EXIT", EVENT_REGION_EXIT},
            {"EPOCH_COUNT", EVENT_EPOCH_COUNT},
        };
        auto it = event_types.find(event_name);
        if (it == event_types.end()) {
            throw geopm::Exception("invalid event type string: " + event_name,
                                   GEOPM_ERROR_INVALID, __FILE__, __LINE__);
        }
        return it->second;
    }

    std::string hint_name(uint64_t hint)
    {
        static const std::string hint_names[] = {
            "UNSET",
            "UNKNOWN",
            "COMPUTE",
            "MEMORY",
            "NETWORK",
            "IO",
            "SERIAL",
            "PARALLEL",
            "IGNORE",
            "INACTIVE",
        };
        static_assert(sizeof(hint_names) / sizeof(hint_names[0]) ==
                      GEOPM_SENTINEL_REGION_HINT,
                      "hint_names mismatch with geopm_region_hint_e");
        check_hint(hint);

        return hint_names[hint];
    }

    uint64_t hint_type(const std::string &hint_name)
    {
        static const std::pair<std::string, geopm_region_hint_e>
            hint_mapping[] = {
                std::make_pair("UNSET", GEOPM_REGION_HINT_UNSET),
                std::make_pair("UNKNOWN", GEOPM_REGION_HINT_UNKNOWN),
                std::make_pair("COMPUTE", GEOPM_REGION_HINT_COMPUTE),
                std::make_pair("MEMORY", GEOPM_REGION_HINT_MEMORY),
                std::make_pair("NETWORK", GEOPM_REGION_HINT_NETWORK),
                std::make_pair("IO", GEOPM_REGION_HINT_IO),
                std::make_pair("SERIAL", GEOPM_REGION_HINT_SERIAL),
                std::make_pair("PARALLEL", GEOPM_REGION_HINT_PARALLEL),
                std::make_pair("IGNORE", GEOPM_REGION_HINT_IGNORE),
                std::make_pair("INACTIVE", GEOPM_REGION_HINT_INACTIVE),
            };
        static constexpr unsigned int HINT_MAP_SIZE = sizeof(hint_mapping) /
                                                      sizeof(hint_mapping[0]);
        static_assert(HINT_MAP_SIZE == GEOPM_SENTINEL_REGION_HINT,
                      "hint_mapping mismatch with geopm_region_hint_e");
        static const std::map<std::string, uint64_t>
            result_map(hint_mapping, hint_mapping + HINT_MAP_SIZE);

        auto it = result_map.find(hint_name);
        if (it == result_map.end()) {
            throw Exception("hint_type():  Unknown hint name: " + hint_name + "\n",
                            GEOPM_ERROR_INVALID, __FILE__, __LINE__);
        }
        return it->second;
    }
}
<|endoftext|>


<commit_before>#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include "rshell.h"

using namespace std;

//Function which finds all the connectors in a char string and puts
//them in a queue
queue<char> findConnectors(char cmdCharString[])
{
    queue<char> connectorQueue;

    //Create a pointer pointing to the first occurance of a connector
    char* connectorPointer = strpbrk(cmdCharString, "|;&#");
    //Create a bool for keeping track of doubles
    bool conDoubCatcher = false;
    bool hashCatcher = false;

    //Loop to find all connectors and put them in the queue
    while (connectorPointer != NULL && hashCatcher == false)
    {
        //Store the characters for each connector
        if (!conDoubCatcher)
        {
            //Push connector character into command queue
            if ((*connectorPointer == '&' || *connectorPointer == '|') &&
                (*connectorPointer == *(connectorPointer + 1)))
            {
                connectorQueue.push(*connectorPointer);
                conDoubCatcher = true;
            }
            //Do not store single & or | and throw error
            else if ((*connectorPointer == '&' || *connectorPointer == '|') &&
                (*connectorPointer != *(connectorPointer + 1)))
            {
                cout << "ERROR: Invalid connector: " << *connectorPointer
                    << endl;
                exit(1);
            }
            else if (*connectorPointer == '#' && *(connectorPointer - 1) == ' ')
            {
                connectorQueue.push(*connectorPointer);
                hashCatcher = true;
            }
            else if (*connectorPointer == ';')
                connectorQueue.push(*connectorPointer);

        }
        else
            conDoubCatcher = false;
        //Go to the next occurance of a connector character
        connectorPointer = strpbrk(connectorPointer + 1, "|;&#");
    }
    //return queue with all found connectors
    return connectorQueue;
}

//Read through input until first non whitespace char and if hash then return
//true else return false
bool firstCharHash(string cmdString)
{
    if (cmdString.find_first_not_of(" \t\r\n") != string::npos &&
        cmdString.at(cmdString.find_first_not_of(" \t\r\n")) == '#')
        return true;
    return false;
}

//Finds the commands and stores them in a queue for later use
queue<char*> findCommands(char cmdCharString[])
{
    queue<char*> commandQueue;

    //Create pointers for finding commands and connectors
    char* conPointer = strpbrk(cmdCharString, "|;&#");
    char* cmdCharPointer = NULL;

    //point at commands based on # placement
    if (conPointer != NULL && *conPointer == '#' && *(conPointer - 1) != ' ')
    {
        cmdCharPointer = strtok(cmdCharString, "|;&");
    }
    else
    {
        cmdCharPointer = strtok(cmdCharString, "|;&#");
    }

    //Go through command cString and find commands
    while (cmdCharPointer != NULL)
    {
        //Get rid of space in begining of command
        while (*cmdCharPointer == ' ')
            cmdCharPointer++;

        //Add the command to the queue
        commandQueue.push(cmdCharPointer);

        //Add commands based on # placement
        if (conPointer != NULL)
        {
            conPointer = strpbrk(conPointer + 1, "|;&#");

            if (conPointer != NULL)
            {
                if(*conPointer == '#' && *(conPointer - 1) != ' ')
                    cmdCharPointer = strtok(NULL, "|;&");
                else
                    cmdCharPointer = strtok(NULL, "|;&#");
            }
            else
                cmdCharPointer = strtok(NULL, "|;&#");
        }
        else
            cmdCharPointer = strtok(NULL, "|;&#");
    }

    return commandQueue;
}

//Checks for starting connector with no previous command
void checkForStartingConnectors(char cmdCharString[])
{
    char* cmdCharPointer = cmdCharString;
    while (*cmdCharPointer == ' ')
            cmdCharPointer++;

    if (cmdCharPointer != NULL && (cmdCharPointer + 1) != NULL &&
        ((*cmdCharPointer == '&' && *(cmdCharPointer + 1) == '&') ||
        (*cmdCharPointer == '|' && *(cmdCharPointer + 1) == '|')))
    {
        cout << "ERROR: syntax error near unexpected token \'"
            << *cmdCharPointer << *cmdCharPointer << "\'" << endl;
        exit(1);
    }
    if (cmdCharPointer != NULL && *cmdCharPointer == ';')
    {
        cout << "ERROR: syntax error near unexpected token \'"
            << *cmdCharPointer << "\'" << endl;
        exit(1);
    }
}

//Creates a queue with the seperate arguments for a command
queue<char*> seperateCommand(char command[])
{
    queue<char*> sepComQueue;

    char* cmdCharPointer = strtok(command, " ");
    while (cmdCharPointer != NULL)
    {
        sepComQueue.push(cmdCharPointer);
        cmdCharPointer = strtok(NULL, " ");
    }

    return sepComQueue;
}

//Fills the 2D array with all arguments for running the command
void fillArgsArray(char** args, queue<char*> sepComQueue)
{
    unsigned i;
    for (i = 0; !sepComQueue.empty(); i++)
    {
        args[i] = sepComQueue.front();
        sepComQueue.pop();
    }
    args[i] = NULL;
}

//Runs the command and returns true if success else return false
bool runCommand(char** args)
{
    //Create bool to store whether command execution was a success
    bool ynSuccess = true;

    pid_t pid;
    int status;

    //Run fork and get parent id
    //If an error while forking occurs then say so and exit
    if ((pid = fork()) < 0)
    {
        cout << "ERROR: forking child process failed" << endl;
        exit(1);
    }
    else if (pid == 0)
    {
        //Run the command
        if(execvp(*args, args) < 0)
            perror(NULL);
    }
    else
    {
        //Wait for completion
        while (wait(&status) != pid);

        //Tests if command executed properly and if not sets success to false
        if (WIFEXITED(status))
        {
            if (WEXITSTATUS(status) != 0)
            {
                ynSuccess = false;
            }
        }
        else
        {
            ynSuccess = false;
        }
    }

    return ynSuccess;
}

//Function to run the shell
void rshell()
{
    //Output Command prompt
    cout << "$ ";

    //Take in command and store it in a c-string
    string cmdString;
    getline(cin, cmdString);
    char* cmdCharString = new char[cmdString.size() + 1];
    strcpy(cmdCharString, cmdString.c_str());

    //For making testing with files better looking
    cout << endl;

    //Check for bad starting connectors
    checkForStartingConnectors(cmdCharString);

    //Catches specific case for Connector followed by # with no command
    //in between
    char* findHash = strpbrk(cmdCharString, "#");
    if (findHash != NULL && findHash != cmdCharString)
    {
        findHash--;
        while (*findHash == ' ' && findHash != cmdCharString)
            findHash--;
        if (*findHash == '&' || *findHash == '|')
        {
            cout << "ERROR: Command string ends in " << *findHash << *findHash
                << " and has no following command" << endl;
            exit(1);
        }
    }

    //Check for two connectors with no command inbetween
    char* findDoubleConnectors = strpbrk(cmdCharString, ";&|");
    while (findDoubleConnectors != NULL && findDoubleConnectors != cmdCharString)
    {
        bool skipChecking = false;
        char* temp = findDoubleConnectors;
        if(*temp == ';')
            temp--;
        else if((*temp == '|' || *temp == '&') && (*(temp - 1) == *temp))
            temp = temp - 2;
        else if((*temp == '|' || *temp == '&') && (*(temp - 1) != *temp))
            skipChecking = true;

        while (*temp == ' ' && temp != cmdCharString && !skipChecking)
            temp--;
        if ((*temp == '&' || *temp == '|') && !skipChecking)
        {
            cout << "Syntax error near unexpected token \'" << *temp << *temp
                << "\'" << endl;
            exit(1);
        }
        else if(*temp == ';' && !skipChecking)
        {
            cout << "Syntax error near unexpected token \'" << *temp << "\'"
                << endl;
            exit(1);
        }
        findDoubleConnectors = strpbrk(findDoubleConnectors + 1, ";&|");
    }

    //Create a queue filled with connectors in order using findConnectors()
    queue<char> connectorCharQueue = findConnectors(cmdCharString);

    //Create a queue filled with commands in order using findCommands()
    queue<char*> commandQueue = findCommands(cmdCharString);

    //Makes sure the string doesn't end in a '&&' or '||' connector
    if (commandQueue.size() <= connectorCharQueue.size()
        && (connectorCharQueue.back() == '|'
        || connectorCharQueue.back() == '&'))
    {
        cout << "ERROR: Command string ends in " << connectorCharQueue.back()
            << connectorCharQueue.back() << " and has no following command"
            << endl;
        exit(1);
    }

    //Create a queue of connector* based on the connectorCharQueue
    queue<connector*> connectorQueue;
    while (!connectorCharQueue.empty() && connectorCharQueue.front() != '#')
    {
        if (connectorCharQueue.front() == '&')
        {
            andConnector* p = new andConnector();
            connectorQueue.push(p);
        }
        else if (connectorCharQueue.front() == '|')
        {
            orConnector* p = new orConnector();
            connectorQueue.push(p);
        }
        else
        {
            semiColonConnector* p = new semiColonConnector();
            connectorQueue.push(p);
        }
        connectorCharQueue.pop();
    }

    //Create bool to store whether command execution was a success
    bool ynSuccess = true;

    //Create a bool for checking if the first non space character is a hash
    bool firstHash = firstCharHash(cmdString);

    //Run commands until we run out of commands to call
    //Don't run if first char is a hash
    while (!commandQueue.empty() && !firstHash)
    {
        //Creates a queue with the seperate arguments for a command
        queue<char*> sepComQueue = seperateCommand(commandQueue.front());

        //Take one out of the queue of commands which need to be run
        commandQueue.pop();

        //Create a 2D array and store all arguments for command
        unsigned argsSize = sepComQueue.size() + 1;
        char** args = new char*[argsSize];
        fillArgsArray(args, sepComQueue);

        //If command is exit then exit
        if(strcmp(*args, "exit") == 0)
            exit(0);

        //Run the command and store whether it was a success
        ynSuccess = runCommand(args);

        //Handles connectors
        if (!connectorQueue.empty())
        {
            while (!connectorQueue.empty() && !commandQueue.empty() &&
                !(connectorQueue.front()->isGoodOrNot(ynSuccess)))
            {
                commandQueue.pop();
                connector* p = connectorQueue.front();
                delete p;
                connectorQueue.pop();
            }

            connector* p = connectorQueue.front();
            delete p;
            connectorQueue.pop();

            //Special case to check for semi colon followed by # with no command
            //in between and at the end of command string
            if (connectorQueue.empty() && connectorCharQueue.front() == '#')
            {
                char* sCHashCatcherString = new char[cmdString.size() + 1];
                strcpy(sCHashCatcherString, commandQueue.front());
                char* semiColonHashCatcher = strtok(sCHashCatcherString, " ");
                if (semiColonHashCatcher == NULL)
                {
                    delete[] sCHashCatcherString;
                    exit(0);
                }
                delete[] sCHashCatcherString;
            }
        }
        else if (!connectorCharQueue.empty())
        {
            if (connectorCharQueue.front() == '#')
                exit(0);
        }

        delete[] args;
    }
    delete[] cmdCharString;
}<commit_msg>Turned a lot of code within rshell function into its own function which is then called by rshell. This makes the rshell function much easier to read and understand<commit_after>#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include "rshell.h"

using namespace std;

//Function which finds all the connectors in a char string and puts
//them in a queue
queue<char> findConnectors(char cmdCharString[])
{
    queue<char> connectorQueue;

    //Create a pointer pointing to the first occurance of a connector
    char* connectorPointer = strpbrk(cmdCharString, "|;&#");
    //Create a bool for keeping track of doubles
    bool conDoubCatcher = false;
    bool hashCatcher = false;

    //Loop to find all connectors and put them in the queue
    while (connectorPointer != NULL && hashCatcher == false)
    {
        //Store the characters for each connector
        if (!conDoubCatcher)
        {
            //Push connector character into command queue
            if ((*connectorPointer == '&' || *connectorPointer == '|') &&
                (*connectorPointer == *(connectorPointer + 1)))
            {
                connectorQueue.push(*connectorPointer);
                conDoubCatcher = true;
            }
            //Do not store single & or | and throw error
            else if ((*connectorPointer == '&' || *connectorPointer == '|') &&
                (*connectorPointer != *(connectorPointer + 1)))
            {
                cout << "ERROR: Invalid connector: " << *connectorPointer
                    << endl;
                exit(1);
            }
            else if (*connectorPointer == '#' && *(connectorPointer - 1) == ' ')
            {
                connectorQueue.push(*connectorPointer);
                hashCatcher = true;
            }
            else if (*connectorPointer == ';')
                connectorQueue.push(*connectorPointer);

        }
        else
            conDoubCatcher = false;
        //Go to the next occurance of a connector character
        connectorPointer = strpbrk(connectorPointer + 1, "|;&#");
    }
    //return queue with all found connectors
    return connectorQueue;
}

//Read through input until first non whitespace char and if hash then return
//true else return false
bool firstCharHash(string cmdString)
{
    if (cmdString.find_first_not_of(" \t\r\n") != string::npos &&
        cmdString.at(cmdString.find_first_not_of(" \t\r\n")) == '#')
        return true;
    return false;
}

//Finds the commands and stores them in a queue for later use
queue<char*> findCommands(char cmdCharString[])
{
    queue<char*> commandQueue;

    //Create pointers for finding commands and connectors
    char* conPointer = strpbrk(cmdCharString, "|;&#");
    char* cmdCharPointer = NULL;

    //point at commands based on # placement
    if (conPointer != NULL && *conPointer == '#' && *(conPointer - 1) != ' ')
    {
        cmdCharPointer = strtok(cmdCharString, "|;&");
    }
    else
    {
        cmdCharPointer = strtok(cmdCharString, "|;&#");
    }

    //Go through command cString and find commands
    while (cmdCharPointer != NULL)
    {
        //Get rid of space in begining of command
        while (*cmdCharPointer == ' ')
            cmdCharPointer++;

        //Add the command to the queue
        commandQueue.push(cmdCharPointer);

        //Add commands based on # placement
        if (conPointer != NULL)
        {
            conPointer = strpbrk(conPointer + 1, "|;&#");

            if (conPointer != NULL)
            {
                if(*conPointer == '#' && *(conPointer - 1) != ' ')
                    cmdCharPointer = strtok(NULL, "|;&");
                else
                    cmdCharPointer = strtok(NULL, "|;&#");
            }
            else
                cmdCharPointer = strtok(NULL, "|;&#");
        }
        else
            cmdCharPointer = strtok(NULL, "|;&#");
    }

    return commandQueue;
}

//Checks for starting connector with no previous command
void checkForStartingConnectors(char cmdCharString[])
{
    char* cmdCharPointer = cmdCharString;
    while (*cmdCharPointer == ' ')
            cmdCharPointer++;

    if (cmdCharPointer != NULL && (cmdCharPointer + 1) != NULL &&
        ((*cmdCharPointer == '&' && *(cmdCharPointer + 1) == '&') ||
        (*cmdCharPointer == '|' && *(cmdCharPointer + 1) == '|')))
    {
        cout << "ERROR: syntax error near unexpected token \'"
            << *cmdCharPointer << *cmdCharPointer << "\'" << endl;
        exit(1);
    }
    if (cmdCharPointer != NULL && *cmdCharPointer == ';')
    {
        cout << "ERROR: syntax error near unexpected token \'"
            << *cmdCharPointer << "\'" << endl;
        exit(1);
    }
}

//Catches specific case for Connector followed by # with no command
//in between
void checkForConnectorHash(char cmdCharString[])
{
    char* findHash = strpbrk(cmdCharString, "#");
    if (findHash != NULL && findHash != cmdCharString)
    {
        findHash--;
        while (*findHash == ' ' && findHash != cmdCharString)
            findHash--;
        if (*findHash == '&' || *findHash == '|')
        {
            cout << "ERROR: Command string ends in " << *findHash << *findHash
                << " and has no following command" << endl;
            exit(1);
        }
    }
}

//Check for two connectors with no command inbetween
void checkForTwoConnectorsNoSpace(char cmdCharString[])
{
    char* findDoubleConnectors = strpbrk(cmdCharString, ";&|");
    while (findDoubleConnectors != NULL &&
        findDoubleConnectors != cmdCharString)
    {
        bool skipChecking = false;
        char* temp = findDoubleConnectors;
        if(*temp == ';')
            temp--;
        else if((*temp == '|' || *temp == '&') && (*(temp - 1) == *temp))
            temp = temp - 2;
        else if((*temp == '|' || *temp == '&') && (*(temp - 1) != *temp))
            skipChecking = true;

        while (*temp == ' ' && temp != cmdCharString && !skipChecking)
            temp--;
        if ((*temp == '&' || *temp == '|') && !skipChecking)
        {
            cout << "Syntax error near unexpected token \'" << *temp << *temp
                << "\'" << endl;
            exit(1);
        }
        else if(*temp == ';' && !skipChecking)
        {
            cout << "Syntax error near unexpected token \'" << *temp << "\'"
                << endl;
            exit(1);
        }
        findDoubleConnectors = strpbrk(findDoubleConnectors + 1, ";&|");
    }
}

//Makes sure the string doesn't end in a '&&' or '||' connector
void checkIfEndsInBadConnector(const queue<char*> commandQueue,
    const queue<char> connectorCharQueue)
{
    if (commandQueue.size() <= connectorCharQueue.size()
        && (connectorCharQueue.back() == '|'
        || connectorCharQueue.back() == '&'))
    {
        cout << "ERROR: Command string ends in " << connectorCharQueue.back()
            << connectorCharQueue.back() << " and has no following command"
            << endl;
        exit(1);
    }
}

//Special case to check for semi colon followed by # with no command
//in between and at the end of command string
void checkForSemiHashEnd(const queue<connector*> connectorQueue,
    const queue<char> connectorCharQueue, const queue<char*> commandQueue,
    const string cmdString)
{
    if (connectorQueue.empty() && connectorCharQueue.front() == '#')
    {
        char* sCHashCatcherString = new char[cmdString.size() + 1];
        strcpy(sCHashCatcherString, commandQueue.front());
        char* semiColonHashCatcher = strtok(sCHashCatcherString, " ");
        if (semiColonHashCatcher == NULL)
        {
            delete[] sCHashCatcherString;
            exit(0);
        }
        delete[] sCHashCatcherString;
    }
}

//Create a queue of connector* based on the connectorCharQueue
queue<connector*> createConnectorQueue(queue<char> &connectorCharQueue)
{
    queue<connector*> connectorQueue;
    while (!connectorCharQueue.empty() && connectorCharQueue.front() != '#')
    {
        if (connectorCharQueue.front() == '&')
        {
            andConnector* p = new andConnector();
            connectorQueue.push(p);
        }
        else if (connectorCharQueue.front() == '|')
        {
            orConnector* p = new orConnector();
            connectorQueue.push(p);
        }
        else
        {
            semiColonConnector* p = new semiColonConnector();
            connectorQueue.push(p);
        }
        connectorCharQueue.pop();
    }

    return connectorQueue;
}

//Creates a queue with the seperate arguments for a command
queue<char*> seperateCommand(char command[])
{
    queue<char*> sepComQueue;

    char* cmdCharPointer = strtok(command, " ");
    while (cmdCharPointer != NULL)
    {
        sepComQueue.push(cmdCharPointer);
        cmdCharPointer = strtok(NULL, " ");
    }

    return sepComQueue;
}

//Fills the 2D array with all arguments for running the command
void fillArgsArray(char** args, queue<char*> sepComQueue)
{
    unsigned i;
    for (i = 0; !sepComQueue.empty(); i++)
    {
        args[i] = sepComQueue.front();
        sepComQueue.pop();
    }
    args[i] = NULL;
}

//Runs the command and returns true if success else return false
bool runCommand(char** args)
{
    //Create bool to store whether command execution was a success
    bool ynSuccess = true;

    pid_t pid;
    int status;

    //Run fork and get parent id
    //If an error while forking occurs then say so and exit
    if ((pid = fork()) < 0)
    {
        cout << "ERROR: forking child process failed" << endl;
        exit(1);
    }
    else if (pid == 0)
    {
        //Run the command
        if(execvp(*args, args) < 0)
            perror(NULL);
    }
    else
    {
        //Wait for completion
        while (wait(&status) != pid);

        //Tests if command executed properly and if not sets success to false
        if (WIFEXITED(status))
        {
            if (WEXITSTATUS(status) != 0)
            {
                ynSuccess = false;
            }
        }
        else
        {
            ynSuccess = false;
        }
    }

    return ynSuccess;
}

//Function to run the shell
void rshell()
{
    //Output Command prompt
    cout << "$ ";

    //Take in command and store it in a c-string
    string cmdString;
    getline(cin, cmdString);
    char* cmdCharString = new char[cmdString.size() + 1];
    strcpy(cmdCharString, cmdString.c_str());

    //For making testing with files better looking
    cout << endl;

    //Check for bad starting connectors
    checkForStartingConnectors(cmdCharString);

    //Catches specific case for Connector followed by # with no command
    //in between
    checkForConnectorHash(cmdCharString);

    //Check for two connectors with no command inbetween
    checkForTwoConnectorsNoSpace(cmdCharString);

    //Create a queue filled with connectors in order using findConnectors()
    queue<char> connectorCharQueue = findConnectors(cmdCharString);

    //Create a queue filled with commands in order using findCommands()
    queue<char*> commandQueue = findCommands(cmdCharString);

    //Makes sure the string doesn't end in a '&&' or '||' connector
    checkIfEndsInBadConnector(commandQueue, connectorCharQueue);

    //Create a queue of connector* based on the connectorCharQueue
    queue<connector*> connectorQueue = createConnectorQueue(connectorCharQueue);

    //Create bool to store whether command execution was a success
    bool ynSuccess = true;

    //Create a bool for checking if the first non space character is a hash
    bool firstHash = firstCharHash(cmdString);

    //Run commands until we run out of commands to call
    //Don't run if first char is a hash
    while (!commandQueue.empty() && !firstHash)
    {
        //Creates a queue with the seperate arguments for a command
        queue<char*> sepComQueue = seperateCommand(commandQueue.front());

        //Take one out of the queue of commands which need to be run
        commandQueue.pop();

        //Create a 2D array and store all arguments for command
        unsigned argsSize = sepComQueue.size() + 1;
        char** args = new char*[argsSize];
        fillArgsArray(args, sepComQueue);

        //If command is exit then exit
        if(strcmp(*args, "exit") == 0)
            exit(0);

        //Run the command and store whether it was a success
        ynSuccess = runCommand(args);

        //Handles connectors
        if (!connectorQueue.empty())
        {
            while (!connectorQueue.empty() && !commandQueue.empty() &&
                !(connectorQueue.front()->isGoodOrNot(ynSuccess)))
            {
                commandQueue.pop();
                connector* p = connectorQueue.front();
                delete p;
                connectorQueue.pop();
            }

            connector* p = connectorQueue.front();
            delete p;
            connectorQueue.pop();

            //Special case to check for semi colon followed by # with no command
            //in between and at the end of command string
            checkForSemiHashEnd(connectorQueue, connectorCharQueue,
                commandQueue, cmdString);
        }
        else if (!connectorCharQueue.empty())
        {
            //If it is the end of the connectorQueue and there is still a '#'
            //in the connectorCharQueue then the command string ends in a hash
            //and thus we exit(0) in order to stop executing things
            if (connectorCharQueue.front() == '#')
                exit(0);
        }

        delete[] args;
    }
    delete[] cmdCharString;
}<|endoftext|>


<commit_before>#ifndef SCANNER_H
#define SCANNER_H

#include <boost/optional.hpp>
#include <string>

enum TokenKind {
  TOK_SEMI,
  TOK_COMMA,
  TOK_HOOK,
  TOK_COLON,
  TOK_INC,
  TOK_DEC,
  TOK_DOT,
  TOK_TRIPLEDOT,
  TOK_LB,
  TOK_RB,
  TOK_LC,
  TOK_RC,
  TOK_LP,
  TOK_RP,
  TOK_NAME,
  TOK_NUMBER,
  TOK_STRING,
  TOK_TEMPLATE_HEAD,
  TOK_NO_SUBS_TEMPLATE,
  TOK_REGEXP,
  TOK_TRUE,
  TOK_FALSE,
  TOK_NULL,
  TOK_THIS,
  TOK_FUNCTION,
  TOK_IF,
  TOK_ELSE,
  TOK_SWITCH,
  TOK_CASE,
  TOK_DEFAULT,
  TOK_WHILE,
  TOK_DO,
  TOK_FOR,
  TOK_BREAK,
  TOK_CONTINUE,
  TOK_VAR,
  TOK_CONST,
  TOK_WITH,
  TOK_RETURN,
  TOK_NEW,
  TOK_DELETE,
  TOK_TRY,
  TOK_CATCH,
  TOK_FINALLY,
  TOK_THROW,
  TOK_DEBUGGER,
  TOK_YIELD,
  TOK_LET,
  TOK_EXPORT,
  TOK_IMPORT,
  TOK_RESERVED,
  TOK_STRICT_RESERVED,
  TOK_OR,
  TOK_AND,
  TOK_BITOR,
  TOK_BITXOR,
  TOK_BITAND,
  TOK_STRICTEQ,
  TOK_EQ,
  TOK_STRICTNE,
  TOK_LT,
  TOK_LE,
  TOK_GT,
  TOK_GE,
  TOK_INSTANCEOF,
  TOK_IN,
  TOK_LSH,
  TOK_RSH,
  TOK_URSH,
  TOK_ADD,
  TOK_SUB,
  TOK_MUL,
  TOK_DIV,
  TOK_MOD,
  TOK_TYPEOF,
  TOK_VOID,
  TOK_NOT,
  TOK_BITNOT,
  TOK_ARROW,
  TOK_ASSIGN,
  TOK_ADDASSIGN,
  TOK_SUBASSIGN,
  TOK_BITORASSIGN,
  TOK_BITXORASSIGN,
  TOK_BITANDASSIGN,
  TOK_LSHASSIGN,
  TOK_RSHASSIGN,
  TOK_URSHASSIGN,
  TOK_MULASSIGN,
  TOK_DIVASSIGN,
  TOK_MODASSIGN,
  TOK_LIMIT                      // domain size
};

template<typename T>
class CircularBuf {
  static const size_t bufSize = 4;
  T items[bufSize];
  int cursor;
public:

};

struct Pos {
  int begin;
  int end;
};

struct Token {
  TokenKind type;
  Pos pos;
  union {
  private:
    double number;
  } info;
};

class Buf {

};

enum FirstCharKind {
    OneChar_Min = 0,
    OneChar_Max = TOK_LIMIT - 1,

    Space = TOK_LIMIT,
    Ident,
    Dec,
    String,
    EOL,
    BasePrefix,
    Other
};

#define _______     Other
static const uint8_t firstCharKinds[] = {
/*         0        1        2        3        4        5        6        7        8        9    */
/*   0+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______,   Space,
/*  10+ */     EOL,   Space,   Space,     EOL, _______, _______, _______, _______, _______, _______,
/*  20+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
/*  30+ */ _______, _______,   Space, _______,  String, _______,   Ident, _______, _______,  String,
/*  40+ */  TOK_LP,  TOK_RP, _______, _______, TOK_COMMA,_______,  _______, _______,BasePrefix,  Dec,
/*  50+ */     Dec,     Dec,     Dec,     Dec,     Dec,     Dec,     Dec,    Dec,  TOK_COLON,TOK_SEMI,
/*  60+ */ _______, _______, _______,TOK_HOOK, _______,   Ident,   Ident,   Ident,   Ident,   Ident,
/*  70+ */   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,
/*  80+ */   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,
/*  90+ */   Ident,  TOK_LB, _______,  TOK_RB, _______,   Ident,   String,   Ident,   Ident,   Ident,
/* 100+ */   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,
/* 110+ */   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,
/* 120+ */   Ident,   Ident,   Ident,  TOK_LC, _______,  TOK_RC,TOK_BITNOT, _______
};
#undef _______

class Scanner {
  std::istream tokStream;

  inline bool isASCIIDecimal(char c) { return (unsigned)c - '0' <= 9; }

public:

  /*
    Spidermonkey's tokenize function has the following control flow structure:

    retry:
    switch (c) {
      case ...:
        goto badchar
      badchar:
      default:
    }
    out:
    error:
   */
  boost::optional<Token> getToken() {
    for (;;) {
      if (tokStream.eof()) {
	return boost::optional<Token>();
      }
      int c = tokStream.get();
      auto initialKind = FirstCharKind(firstCharKinds[c]);
      // Cases:
      // 1. Single character token
      if (initialKind <= OneChar_Max) {
	return boost::optional<Token>(Token());
      }
      // 2. Whitespace
      // 3. Identifier
      for (;;) {
	c = tokStream.get();

      }
      // 4. Decimal
      if (isASCIIDecimal(c)) {
	while (isASCIIDecimal(c)) {
	  c = tokStream.get();
	  if (c == '.') {
	    do {
	      c = tokStream.get();
	    } while (isASCIIDecimal(c));
	  }
	  if (c == 'e' || c == 'E') {
	    c = tokStream.get();
	    if (c == '+' || c == '-')
	      c = tokStream.get();
	    if (!isASCIIDecimal(c)) {
	      std::cerr << "error: malformed decimal" << std::endl;
	      return boost::optional<Token>();
	    }
	    do {
	      c = tokStream.get();
	    } while (isASCIIDecimal(c));
	  }
	}

      }
      // 5. String or Template String
      // 6. EOL
      // 7. Hex, octal, binary
      // 8. Operators
    }
  }
};

#endif /* SCANNER_H */
<commit_msg>triple dot<commit_after>#ifndef SCANNER_H
#define SCANNER_H

#include <boost/optional.hpp>
#include <string>

enum TokenKind {
  TOK_SEMI,
  TOK_COMMA,
  TOK_HOOK,
  TOK_COLON,
  TOK_INC,
  TOK_DEC,
  TOK_DOT,
  TOK_TRIPLEDOT,
  TOK_LB,
  TOK_RB,
  TOK_LC,
  TOK_RC,
  TOK_LP,
  TOK_RP,
  TOK_NAME,
  TOK_NUMBER,
  TOK_STRING,
  TOK_TEMPLATE_HEAD,
  TOK_NO_SUBS_TEMPLATE,
  TOK_REGEXP,
  TOK_TRUE,
  TOK_FALSE,
  TOK_NULL,
  TOK_THIS,
  TOK_FUNCTION,
  TOK_IF,
  TOK_ELSE,
  TOK_SWITCH,
  TOK_CASE,
  TOK_DEFAULT,
  TOK_WHILE,
  TOK_DO,
  TOK_FOR,
  TOK_BREAK,
  TOK_CONTINUE,
  TOK_VAR,
  TOK_CONST,
  TOK_WITH,
  TOK_RETURN,
  TOK_NEW,
  TOK_DELETE,
  TOK_TRY,
  TOK_CATCH,
  TOK_FINALLY,
  TOK_THROW,
  TOK_DEBUGGER,
  TOK_YIELD,
  TOK_LET,
  TOK_EXPORT,
  TOK_IMPORT,
  TOK_RESERVED,
  TOK_STRICT_RESERVED,
  TOK_OR,
  TOK_AND,
  TOK_BITOR,
  TOK_BITXOR,
  TOK_BITAND,
  TOK_STRICTEQ,
  TOK_EQ,
  TOK_STRICTNE,
  TOK_LT,
  TOK_LE,
  TOK_GT,
  TOK_GE,
  TOK_INSTANCEOF,
  TOK_IN,
  TOK_LSH,
  TOK_RSH,
  TOK_URSH,
  TOK_ADD,
  TOK_SUB,
  TOK_MUL,
  TOK_DIV,
  TOK_MOD,
  TOK_TYPEOF,
  TOK_VOID,
  TOK_NOT,
  TOK_BITNOT,
  TOK_ARROW,
  TOK_ASSIGN,
  TOK_ADDASSIGN,
  TOK_SUBASSIGN,
  TOK_BITORASSIGN,
  TOK_BITXORASSIGN,
  TOK_BITANDASSIGN,
  TOK_LSHASSIGN,
  TOK_RSHASSIGN,
  TOK_URSHASSIGN,
  TOK_MULASSIGN,
  TOK_DIVASSIGN,
  TOK_MODASSIGN,
  TOK_LIMIT                      // domain size
};

template<typename T>
class CircularBuf {
  static const size_t bufSize = 4;
  T items[bufSize];
  int cursor;
public:

};

struct Pos {
  int begin;
  int end;
};

struct Token {
  TokenKind type;
  Pos pos;
  union {
  private:
    double number;
  } info;
};

class Buf {

};

enum FirstCharKind {
    OneChar_Min = 0,
    OneChar_Max = TOK_LIMIT - 1,

    Space = TOK_LIMIT,
    Ident,
    Dec,
    String,
    EOL,
    BasePrefix,
    Other
};

#define _______     Other
static const uint8_t firstCharKinds[] = {
/*         0        1        2        3        4        5        6        7        8        9    */
/*   0+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______,   Space,
/*  10+ */     EOL,   Space,   Space,     EOL, _______, _______, _______, _______, _______, _______,
/*  20+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
/*  30+ */ _______, _______,   Space, _______,  String, _______,   Ident, _______, _______,  String,
/*  40+ */  TOK_LP,  TOK_RP, _______, _______, TOK_COMMA,_______,  _______, _______,BasePrefix,  Dec,
/*  50+ */     Dec,     Dec,     Dec,     Dec,     Dec,     Dec,     Dec,    Dec,  TOK_COLON,TOK_SEMI,
/*  60+ */ _______, _______, _______,TOK_HOOK, _______,   Ident,   Ident,   Ident,   Ident,   Ident,
/*  70+ */   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,
/*  80+ */   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,
/*  90+ */   Ident,  TOK_LB, _______,  TOK_RB, _______,   Ident,   String,   Ident,   Ident,   Ident,
/* 100+ */   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,
/* 110+ */   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,   Ident,
/* 120+ */   Ident,   Ident,   Ident,  TOK_LC, _______,  TOK_RC,TOK_BITNOT, _______
};
#undef _______

class Scanner {
  std::istream tokStream;

  inline bool isASCIIDecimal(char c) { return (unsigned)c - '0' <= 9; }

public:

  /*
    Spidermonkey's tokenize function has the following control flow structure:

    retry:
    switch (c) {
      case ...:
        goto badchar
      badchar:
      default:
    }
    out:
    error:
   */
  boost::optional<Token> getToken() {
    for (;;) {
      Token token;

      if (tokStream.eof()) {
	return boost::optional<Token>();
      }
      int c = tokStream.get();
      auto initialKind = FirstCharKind(firstCharKinds[c]);
      // Cases:
      // 1. Single character token
      if (initialKind <= OneChar_Max) {
	return boost::optional<Token>(Token());
      }
      // 2. Whitespace
      // 3. Identifier
      for (;;) {
	c = tokStream.get();

      }
      // 4. Decimal
      if (isASCIIDecimal(c)) {
	while (isASCIIDecimal(c)) {
	  c = tokStream.get();
	  if (c == '.') {
	    do {
	      c = tokStream.get();
	    } while (isASCIIDecimal(c));
	  }
	  if (c == 'e' || c == 'E') {
	    c = tokStream.get();
	    if (c == '+' || c == '-')
	      c = tokStream.get();
	    if (!isASCIIDecimal(c)) {
	      std::cerr << "error: malformed decimal" << std::endl;
	      return boost::optional<Token>();
	    }
	    do {
	      c = tokStream.get();
	    } while (isASCIIDecimal(c));
	  }
	}

      }
      // 5. String or Template String
      // 6. EOL
      // 7. Hex, octal, binary
      // 8. Operators
      switch (c) {
      case '.':
	c = tokStream.get();
	if (c == '.' && tokStream.get() == '.') {
	  token.type = TOK_TRIPLEDOT;
	  return boost::optional<Token>(token);
	}
      }
    }
  }
};

#endif /* SCANNER_H */
<|endoftext|>