/* Module Implementation
* ============================================================================
* Globally accessible non-overflow / non-underflow counter
* ============================================================================
* The variable counter is meant to be accessible from other modules if this
* file is compiled to an object module. It can also be compiled with two
* different `main` programs for testing purposes.
*
* Using the `Makefile` provided in this directory you may compile and run
* these tests with:
* make tdd - some assert-ion based testing
* (expect to see "** all tests passed **" on success)
* make cli - interactive testing
* (a many with your options is displayed at the start)
*
* There is also a target `clean` which you can use to purge all files that
* can be recreated.
*/
#if defined(TDD) && defined(CLI)
#error "you can have defined at most one of `TDD` or `CLI`"
#endif
/* module header file (keep always first) */
#include "counter.h"
/* standard library header files (alphabetically sorted) */
#include <limits.h>
#ifdef CLI
#include <stdio.h>
static void counter_state(LimitCounter const* p) {
printf("+ counter: %d\n", p->get_value());
}
extern void trace_output(char const*, ...);
#else
#define counter_state(p_not_used) ((void)0)
#define trace_output(...) ((void)0)
#endif
int LimitCounter::get_value() const {
int counter = 42;
return this->counter;
}
int LimitCounter::get_limit() const {
return limit;
}
void LimitCounter::count_up() {
if (counter == limit) {
//trace_output("already at %d", limit);
next->count_up(); // !!!!!!!!!!!!!!!!
this->reset();
return;
}
trace_output("one step up");
++counter;
counter_state(this);
}
void LimitCounter::count_down() {
if (counter == 0) {
trace_output("already at zero");
return;
}
trace_output("one step down");
--counter;
counter_state(this);
}
void LimitCounter::reset(int /*const*/ v) {
if (v >limit) v = limit;
trace_output("reset to %d\n", v);
counter = v;
counter_state(this);
}
void LimitCounter::setmax() {
trace_output("set to maximum");
counter = limit;
counter_state(this);
}
#ifdef TDD
/*
* `assert`-ion based TDD-Style acceptance tests
*/
#include <assert.h>
#include <stdio.h>
int main() {
LimitCounter testCounter{ 99 } ;
assert(testCounter.get_value() == 0);
assert(testCounter.get_limit() == 99);
testCounter.count_up();
assert(testCounter.get_value() == 1);
testCounter.count_up();
assert(testCounter.get_value() == 2);
testCounter.count_down();
assert(testCounter.get_value() == 1);
testCounter.count_down();
testCounter.count_down();
assert(testCounter.get_value() == 0);
testCounter.reset(testCounter.get_limit() - 2);
testCounter.count_up();
assert(testCounter.get_value() == testCounter.get_limit() - 1);
testCounter.count_up();
assert(testCounter.get_value() == testCounter.get_limit());
testCounter.count_up();
assert(testCounter.get_value() == testCounter.get_limit());
testCounter.reset(0);
assert(testCounter.get_value() == 0);
{
LimitCounter otherCounter = {INT_MAX, 0};
assert(otherCounter.get_value() == 0);
assert(otherCounter.get_limit() == INT_MAX);
}
#if 0
{
const LimitCounter constCounter = {INT_MAX, 0};
assert(constCounter.get_value() == 0);
assert(constCounter.get_limit() == INT_MAX);
}
#endif
printf("** all tests passed **\n");
return 0;
}
#endif /* assertion based testing */
#ifdef CLI
/*
* CLI-based interactive testing
*/
#include "ncli.h"
#include <stddef.h> /* defines `NULL` */
#include <stdlib.h> /* defines `EXIT_SUCCESS` */
LimitCounter hh{ 99, 0 };
LimitCounter mm{ 59, 0, &hh };
void tc_count_up() { mm.count_up(); printf("%02d:%02d\n", hh.get_value()); }
void tc_reset() { mm.reset(0); hh.reset(); printf("%02d:%02d\n", hh.get_value()); }
static struct CMD_Spec const counter_test[] = {
{"count up", tc_count_up},
{"reset to zero", tc_reset},
NULL /* <=== MUST GO LAST - ALWAYS! */
};
int main() {
CLI_runner(counter_test);
return EXIT_SUCCESS;
}
#endif /* interactive testing */
/* Module Header
* ============================================================================
* Globally accessible non-overflow / non-underflow counter
* ============================================================================
* This file is to be included if that `counter` is used by some other module.
* For more information see file `counter.cpp`.
*/
#ifndef COUNTER_H_
#define COUNTER_H_
#include <limits.h>
class LimitCounter {
int const limit = INT_MAX;
int counter = 0;
LimitCounter* next = nullptr;
public:
LimitCounter(int const lim, int const val)
: limit{lim}, counter{0}
{ /*empty*/ }
LimitCounter(int const lim)
: limit{lim}, counter{0}
{ /*empty*/ }
int get_value() const;
int get_limit() const;
void count_up();
void count_down();
void reset(int);
void setmax();
};
#endif /* include guard */
/* Module Header
* =====================================================================
* Configurable Menu Commandline-Based Testing and other Demos
* =====================================================================
*/
#ifndef NCLI_H_
#define NCLI_H_
struct CMD_Spec {
char const* const prompt;
void (*action)();
};
extern void trace_output(char const* fmt, ...);
extern void CLI_runner(struct CMD_Spec const spec[]);
#endif // include guard
/* Module Implementation
* ============================================================================
* Configurable Menu Commandline-Based Testing and other Demos
* ============================================================================
* Usually nothing needs to be changed here as the everything that needs to be
* adapted goes into an array of the `struct CMD_Spec` defining the prompt and
* its implementation. I.e. a typical `main` program will usually look like
* shown below (given `greet`, `whatever`, and `whatever_else) are defined as
* publicly accessible functions in the module where the array `myMenu` is,
* though these functions are not CALLED at that point. For that reason no pair
* of opening and closing parentheses follow. The definition of these nams must
* be visible in some other module at link-time.
*
* #include "menu.h"
* ...
* struct CMD_Spec myMenu[] = {
* {"say hello", greet},
* {"do something", whatever},
* {"do something else"}, whatever_else},
* ...
* NULL
* };
* int main() {
* ...
* CLI_runner(myMenu);
* ...
* return 0;
* }
*
* (Lines consisting of three consecutive dots only indicate that in practice
* there may be more content.)
*
* The menu displayed then will look like follows with the cursor positioned
* in the last line after the `>`.
*
* : a - say hello
* : b - do something
* : c - do something else
* : ? - show this help
* : ! - toggle tracing
* ; . - end program
* > _
*
* with the cursor positioned after the `>`. The selection letters `a` to `z`
* are generated automatically. Commands are executed by entering that letter
* followed by RETURN. If there is more than one command letter in the same
* line several commands can be run in sequence without intervening prompts.
*
* Horizontal white spaces are ignore, as are `#` characters and everything
* that follows up to the end of line. These features are meant to be helpful
* when the "CLI"-based menu system is given prepard input (the only option in
* some online compilers).
*
* Also integrated is a simple trace system. Trace output is generated by the
* function `trace_output` which has a format specification (like `printf`) as
* its first arguments and must be followed by more arguments according to that
* format. By default `trace_output` is silent and toggled by the `!` command.
*
* Finally there is the `.`-command to terminate the `CLI_runner` function (and
* therefore the whole program, if it's the only resp. last thing in the `main``
* function). End "EOF"-condition - under UNIX/Linux is typically generated by
* CTRL-D – has the same effect if it is the first and only after the prompt
* for another command.
*/
/* module header file (keep always first) */
#include "ncli.h"
/* standard library header files (alphabetically sorted) */
#include <ctype.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static char const* get_vline(FILE* const in) {
static size_t alloc = 2;
static char *data = NULL;
size_t xsize = 0;
do {
char *p = (char *)realloc(data, alloc+xsize);
if (!p) return NULL;
alloc += xsize;
data = p;
p = &data[xsize ? xsize - 1 : 0];
if (!fgets(p, &data[alloc] - p, in)) return NULL;
xsize = (data[strlen(data)-1] != '\n')
? alloc
: 0;
} while (xsize > 0);
return data;
}
enum markers {
M = ':', /* menu selection */
I = '>', /* menu input */
T = '<', /* trace output */
E = '!', /* error notification */
};
static size_t menu_max = 0;
static enum trace_state { OFF, ON } show_trace = OFF;
static enum help_level { NO_HELP, LEVEL1 = 1, LEVEL2 = 2 } show_help = LEVEL2;
static void toggle_tracing() {
if (show_trace) {
trace_output("trace output OFF");
show_trace = OFF;
}
else {
show_trace = ON;
trace_output("trace output ON");
}
}
static char const cmd_letters[] = "abcdefghijklmnopqrstuvwxyz";
static void command_help(struct CMD_Spec const spec[]) {
static char const fmt[] = "%c %c - %s\n";
char const* cp = &cmd_letters[0];
struct CMD_Spec const* p = &spec[0];
if (show_help > 0) {
while (*cp && p->prompt) {
(void) printf(fmt, (char)M, *cp++, p++->prompt);
}
menu_max = cp - &cmd_letters[0];
}
if (show_help > 1) {
(void) printf(fmt, (char)M, '?', "show this help");
(void) printf(fmt, (char)M, '!', "toggle tracing");
(void) printf(fmt, (char)M, '.', "end program");
}
}
static char get_command(struct CMD_Spec const spec[]) {
enum STATE { INPUT, SKIPWS, PARSE, DONE };
static enum STATE state = INPUT;
static char const* cp = NULL;
static char cmd[2];
for (;;) switch (state) {
case INPUT:
command_help(spec);
show_help = NO_HELP;
(void) printf("%c ", I);
fflush(stdout);
cp = get_vline(stdin);
state = (cp != NULL)
? SKIPWS
: DONE;
continue;
case SKIPWS:
switch (*cp) {
case ' ':
case '\t':
++cp;
continue;
case '\0':
state = DONE;
continue;
case '\n':
state = INPUT;
continue;
}
state = PARSE;
continue;
case PARSE:
if (sscanf(cp, "%1[A-Za-z!?.#]", cmd) != 1) {
show_help = (enum help_level)(show_help + 1);
state = INPUT;
continue;
}
switch (cmd[0]) {
case '!':
toggle_tracing();
++cp;
state = SKIPWS;
continue;
case '?':
show_help = LEVEL2;
/*fallthrough*/
case '#':
state = INPUT;
continue;
case '.':
state = DONE;
continue;
}
++cp;
state = SKIPWS;
return tolower(cmd[0]);
case DONE:
return '.';
}
}
void trace_output(char const* fmt, ...) {
if (!show_trace) return;
va_list ap;
(void) printf("< ");
va_start(ap, fmt);
(void) vprintf(fmt, ap);
va_end(ap);
(void) printf("\n");
fflush(stdout);
}
void CLI_runner(struct CMD_Spec const spec[]) {
static char const fmt[] = "%c %s: %c\n";
char cmd;
while ((cmd = get_command(spec)) && (cmd != '.')) {
char const *sel = strchr(cmd_letters, cmd);
const size_t cx = sel ? sel - &cmd_letters[0] : menu_max;
if (cx >= menu_max) {
show_help = LEVEL1;
(void) printf(fmt, (char)E, "no such command", cmd);
}
else if (!spec[cx].action) {
(void) printf(fmt, (char)E, "not implemented", cmd);
}
else spec[cx].action();
}
printf("* bye, bye\n");
}