first commit

This commit is contained in:
Bob Polis 2020-11-19 16:02:02 +01:00
commit 8fecda2a81
10 changed files with 606 additions and 0 deletions

68
Makefile Normal file
View File

@ -0,0 +1,68 @@
include premake.make
BIN := $(shell basename $$(pwd))
MANSECTION := 1
MANPAGE := $(BIN).$(MANSECTION)
SRCS := $(notdir $(wildcard src/*.cpp))
OBJS := $(SRCS:.cpp=.o)
DEPS := $(SRCS:.cpp=.d)
BUILDDIR := build/intermediates/
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
CONFIGDIR ?= $(PREFIX)/etc
DATADIR ?= $(PREFIX)/share
MANDIR ?= $(DATADIR)/man/man
DOCDIR ?= $(DATADIR)/$(BIN)/doc
CXX ?= g++
RM := /bin/rm -rf
INSTALL := /usr/bin/install -c
CXXFLAGS := $(CXXFLAGS) -Wshadow -Wall -Wpedantic -Wextra -g -fno-strict-aliasing -std=c++17 -pthread
ifeq ($(DEBUG),1)
CXXFLAGS += -D DEBUG -O0
CONFIG := debug
else
CXXFLAGS += -D NDEBUG -O3
CONFIG := release
endif
OUTDIR := build/$(CONFIG)/
vpath %.cpp src
vpath %.d $(BUILDDIR)
vpath %.o $(BUILDDIR)
.PHONY: all clean install prebuild test
all: prebuild $(OUTDIR)$(BIN)
prebuild:
@mkdir -p $(BUILDDIR) $(OUTDIR)
$(OUTDIR)$(BIN): $(OBJS) $(DEPS)
$(CXX) $(addprefix $(BUILDDIR),$(OBJS)) $(LDFLAGS) $(LDLIBS) -o $(OUTDIR)$(BIN)
@ln -sf $(OUTDIR)$(BIN) $(BIN)
%.o: %.cpp %.d
$(CXX) $(CXXFLAGS) -MMD -MP -MT $@ -MF $*.d -c $<
@mv $@ $*.d $(BUILDDIR)
-include $(BUILDDIR)*.d
%.d: ;
test:
$(MAKE) -C tests && tests/tests
clean:
$(RM) build $(BIN)
$(MAKE) -C tests clean
install: $(OUTDIR)$(BIN)
$(INSTALL) -d $(BINDIR)
$(INSTALL) $(OUTDIR)$(BIN) $(BINDIR)
$(INSTALL) -d $(MANDIR)$(MANSECTION)
$(INSTALL) $(MANPAGE) $(MANDIR)$(MANSECTION)

57
autogram.1 Normal file
View File

@ -0,0 +1,57 @@
.Dd $Mdocdate$
.Dt autogram 1
.Os
.Sh NAME
.Nm autogram
.Nd one line about what it does
.\" .Sh LIBRARY
.\" For sections 2, 3, and 9 only.
.\" Not used in OpenBSD.
.Sh SYNOPSIS
.Nm
.Fl h | v
.Nm
.Op Ar
.Sh DESCRIPTION
The
.Nm
utility processes files ...
When no file arguments are given,
.Nm
will read from the standard input.
.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl h, \-help
Print help text and exit.
.It Fl v, \-version
Print version info and exit.
.El
.\" .Sh CONTEXT
.\" For section 9 functions only.
.\" .Sh IMPLEMENTATION NOTES
.\" Not used in OpenBSD.
.\" .Sh RETURN VALUES
.\" For sections 2, 3, and 9 function return values only.
.\" .Sh ENVIRONMENT
.\" For sections 1, 6, 7, and 8 only.
.\" .Sh FILES
.Sh EXIT STATUS
.\" For sections 1, 6, and 8 only.
.Nm
exits 0 on success, and 1 if an error occurs.
.\" .Sh EXAMPLES
.\" .Sh DIAGNOSTICS
.\" For sections 1, 4, 6, 7, 8, and 9 printf/stderr messages only.
.\" .Sh ERRORS
.\" For sections 2, 3, 4, and 9 errno settings only.
.\" .Sh SEE ALSO
.\" .Xr foobar 1
.\" .Sh STANDARDS
.\" .Sh HISTORY
.Sh AUTHORS
Bob Polis
.\" .Sh CAVEATS
.\" .Sh BUGS
.\" .Sh SECURITY CONSIDERATIONS
.\" Not used in OpenBSD.

BIN
numerals.db Normal file

Binary file not shown.

1
premake.make Normal file
View File

@ -0,0 +1 @@
LDLIBS := -lm -lpthread

284
src/engine.cpp Normal file
View File

@ -0,0 +1,284 @@
// robinsonizer engine by Bob Polis
// copyright (c) 1994-2019
// TODO Use correct plurals: adjust numeral file format to include this.
// TODO Don't assume 26-letter alphabet, but allow for arbitrary character list.
// TODO Switch to get_long_options in main.
// C++
#include <iostream>
#include <string>
#include <fstream>
#include <cstring>
using namespace std;
// libcommon
#include <libcommon.hpp>
// Project
#include "engine.hpp"
const int s_index = 's' - 'a';
engine::engine(std::string start,
int maxiter,
int maxseed,
std::string numerals_file,
std::string characters_file,
unsigned int vl,
robinsonizer_mode mode,
bool easy_parsing,
int engine_id) :
_start {start},
_maxiter {maxiter},
_verbosity_level {vl},
_mode {mode},
_easy_parsing {easy_parsing},
_engine_id {engine_id}
{
// read numerals for desired language from text file, init letter frequency table
{
ifstream file {numerals_file};
string line;
while (getline(file, line)) {
_numerals.push_back(line);
}
}
// setup random distribution
_dist.param(uniform_int_distribution<>::param_type {0, min<int>(abs(maxseed), _numerals.size())});
// now we know how many numerals we have, so we can allocate our efficient buffers
int n;
for (n = 0; n < _numerals.size(); ++n) {
vector<int> vec;
vec.resize(26, 0);
_frequencies.push_back(vec);
_freq.push_back(_frequencies.back().data());
}
_start_freq.resize(26, 0);
_old.resize(26, 0);
_new.resize(26, 0);
_used.resize(26, 0);
// init numeral letter frequency table
for (n = 0; n < _numerals.size(); n++) {
for (char c : _numerals[n]) {
if (c >= 'a' && c <= 'z') {
++_freq[n][c - 'a'];
}
}
}
if (_verbosity_level > 1) {
numeral_frequencies(cerr);
}
// get letter frequencies from sentence start
for (char c : start) {
// optionally translate upper- to lowercase
const char up_lo_dif = 'a' - 'A';
if (c >= 'A' && c <= 'Z') {
c += up_lo_dif;
}
// skip non-alphabetical chars
if (c >= 'a' && c <= 'z') {
++_start_freq[c - 'a'];
}
}
// add letters from 'and', reset 'and' vector
for (n = 0; n < 26; n++) {
_start_freq[n] += _freq[0][n];
_freq[0][n] = 0;
}
// build 'used' table for correct autogram seeding
for (n = 0; n < 26; ++n) {
_used[n] = _start_freq[n];
}
for (n = 0; n < _numerals.size(); ++n) {
for (int i = 0; i < 26; ++i) {
_used[i] += _freq[n][i];
}
}
}
void engine::run()
{
auto prev = _old.data();
auto next = _new.data();
auto freq = _freq.data();
auto start = _start_freq.data();
do {
// setup
int num_iter = 0;
unsigned int k, n;
// create random seed vector
switch (_mode) {
case robinsonizer_mode::pangram:
case robinsonizer_mode::strict_autogram:
for (n = 0; n < 26; n++) {
prev[n] = _dist(_random_engine);
}
break;
case robinsonizer_mode::lax_autogram:
for (n = 0; n < 26; ++n) {
if (_used[n]) { // only if letter occurs in numerals or sentence start
prev[n] = _dist(_random_engine);
} else {
prev[n] = 0;
}
}
break;
default:
break;
}
#if DEBUG
// logging, if desired
if (_verbosity_level > 1) {
frequencies(cerr, const_cast<const int*>(prev));
}
#endif
do {
// setup
num_iter++;
_total_iterations++;
memcpy(next, start, 26 * sizeof(int));
// count letters in resulting sentence by incrementing result freqmap elements
for (n = 0; n < 26; n++) {
if (static_cast<unsigned int>(prev[n]) < _numerals.size()) {
auto p = freq[prev[n]];
for (k = 0; k < 26; k++) {
next[k] += p[k];
}
} else {
char c = 'a' + n;
if (_easy_parsing) {
cout << "OVFL[" << _engine_id << "] " << c << " (" << prev[n] << ")" << endl;
} else {
cerr << endl << "overflow: " << c << " (" << prev[n] << ")";
}
break;
}
}
// increment frequency for 's' for every letter which occurs more than once,
// and increment the count for every letter which is (or should be) mentioned
for (n = 0; n < 26; n++) {
switch (_mode) {
case robinsonizer_mode::pangram:
++next[n];
break;
case robinsonizer_mode::strict_autogram:
if (next[n]) {
++next[n];
}
break;
case robinsonizer_mode::lax_autogram:
if (prev[n]) {
++next[n];
}
break;
default:
break;
}
if (next[n] > 1) {
++next[s_index];
}
}
#if DEBUG
// debug output, only if verbosity level is high enough
if (_verbosity_level > 1) {
write_result(cerr);
cerr << endl;
}
#endif
// test if our result equals the previous one (if so, we found a valid sentence)
_found = memcmp(next, prev, 26 * sizeof(int)) == 0;
if (_found) {
break;
}
if (num_iter == _maxiter) {
break;
} else {
memcpy(prev, next, 26 * sizeof(int));
}
} while (true);
} while (!_found);
}
void engine::frequencies(ostream& os, const int fm[]) const
{
bool output = false;
for (unsigned int n = 0; n < 26; n++) {
if (fm[n]) {
if (output) {
os << ", ";
}
char c = n + 'a';
os << c << " (" << fm[n] << ")";
output = true;
}
}
os << endl;
}
void engine::numeral_frequencies(ostream& os) const
{
for (unsigned int i = 0; i < _numerals.size(); i++) {
os << _numerals[i] << ": ";
frequencies(os, _freq[i]);
}
}
void engine::write_result(ostream& os) const
{
if (_easy_parsing) {
os << "RSLT[" << _engine_id << "] ";
}
os << _start << " ";
unsigned int n;
unsigned int first = 0;
unsigned int last = 25;
bool first_found = false;
for (n = 0; n < 26; n++) { // pre-scan
if (_new[n]) {
if (!first_found) {
first = n;
first_found = true;
}
last = n;
}
}
for (n = 0; n < 26; n++) {
if (_new[n]) {
if (n == last) {
os << " " << _numerals[0] << " ";
} else if (n > first) {
os << ", ";
}
char c = n + 'a';
os << _numerals[_new[n]] << " " << c;
if (_new[n] > 1) {
os << "'s";
}
}
}
os << ".";
}
ostream& operator<<(ostream& os, const engine& engine) {
engine.write_result(os);
return os;
}

56
src/engine.hpp Normal file
View File

@ -0,0 +1,56 @@
// robinsonizer engine by Bob Polis
// copyright (c) 1994-2019
#ifndef __engine__
#define __engine__
#include <string>
#include <vector>
#include <ostream>
#include <random>
#include "robinsonizer_mode.hpp"
class engine {
public:
engine(std::string start,
int maxiter,
int maxseed,
std::string numerals_file,
std::string characters_file,
unsigned int vl,
robinsonizer_mode mode,
bool easy_parsing,
int engine_id);
engine() = delete;
void run();
void write_result(std::ostream& os) const;
void frequencies(std::ostream& os, const int fm[]) const;
void numeral_frequencies(std::ostream& os) const;
unsigned long long total_iterations() const {return _total_iterations; }
bool found() const { return _found; }
protected:
std::string _start;
int _maxiter;
unsigned long long _total_iterations {0};
std::vector<std::string> _numerals;
std::vector<std::vector<int>> _frequencies; // for memory mgmt only
std::vector<int*> _freq;
std::vector<int> _start_freq;
std::vector<int> _old;
std::vector<int> _new;
std::vector<int> _used;
unsigned int _verbosity_level;
bool _found {false};
robinsonizer_mode _mode;
bool _easy_parsing;
int _engine_id;
std::default_random_engine _random_engine {std::random_device {}()};
std::uniform_int_distribution<int> _dist;
};
std::ostream& operator<<(std::ostream& os, const engine& engine);
#endif // __engine__

68
src/main.cpp Normal file
View File

@ -0,0 +1,68 @@
//
// main.cpp
// autogram
//
// Created by Bob Polis at 2020-11-19
// Copyright (c) 2020 SwiftCoder. All rights reserved.
//
#include <iostream>
#include <cstdlib>
#include <string>
#include <stdexcept>
#include <getopt.h>
void print_help() {
std::cout << "usage: autogram [-h|--version]\n";
std::cout << " -h, --help show this help text and exit\n";
std::cout << " --version show version number and exit\n";
}
void print_version() {
std::cout << "autogram version 1.0\n";
}
int main(int argc, const char * argv[]) {
try {
int opt_char, opt_val;
struct option long_options[] = {
{"help", no_argument, nullptr, 'h'},
{"version", no_argument, &opt_val, 1},
{nullptr, 0, nullptr, 0}
};
while ((opt_char = getopt_long(argc, const_cast<char* const *>(argv), "h", long_options, nullptr)) != -1) {
std::string arg {optarg ? optarg : ""};
switch (opt_char) {
case 0: {
// handle long-only options here
switch (opt_val) {
case 1:
print_version();
return EXIT_SUCCESS;
}
break;
}
case 'h':
print_help();
return EXIT_SUCCESS;
case '?':
throw std::runtime_error("unrecognized option");
}
}
if (optind == argc) {
// here when no file args
}
for (int i = optind; i < argc; ++i) {
try {
// process file argv[i]
} catch (const std::runtime_error& ex) {
std::cerr << "autogram: " << ex.what() << '\n';
}
}
std::cout << "hello, autogram\n";
} catch (const std::exception& ex) {
std::cerr << "autogram: " << ex.what() << '\n';
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

13
src/robinsonizer_mode.hpp Normal file
View File

@ -0,0 +1,13 @@
//
// robinsonizer_mode.hpp
// robinsonizer
//
// Created by Bob Polis at 2019-02-02
// Copyright (c) 2019 SwiftCoder. All rights reserved.
//
enum class robinsonizer_mode {
pangram,
lax_autogram,
strict_autogram
};

51
tests/Makefile Normal file
View File

@ -0,0 +1,51 @@
include ../premake.make
LDLIBS += -lboost_unit_test_framework
BIN := $(shell basename $$(pwd))
SRCS := $(notdir $(wildcard src/*.cpp))
SRCS += $(notdir $(filter-out ../src/main.cpp,$(wildcard ../src/*.cpp)))
OBJS := $(SRCS:.cpp=.o)
DEPS := $(SRCS:.cpp=.d)
BUILDDIR := build/intermediates/
CXX ?= g++
RM := /bin/rm -rf
INSTALL := /usr/bin/install -c
CXXFLAGS := $(CXXFLAGS) -Wshadow -Wall -Wpedantic -Wextra -g -fno-strict-aliasing -std=c++17 -pthread -I../src
ifeq ($(DEBUG),1)
CXXFLAGS += -D DEBUG -O0
CONFIG := debug
else
CXXFLAGS += -D NDEBUG -O3
CONFIG := release
endif
OUTDIR := build/$(CONFIG)/
vpath %.cpp src ../src
vpath %.d $(BUILDDIR)
vpath %.o $(BUILDDIR)
.PHONY: all clean prebuild
all: prebuild $(OUTDIR)$(BIN)
prebuild:
@mkdir -p $(BUILDDIR) $(OUTDIR)
$(OUTDIR)$(BIN): $(OBJS) $(DEPS)
$(CXX) $(addprefix $(BUILDDIR),$(OBJS)) $(LDFLAGS) $(LDLIBS) -o $(OUTDIR)$(BIN)
@ln -sf $(OUTDIR)$(BIN) $(BIN)
%.o: %.cpp %.d
$(CXX) $(CXXFLAGS) -MMD -MP -MT $@ -MF $*.d -c $<
@mv $@ $*.d $(BUILDDIR)
-include $(BUILDDIR)*.d
%.d: ;
clean:
$(RM) build $(BIN)

8
tests/src/main.cpp Normal file
View File

@ -0,0 +1,8 @@
#define BOOST_TEST_MODULE My Test
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
BOOST_AUTO_TEST_CASE(first_test)
{
BOOST_TEST(1 == 1);
}