diff --git a/P06_Integer_Rechner/integer-rechner/Makefile b/P06_Integer_Rechner/integer-rechner/Makefile new file mode 100644 index 0000000..fcaf8fd --- /dev/null +++ b/P06_Integer_Rechner/integer-rechner/Makefile @@ -0,0 +1,9 @@ +SNP_SHARED_MAKEFILE := $(if $(SNP_SHARED_MAKEFILE),$(SNP_SHARED_MAKEFILE),"~/snp/shared.mk") + +TARGET := bin/integer-rechner +MODULES := src/calc.c src/scan.c src/stack.c src/eval.c src/error.c +SOURCES := src/main.c $(MODULES) +TSTSOURCES := tests/tests.c $(MODULES) + +include $(SNP_SHARED_MAKEFILE) + diff --git a/P06_Integer_Rechner/integer-rechner/mainpage.dox b/P06_Integer_Rechner/integer-rechner/mainpage.dox new file mode 100644 index 0000000..6325e98 --- /dev/null +++ b/P06_Integer_Rechner/integer-rechner/mainpage.dox @@ -0,0 +1,8 @@ +/** + * @mainpage SNP - P06 Integer Rechner + * + * @section Purpose + * + * This is a lab on usage of arrays. + * + */ diff --git a/P06_Integer_Rechner/integer-rechner/src/calc.c b/P06_Integer_Rechner/integer-rechner/src/calc.c new file mode 100644 index 0000000..70552f8 --- /dev/null +++ b/P06_Integer_Rechner/integer-rechner/src/calc.c @@ -0,0 +1,168 @@ +/* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief Lab implementation + */ +#include +#include +#include "calc.h" +#include "eval.h" +#include "error.h" + +/** + * @brief The details of the otherwise opaque type. + */ +struct calc { + scan_t *scan; ///< DI: the scanner (this instance does not take over ownership) + eval_t *eval; ///< DI: the evaluator (this instance does not take over ownership) +}; + +static void parse_expression(calc_t *instance); + +static eval_op_t unary_tk2op(int tk) { + switch(tk) { + case '~': return OP_INV; + case '+': return OP_NOP; + case '-': return OP_CHS; + default: + error_fatal("not supported unary op"); + return OP_NOP; + } +} + +static eval_op_t binary_tk2op(int tk) { + switch(tk) { + case '|': return OP_BIT_OR; + case '^': return OP_BIT_XOR; + case '&': return OP_BIT_AND; + case TK_LSHIFT: return OP_BIT_LEFT; + case TK_RSHIFT: return OP_BIT_RIGHT; + case '+': return OP_ADD; + case '-': return OP_SUB; + case '*': return OP_MUL; + case '/': return OP_DIV; + case '%': return OP_MOD; + default: + error_fatal("not supported binary op"); + return OP_NOP; + } +} + + +static int parse_optional_op(calc_t *instance, const int ops[]) +{ + assert(instance); + assert(ops); + const int *op = ops; + while(*op && *op != scan_curr_token(instance->scan)) op++; + if (*op) scan_next_token(instance->scan); + return *op; +} + +static void parse_unary_op(calc_t *instance, void (*parse)(calc_t *), const int ops[]) +{ + assert(instance); + int op = parse_optional_op(instance, ops); + if (op) { + parse_unary_op(instance, parse, ops); + eval_op(instance->eval, unary_tk2op(op)); + } + else { + (*parse)(instance); + } +} + +static void parse_binary_op(calc_t *instance, void (*parse)(calc_t *), const int ops[]) +{ + assert(instance); + (*parse)(instance); + int op = parse_optional_op(instance, ops); + while (op) { + (*parse)(instance); + eval_op(instance->eval, binary_tk2op(op)); + op = parse_optional_op(instance, ops); + } +} + +static void parse_nested_expression(calc_t *instance) +{ + assert(instance); + // printf("parse_nested\n"); + if ('(' != scan_curr_token(instance->scan)) error_fatal("number or '(' ... ')' expected"); + if (scan_next_token(instance->scan) == TK_EOT) error_fatal("expression expected"); + parse_expression(instance); + if (scan_curr_token(instance->scan) != ')') error_fatal("closing ')' expected"); + scan_next_token(instance->scan); +} + +static void parse_number(calc_t *instance) +{ + assert(instance); + // printf("parse_number\n"); + if (scan_curr_token(instance->scan) != TK_NUM) error_fatal("number expected"); + eval_number(instance->eval, scan_curr_value(instance->scan)); + scan_next_token(instance->scan); +} + +static void parse_primary(calc_t *instance) +{ + assert(instance); + // printf("parse_primary(%d)\n", scan_curr_token(instance->scan)); + int t = scan_curr_token(instance->scan); + if (t == TK_NUM) parse_number(instance); + else if (t == '(') parse_nested_expression(instance); + else error_fatal("number or '(' ... ')' expected"); +} + +static void parse_unary(calc_t *instance) { parse_unary_op(instance, parse_primary, (const int[]){ '+', '-', '~', 0 }); } + +static void parse_mul(calc_t *instance) { parse_binary_op(instance, parse_unary, (const int[]){ '*', '/', '%', 0 }); } + +static void parse_sum(calc_t *instance) { parse_binary_op(instance, parse_mul, (const int[]){ '+', '-', 0 }); } + +static void parse_bit_shift(calc_t *instance) { parse_binary_op(instance, parse_sum, (const int[]){ TK_LSHIFT, TK_RSHIFT, 0 }); } + +static void parse_bit_and(calc_t *instance) { parse_binary_op(instance, parse_bit_shift, (const int[]){ '&', 0 }); } + +static void parse_bit_xor(calc_t *instance) { parse_binary_op(instance, parse_bit_and, (const int[]){ '^', 0 }); } + +static void parse_bit_or(calc_t *instance) { parse_binary_op(instance, parse_bit_xor, (const int[]){ '|', 0 }); } + +static void parse_expression(calc_t *instance) { parse_bit_or(instance); } + + +calc_t *calc_new(scan_t *scan, eval_t *eval) +{ + assert(scan); + assert(eval); + calc_t *instance = malloc(sizeof(calc_t)); + if (!instance) error_fatal_errno("calc_new: instance"); + instance->scan = scan; + instance->eval = eval; + return instance; +} + +void calc_destroy(calc_t *instance) +{ + assert(instance); + free(instance); +} + +void calc_execute(calc_t *instance) +{ + assert(instance); + if (scan_next_token(instance->scan) == TK_EOT) error_fatal("missing expression text"); + parse_expression(instance); + // printf("excess = %d\n", scan_curr_token(instance->scan)); + if (scan_curr_token(instance->scan) != TK_EOT) error_fatal("excess expression text found"); + + eval_result(instance->eval); +} diff --git a/P06_Integer_Rechner/integer-rechner/src/calc.h b/P06_Integer_Rechner/integer-rechner/src/calc.h new file mode 100644 index 0000000..65635e8 --- /dev/null +++ b/P06_Integer_Rechner/integer-rechner/src/calc.h @@ -0,0 +1,50 @@ +/* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief Infix calculator + */ + +#ifndef _CALC_H_ +#define _CALC_H_ + +#include "scan.h" +#include "eval.h" + +/** + * @brief Opaque type - detailed in the implementation only. + */ +typedef struct calc calc_t; + +/** + * @brief Construcor. + * @param scan [IN] Dependency Injection: the serving scanner. + * @param eval [IN] Dependency Injection: the serving evaluator. + * @return The newly created and initialized instance + * @remark In case of error, the program terminates with EXIT_FAILURE and an appropriate error message. + * @remark Does not take over ownership of the injected dependencies. + */ +calc_t *calc_new(scan_t *scan, eval_t *eval); + +/** + * @brief Frees the instances memory and the data it owns. + * @param instance [INOUT] The affected instance. + * @remark assert(instance) is performed, i.e. it is considered a programming error if instance is NULL. + */ +void calc_destroy(calc_t *instance); + +/** + * @brief Entry point for processing the expression from stdin. + * @param instance [INOUT] The affected instance. + * @remark assert(instance) is performed, i.e. it is considered a programming error if instance is NULL. + */ +void calc_execute(calc_t *instance); + +#endif // _CALC_H_ diff --git a/P06_Integer_Rechner/integer-rechner/src/error.c b/P06_Integer_Rechner/integer-rechner/src/error.c new file mode 100644 index 0000000..d91aa69 --- /dev/null +++ b/P06_Integer_Rechner/integer-rechner/src/error.c @@ -0,0 +1,36 @@ +/* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief Errors + */ +#include +#include +#include "error.h" + +static size_t context = 0; +void error_set_pos(size_t pos) +{ + context = pos; +} + +void error_fatal(const char message[]) +{ + fprintf(stderr, "ERROR: at or near position %zd: %s\n", context, message); + exit(EXIT_FAILURE); +} + +void error_fatal_errno(const char message[]) +{ + fprintf(stderr, "ERROR: at or near position %zd: ", context); + perror(message); + exit(EXIT_FAILURE); +} + diff --git a/P06_Integer_Rechner/integer-rechner/src/error.h b/P06_Integer_Rechner/integer-rechner/src/error.h new file mode 100644 index 0000000..744ebde --- /dev/null +++ b/P06_Integer_Rechner/integer-rechner/src/error.h @@ -0,0 +1,36 @@ +/* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief Error handling + */ + +#ifndef _ERROR_H_ +#define _ERROR_H_ + +/** + * @brief Sets the context of the current token for potential error reports. + * @param pos [IN] The current input start position of the token which is currently processed. + */ +void error_set_pos(size_t pos); + +/** + * @brief Issues on stderr an error message and terminates the program with EXIT_FAILURE. + * @param message [IN] Context information. + */ +void error_fatal(const char message[]); + +/** + * @brief Issues on stderr an error message (includeing the errno text) and terminates the program with EXIT_FAILURE. + * @param message [IN] Context information. + */ +void error_fatal_errno(const char message[]); + +#endif // _ERROR_H_ diff --git a/P06_Integer_Rechner/integer-rechner/src/eval.c b/P06_Integer_Rechner/integer-rechner/src/eval.c new file mode 100644 index 0000000..9fce346 --- /dev/null +++ b/P06_Integer_Rechner/integer-rechner/src/eval.c @@ -0,0 +1,244 @@ +/* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief RPN Evaluator + */ +#include +#include +#include +#include "eval.h" +#include "error.h" + +/** + * @brief The details of the otherwise opaque type. + */ +struct eval { + stack_t *stack; ///< DI: the stack (this instance does not take over ownership) + int verbose; ///< verbosity control (0 = silent, 1 = verbose) +}; + +static void write_op(FILE *fd, eval_op_t op) +{ + switch(op) { + case OP_NOP: fprintf(fd, "OP_NOP\n"); break; + case OP_PRINT_HEX: fprintf(fd, "OP_PRINT_HEX\n"); break; + case OP_PRINT_DEC: fprintf(fd, "OP_PRINT_DEC\n"); break; + case OP_PRINT_OCT: fprintf(fd, "OP_PRINT_OCT\n"); break; + case OP_PRINT_BIN: fprintf(fd, "OP_PRINT_BIN\n"); break; + case OP_BIT_OR: fprintf(fd, "OP_BIT_OR\n"); break; + case OP_BIT_XOR: fprintf(fd, "OP_BIT_XOR\n"); break; + case OP_BIT_AND: fprintf(fd, "OP_BIT_AND\n"); break; + case OP_BIT_LEFT: fprintf(fd, "OP_BIT_LEFT\n"); break; + case OP_BIT_RIGHT: fprintf(fd, "OP_BIT_RIGHT\n"); break; + case OP_ADD: fprintf(fd, "OP_ADD\n"); break; + case OP_SUB: fprintf(fd, "OP_SUB\n"); break; + case OP_MUL: fprintf(fd, "OP_MUL\n"); break; + case OP_DIV: fprintf(fd, "OP_DIV\n"); break; + case OP_MOD: fprintf(fd, "OP_MOD\n"); break; + case OP_CHS: fprintf(fd, "OP_CHS\n"); break; + case OP_INV: fprintf(fd, "OP_INV\n"); break; + } +} + +static void write_value(FILE *fd, eval_op_t op, unsigned int value) +{ + const unsigned int top_bit = 1 << (sizeof(unsigned int) * 8 - 1); + switch(op) { + case OP_PRINT_HEX: + fprintf(fd, "hex=0x%0*x\n", (int)(sizeof(unsigned int)*2), value); + break; + case OP_PRINT_DEC: + fprintf(fd, "dec=%d\n", value); + break; + case OP_PRINT_OCT: + fprintf(fd, "oct=0%0*o\n", (int)((sizeof(unsigned int)*8+2)/3), value); + break; + case OP_PRINT_BIN: + fprintf(fd, "bin=0b"); + for(size_t i = 0; i < sizeof(unsigned int)*8; i++) { + fputc((value & top_bit) ? '1' : '0', fd); + value <<= 1; + } + fputc('\n', fd); + break; + default: + break; + } +} + +static int eval_top(eval_t *instance, eval_op_t op) +{ + assert(instance); + assert(instance->stack); + + switch(op) { + case OP_NOP: + return 1; + case OP_PRINT_HEX: + case OP_PRINT_DEC: + case OP_PRINT_OCT: + case OP_PRINT_BIN: + write_value(stdout, op, stack_top(instance->stack)); + return 1; + default: + break; + } + return 0; +} + +static int eval_unary(eval_t *instance, eval_op_t op) +{ + assert(instance); + assert(instance->stack); + + unsigned int v; + switch(op) { + case OP_CHS: + v = stack_pop(instance->stack); + stack_push(instance->stack, -v); + return 1; + case OP_INV: + // 1. implement the ~ operator analogous to the - sign operator above + // BEGIN-STUDENTS-TO-ADD-CODE + + + + + // END-STUDENTS-TO-ADD-CODE + default: + break; + } + return 0; +} + +static int eval_binary(eval_t *instance, eval_op_t op) +{ + assert(instance); + assert(instance->stack); + + unsigned int a; + unsigned int b; + + switch(op) { + case OP_ADD: + b = stack_pop(instance->stack); + a = stack_pop(instance->stack); + stack_push(instance->stack, a + b); + return 1; + case OP_SUB: + b = stack_pop(instance->stack); + a = stack_pop(instance->stack); + stack_push(instance->stack, a - b); + return 1; + case OP_MUL: + b = stack_pop(instance->stack); + a = stack_pop(instance->stack); + stack_push(instance->stack, a * b); + return 1; + case OP_DIV: + b = stack_pop(instance->stack); + a = stack_pop(instance->stack); + stack_push(instance->stack, a / b); + return 1; + case OP_MOD: + b = stack_pop(instance->stack); + a = stack_pop(instance->stack); + stack_push(instance->stack, a % b); + return 1; + case OP_BIT_OR: + // 1. implement the | operator analogous to the * operator above + // BEGIN-STUDENTS-TO-ADD-CODE + + + + + // END-STUDENTS-TO-ADD-CODE + case OP_BIT_XOR: + // 1. implement the ^ operator analogous to the * operator above + // BEGIN-STUDENTS-TO-ADD-CODE + + + + + // END-STUDENTS-TO-ADD-CODE + case OP_BIT_AND: + // 1. implement the & operator analogous to the * operator above + // BEGIN-STUDENTS-TO-ADD-CODE + + + + + // END-STUDENTS-TO-ADD-CODE + case OP_BIT_LEFT: + // 1. implement the << operator analogous to the * operator above + // BEGIN-STUDENTS-TO-ADD-CODE + + + + + // END-STUDENTS-TO-ADD-CODE + case OP_BIT_RIGHT: + // 1. implement the >> operator analogous to the * operator above + // BEGIN-STUDENTS-TO-ADD-CODE + + + + + // END-STUDENTS-TO-ADD-CODE + default: + break; + } + return 0; +} + +eval_t *eval_new(stack_t *stack, int verbose) +{ + assert(stack); + eval_t *instance = malloc(sizeof(eval_t)); + if (!instance) error_fatal_errno("eval_new: instance"); + instance->stack = stack; + instance->verbose = verbose; + return instance; +} + +void eval_destroy(eval_t *instance) +{ + assert(instance); + free(instance); +} + +void eval_number(eval_t *instance, unsigned int value) +{ + assert(instance); + stack_push(instance->stack, value); + if (instance->verbose) write_value(stderr, OP_PRINT_HEX, value); +} + +void eval_op(eval_t *instance, eval_op_t op) +{ + if (eval_top(instance, op) || eval_unary(instance, op) || eval_binary(instance, op)) { + // successfully evaluated + if (instance->verbose) write_op(stderr, op); + } else { + error_fatal("eval_op: unknown op"); + } +} + +void eval_result(eval_t *instance) +{ + assert(instance); + unsigned int v = stack_top(instance->stack); + printf("--- RESULT ---\n"); + write_value(stdout, OP_PRINT_DEC, v); + write_value(stdout, OP_PRINT_HEX, v); + write_value(stdout, OP_PRINT_OCT, v); + write_value(stdout, OP_PRINT_BIN, v); +} diff --git a/P06_Integer_Rechner/integer-rechner/src/eval.h b/P06_Integer_Rechner/integer-rechner/src/eval.h new file mode 100644 index 0000000..91f24e4 --- /dev/null +++ b/P06_Integer_Rechner/integer-rechner/src/eval.h @@ -0,0 +1,87 @@ +/* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief RPN Evaluator + */ +#ifndef _EVAL_H_ +#define _EVAL_H_ + +#include "stack.h" + +/** + * @brief All RPN Operators. + */ +typedef enum { + OP_NOP, ///< no operation + OP_PRINT_HEX, ///< print as hex + OP_PRINT_DEC, ///< print as dec + OP_PRINT_OCT, ///< print as oct + OP_PRINT_BIN, ///< print as bin + OP_BIT_OR, ///< unsigned integer bit or + OP_BIT_XOR, ///< unsigned integer bit xor + OP_BIT_AND, ///< unsigned integer bit and + OP_BIT_LEFT, ///< unsigned integer shift-left + OP_BIT_RIGHT, ///< unsigned integer shift-right + OP_ADD, ///< unsigned integer addition + OP_SUB, ///< unsigned integer subtraction + OP_MUL, ///< unsigned integer multiplication + OP_DIV, ///< unsigned integer division + OP_MOD, ///< unsigned integer modulo + OP_CHS, ///< unsigned integer twos-complement + OP_INV, ///< unsigned integer ones-complement +} eval_op_t; + +/** + * @brief Opaque type - detailed in the implementation only. + */ +typedef struct eval eval_t; + +/** + * @brief Construcor. + * @param stack [IN] Dependency Injection: the serving stack. + * @param verbose [IN] Cointrol debug output (stderr): 0 = no output, 1 = additional output. + * @return The newly created and initialized instance + * @remark In case of error, the program terminates with EXIT_FAILURE and an appropriate error message. + * @remark Does not take over ownership of the injected dependencies. + */ +eval_t *eval_new(stack_t *stack, int verbose); + +/** + * @brief Frees the instances memory and the data it owns. + * @param instance [INOUT] The affected instance. + * @remark assert(instance) is performed, i.e. it is considered a programming error if instance is NULL. + */ +void eval_destroy(eval_t *instance); + +/** + * @brief Feeds the value in RPN sequence to the evaluator. + * @param instance [INOUT] The affected instance. + * @param value [IN] The value to stack on the RPN calculator. + * @remark assert(instance) is performed, i.e. it is considered a programming error if instance is NULL. + */ +void eval_number(eval_t *instance, unsigned int value); + +/** + * @brief Feeds the operation in RPN sequence to the evaluator. + * @param instance [INOUT] The affected instance. + * @param op [IN] The operator to evaluate. + * @remark assert(instance) is performed, i.e. it is considered a programming error if instance is NULL. + */ +void eval_op(eval_t *instance, eval_op_t op); + +/** + * @brief Prints the top entry of the stack. + * @param instance [INOUT] The affected instance. + * @remark assert(instance) is performed, i.e. it is considered a programming error if instance is NULL. + */ +void eval_result(eval_t *instance); + +#endif // _EVAL_H_ diff --git a/P06_Integer_Rechner/integer-rechner/src/main.c b/P06_Integer_Rechner/integer-rechner/src/main.c new file mode 100644 index 0000000..f39f52e --- /dev/null +++ b/P06_Integer_Rechner/integer-rechner/src/main.c @@ -0,0 +1,42 @@ +/* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief Lab implementation + */ +#include +#include +#include "calc.h" + +/** + * @brief Main entry point. + * @param[in] argc The size of the argv array. + * @param[in] argv The command line arguments... + * @returns Returns EXIT_SUCCESS (=0) on success, EXIT_FAILURE (=1) there is an expression syntax error. + */ +int main(int argc, char* argv[]) +{ + int verbose = argc > 1 && strtol(argv[1], NULL, 10); + + const size_t stack_size = 10; + stack_t *stack = stack_new(stack_size); + eval_t *eval = eval_new(stack, verbose); + scan_t *scan = scan_new(); + calc_t *calc = calc_new(scan, eval); + + calc_execute(calc); + + calc_destroy(calc); + scan_destroy(scan); + eval_destroy(eval); + stack_destroy(stack); + + return EXIT_SUCCESS; +} diff --git a/P06_Integer_Rechner/integer-rechner/src/scan.c b/P06_Integer_Rechner/integer-rechner/src/scan.c new file mode 100644 index 0000000..4912526 --- /dev/null +++ b/P06_Integer_Rechner/integer-rechner/src/scan.c @@ -0,0 +1,204 @@ +/* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief Infix calculator tokenizer + */ + +#include +#include +#include +#include +#include +#include "scan.h" +#include "error.h" + +/** + * @brief The details of the otherwise opaque type. + */ +struct scan { + size_t count; ///< counts the read characters (used for error reporting) + int la; ///< look-ahead-1 character + int token; ///< the currently recognized token + unsigned int value; ///< the current TK_NUM value +}; + + +static int next_char(scan_t *instance) +{ + assert(instance); + instance->la = getc(stdin); + if (instance->la != EOF) instance->count++; + return instance->la; +} + +static int curr_char(scan_t *instance) +{ + assert(instance); + return instance->la; +} + +static int skip_chars(scan_t *instance) +{ + assert(instance); + while(instance->la != EOF && isspace(instance->la)) next_char(instance); + return instance->la; +} + +static int eof(scan_t *instance) +{ + assert(instance); + // printf("eof?\n"); + if (curr_char(instance) == EOF) { + instance->token = TK_EOT; + return 1; + } + return 0; +} + +static int op1(scan_t *instance, const char ops[]) +{ + assert(instance); + // printf("op1?\n"); + assert(ops); + if (strchr(ops, curr_char(instance))) { + instance->token = curr_char(instance); + next_char(instance); + return 1; + } + return 0; +} + +static int op2(scan_t *instance, const char *op, int t) +{ + assert(instance); + // printf("op2?\n"); + assert(op); + assert(strlen(op) == 2); + if (curr_char(instance) == op[0] && next_char(instance) == op[1]) { + instance->token = t; + next_char(instance); + return 1; + } + return 0; +} + +static int number(scan_t *instance) +{ + assert(instance); + // printf("number\n"); + int c = curr_char(instance); + if (c == '0') { + instance->value = 0; + instance->token = TK_NUM; + c = next_char(instance); + if (strchr("xX", c)) { + instance->token = TK_ERROR; + c = next_char(instance); + while(isxdigit(c) || c == '_') { + if (c != '_') { + instance->value *= 16; + instance->token = TK_NUM; + if (isdigit(c)) { + instance->value += c - '0'; + } else { + instance->value += 10 + c - (isupper(c) ? 'A' : 'a'); + } + } + c = next_char(instance); + } + } else if (strchr("bB", c)) { + instance->token = TK_ERROR; + c = next_char(instance); + while(strchr("01_", c)) { + if (c != '_') { + instance->value *= 2; + instance->value += c - '0'; + instance->token = TK_NUM; + } + c = next_char(instance); + } + } else if (strchr("01234567_", c)) { + while(strchr("01234567_", c)) { + if (c != '_') { + instance->value *= 8; + instance->value += c - '0'; + } + c = next_char(instance); + } + } + return 1; + } else if (isdigit(c)) { + instance->value = 0; + while(isdigit(c) || c == '_') { + if (c != '_') { + instance->value *= 10; + instance->value += c - '0'; + c = next_char(instance); + } + } + instance->token = TK_NUM; + return 1; + } + return 0; +} + + + +scan_t *scan_new() +{ + scan_t *instance = malloc(sizeof(scan_t)); + if (!instance) error_fatal_errno("scan_new: instance"); + instance->count = 0; + instance->la = EOF; + instance->token = TK_EOT; + instance->value = 0; + return instance; +} + +void scan_destroy(scan_t *instance) +{ + assert(instance); + free(instance); +} + +int scan_next_token(scan_t *instance) +{ + assert(instance); + // printf("next...\n"); + int c = curr_char(instance); + if (c == EOF) c = next_char(instance); // initial (or final) situation + c = skip_chars(instance); + error_set_pos(instance->count); + + instance->token = TK_ERROR; + instance->value = 0; + + if (eof(instance) || op1(instance, "+-*%/|^&~()") || op2(instance, "<<", TK_LSHIFT) || op2(instance, ">>", TK_RSHIFT) || number(instance)) { + // one of the above was found + skip_chars(instance); + } + + // printf("next = %d\n", token); + return instance->token; +} + +int scan_curr_token(scan_t *instance) +{ + assert(instance); + return instance->token; +} + +unsigned int scan_curr_value(scan_t *instance) +{ + assert(instance); + if (scan_curr_token(instance) != TK_NUM) error_fatal_errno("scan_curr_value: wrong token"); + return instance->value; +} diff --git a/P06_Integer_Rechner/integer-rechner/src/scan.h b/P06_Integer_Rechner/integer-rechner/src/scan.h new file mode 100644 index 0000000..46a50ac --- /dev/null +++ b/P06_Integer_Rechner/integer-rechner/src/scan.h @@ -0,0 +1,75 @@ +/* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief Infix calculator tokenizer + */ + +#ifndef _SCAN_H_ +#define _SCAN_H_ + +/** + * @brief Opaque type - detailed in the implementation only. + */ +typedef struct scan scan_t; + +/** + * @brief Specific token enum values beside the ones provided as plain characters. + * @remark The last enum value must be below printable ascii code + */ +typedef enum { + TK_ERROR = 0, ///< error or end mark for arrays of tokens (must be 0) + TK_EOT, ///< end of text, i.e. when EOF is recognized + TK_LSHIFT, ///< left-shift operator (2-char token) + TK_RSHIFT, ///< right-shift operator (2-char token) + TK_NUM, ///< number token (e.g. 3, 0x1F, etc.) +} scan_tk_t; + +/** + * @brief Construcor. + * @return The newly created and initialized instance + * @remark In case of error, the program terminates with EXIT_FAILURE and an appropriate error message. + */ +scan_t *scan_new(); + +/** + * @brief Frees the instances memory and the data it owns. + * @param instance [INOUT] The affected instance. + * @remark assert(instance) is performed, i.e. it is considered a programming error if instance is NULL. + */ +void scan_destroy(scan_t *instance); + +/** + * @brief Moves to the next token. + * @param instance [INOUT] The affected instance. + * @return Returns the next recognized token (single character token or scan_tk_t enum value). + * @remark assert(instance) is performed, i.e. it is considered a programming error if instance is NULL. + * @remark If the token is a TK_NUM, the scan_curr_value() function returns the associated value. + */ +int scan_next_token(scan_t *instance); + +/** + * @brief Gets the token which mas found with the last scan_next_token() call. + * @param instance [INOUT] The affected instance. + * @return Returns the currently recognized token (single character token or scan_tk_t enum value). + * @remark assert(instance) is performed, i.e. it is considered a programming error if instance is NULL. + */ +int scan_curr_token(scan_t *instance); + +/** + * @brief Gets the value associated with the TK_NUM token. + * @param instance [INOUT] The affected instance. + * @return Returns the value associated with the current TK_NUM. + * @remark assert(instance) is performed, i.e. it is considered a programming error if instance is NULL. + * @remark This only gives valid values if the scan_curr_token() equals TK_NUM. + */ +unsigned int scan_curr_value(scan_t *instance); + +#endif // _SCAN_H_ diff --git a/P06_Integer_Rechner/integer-rechner/src/stack.c b/P06_Integer_Rechner/integer-rechner/src/stack.c new file mode 100644 index 0000000..7cf6e09 --- /dev/null +++ b/P06_Integer_Rechner/integer-rechner/src/stack.c @@ -0,0 +1,132 @@ +/* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief Stack + */ + +#include +#include +#include "stack.h" +#include "error.h" + +/** + * @brief The details of the otherwise opaque type. + */ +struct stack { + stack_value_t *stack; ///< the data container (the instance owns this) + stack_value_t *top; ///< the pointer into the container where the current top data resides (has to point initially to stack-1) + stack_value_t *full; ///< the pointer to the last entry of the conatainer +}; + +stack_t *stack_new(size_t max_elements) +{ + stack_t *instance = NULL; + // 1. allocate a stack_t instance on the heap and set the instance variable to it + // 2. call error_fatal_errno("stack_new: instance"); if failed to allocate the memory on the heap + // 3. allocate an array of max_elements value_t's on the heap and store its address in the stack member of the stack_t instance + // 4. call error_fatal_errno("stack_new: stack"); if failed to allocate the memory on the heap + // 5. set the top member of the stack_t instance to the address of the "element" before the first stack array element + // 6. set the full member of the stack_t instance to the address of the last element of the stack array + // BEGIN-STUDENTS-TO-ADD-CODE + + + + + + + + + + + // END-STUDENTS-TO-ADD-CODE + return instance; +} + +void stack_destroy(stack_t *instance) +{ + assert(instance); + // 1. free the stack array + // 2. free the stack_t instance + // BEGIN-STUDENTS-TO-ADD-CODE + + + + + // END-STUDENTS-TO-ADD-CODE +} + +void stack_push(stack_t *instance, stack_value_t value) +{ + assert(instance); + if (stack_is_full(instance)) error_fatal("stack_push: full"); + // 1. increment by one element the address stored in the top member of the stack_t instance + // 2. store the received value at the new top location + // BEGIN-STUDENTS-TO-ADD-CODE + + + + // END-STUDENTS-TO-ADD-CODE +} + +stack_value_t stack_pop(stack_t *instance) +{ + assert(instance); + if (stack_is_empty(instance)) error_fatal("stack_pop: empty"); + stack_value_t value = 0; + // 1. set the variable value to the value from the address the top member points to + // 2. decrement by one element the address stored in the top member of the stack_t instance + // BEGIN-STUDENTS-TO-ADD-CODE + + + + // END-STUDENTS-TO-ADD-CODE + return value; +} + +stack_value_t stack_top(stack_t *instance) +{ + assert(instance); + if (stack_is_empty(instance)) error_fatal("stack_top: empty"); + stack_value_t value = 0; + // 1. set the variable value to the value from the address the top member points to + // BEGIN-STUDENTS-TO-ADD-CODE + + + + // END-STUDENTS-TO-ADD-CODE + return value; +} + +int stack_is_empty(stack_t *instance) +{ + assert(instance); + int is_empty = 1; + // 1. set is_empty to 1 if the top equals the initial condition as done in stack_new(), otherwise to 0 + // BEGIN-STUDENTS-TO-ADD-CODE + + + + // END-STUDENTS-TO-ADD-CODE + return is_empty; +} + +int stack_is_full(stack_t *instance) +{ + assert(instance); + int is_full = 1; + // 1. set is_full to 1 if the top equals the full pointer as set in the stack_new() function, otherwise 0 + // BEGIN-STUDENTS-TO-ADD-CODE + + + + // END-STUDENTS-TO-ADD-CODE + return is_full; +} diff --git a/P06_Integer_Rechner/integer-rechner/src/stack.h b/P06_Integer_Rechner/integer-rechner/src/stack.h new file mode 100644 index 0000000..f13d075 --- /dev/null +++ b/P06_Integer_Rechner/integer-rechner/src/stack.h @@ -0,0 +1,86 @@ +/* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief Stack + */ + +#ifndef _STACK_H_ +#define _STACK_H_ + +/** + * @brief Convenience alias. + */ +typedef unsigned int stack_value_t; + +/** + * @brief Opaque type - detailed in the implementation only. + */ +typedef struct stack stack_t; + +/** + * @brief Construcor. + * @param max_elements [IN] Gives the size of the stack. + * @return The newly created and initialized instance + * @remark In case of error, the program terminates with EXIT_FAILURE and an appropriate error message. + */ +stack_t *stack_new(size_t max_elements); + +/** + * @brief Frees the instances memory and the data it owns (i.e. the stack container). + * @param instance [INOUT] The affected instance. + * @remark assert(instance) is performed, i.e. it is considered a programming error if instance is NULL. + */ +void stack_destroy(stack_t *instance); + +/** + * @brief Pushes the value to the stack. + * @param instance [INOUT] The affected instance. + * @param value [IN] The value to pus. + * @remark In case of error, the program terminates with EXIT_FAILURE and an appropriate error message. + * @remark assert(instance) is performed, i.e. it is considered a programming error if instance is NULL. + */ +void stack_push(stack_t *instance, stack_value_t value); + +/** + * @brief Removes the top stack entry and returns it. + * @param instance [INOUT] The affected instance. + * @return Returns the top most entry. + * @remark In case of error, the program terminates with EXIT_FAILURE and an appropriate error message. + * @remark assert(instance) is performed, i.e. it is considered a programming error if instance is NULL. + */ +stack_value_t stack_pop(stack_t *instance); + +/** + * @brief Gets the top stack entry. + * @param instance [INOUT] The affected instance. + * @return Returns the top most entry. + * @remark In case of error, the program terminates with EXIT_FAILURE and an appropriate error message. + * @remark assert(instance) is performed, i.e. it is considered a programming error if instance is NULL. + */ +stack_value_t stack_top(stack_t *instance); + +/** + * @brief Queries if the stack is empty - i.e. if a pop operation is possible. + * @param instance [INOUT] The affected instance. + * @return Returns 1 if empry, 0 otherwise. + * @remark assert(instance) is performed, i.e. it is considered a programming error if instance is NULL. + */ +int stack_is_empty(stack_t *instance); + +/** + * @brief Queries if the stack is full - i.e. if a push operation is possible. + * @param instance [INOUT] The affected instance. + * @return Returns 1 if full, 0 otherwise. + * @remark assert(instance) is performed, i.e. it is considered a programming error if instance is NULL. + */ +int stack_is_full(stack_t *instance); + +#endif // _STACK_H_ diff --git a/P06_Integer_Rechner/integer-rechner/tests/tests.c b/P06_Integer_Rechner/integer-rechner/tests/tests.c new file mode 100644 index 0000000..8883979 --- /dev/null +++ b/P06_Integer_Rechner/integer-rechner/tests/tests.c @@ -0,0 +1,263 @@ +/* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief Test suite for the given package. + */ +#include +#include +#include +#include +#include +#include +#include "test_utils.h" +#include "stack.h" + +#ifndef TARGET // must be given by the make file --> see test target +#error missing TARGET define +#endif + +/// @brief alias for EXIT_SUCCESS +#define OK EXIT_SUCCESS +/// @brief alias for EXIT_FAILURE +#define FAIL EXIT_FAILURE + +/// @brief The name of the STDOUT text file. +#define OUTFILE "stdout.txt" +/// @brief The name of the STDERR text file. +#define ERRFILE "stderr.txt" + +#define TRACE_INDENT "\n " ///< allow for better stdout formatting in case of error + +// setup & cleanup +static int setup(void) +{ + remove_file_if_exists(OUTFILE); + remove_file_if_exists(ERRFILE); + return 0; // success +} + +static int teardown(void) +{ + // Do nothing. + // Especially: do not remove result files - they are removed in int setup(void) *before* running a test. + return 0; // success +} + +// tests +static void test_stack_new(void) +{ + // arrange + const size_t size = 2; + // act + stack_t *instance = stack_new(size); + // assert + CU_ASSERT_PTR_NOT_NULL_FATAL(instance); + CU_ASSERT_EQUAL(stack_is_empty(instance), 1); + // cleanup + stack_destroy(instance); +} + +static void test_stack_push(void) +{ + // arrange + const size_t size = 2; + stack_t *instance = stack_new(size); + CU_ASSERT_PTR_NOT_NULL_FATAL(instance); + // act & assert + for(size_t i = 0; i < size; i++) { + stack_push(instance, i); + CU_ASSERT_EQUAL(stack_top(instance), i); + } + // cleanup + stack_destroy(instance); +} + +static void test_stack_top(void) +{ + // arrange + const size_t size = 2; + stack_t *instance = stack_new(size); + CU_ASSERT_PTR_NOT_NULL_FATAL(instance); + // act & assert + CU_ASSERT_EQUAL(stack_is_empty(instance), 1); + for(size_t i = 0; i < size; i++) { + CU_ASSERT_EQUAL(stack_is_full(instance), 0); + stack_push(instance, i); + CU_ASSERT_EQUAL(stack_is_empty(instance), 0); + CU_ASSERT_EQUAL(stack_top(instance), i); + } + CU_ASSERT_EQUAL(stack_is_full(instance), 1); + // cleanup + stack_destroy(instance); +} + +static void test_stack_pop(void) +{ + // arrange + const size_t size = 2; + stack_t *instance = stack_new(size); + CU_ASSERT_PTR_NOT_NULL_FATAL(instance); + for(size_t i = 0; i < size; i++) { + stack_push(instance, i); + } + // act & assert + for(size_t i = size; i > 0; i--) { + CU_ASSERT_EQUAL(stack_is_empty(instance), 0); + CU_ASSERT_EQUAL(stack_pop(instance), i-1); + CU_ASSERT_EQUAL(stack_is_full(instance), 0); + } + // cleanup + stack_destroy(instance); +} + +static void test_stack_is_empty(void) +{ + // arrange + const size_t size = 2; + stack_t *instance = stack_new(size); + CU_ASSERT_PTR_NOT_NULL_FATAL(instance); + // act & assert + CU_ASSERT_EQUAL(stack_is_empty(instance), 1); + for(size_t i = 0; i < size; i++) { + stack_push(instance, i); + CU_ASSERT_EQUAL(stack_is_empty(instance), 0); + } + for(size_t i = size; i > 0; i--) { + CU_ASSERT_EQUAL(stack_is_empty(instance), 0); + stack_pop(instance); + } + CU_ASSERT_EQUAL(stack_is_empty(instance), 1); + // cleanup + stack_destroy(instance); +} + +static void test_stack_is_full(void) +{ + // arrange + const size_t size = 2; + stack_t *instance = stack_new(size); + CU_ASSERT_PTR_NOT_NULL_FATAL(instance); + // act & assert + CU_ASSERT_EQUAL(stack_is_full(instance), 0); + for(size_t i = 0; i < size; i++) { + CU_ASSERT_EQUAL(stack_is_full(instance), 0); + stack_push(instance, i); + } + CU_ASSERT_EQUAL(stack_is_full(instance), 1); + for(size_t i = size; i > 0; i--) { + stack_pop(instance); + CU_ASSERT_EQUAL(stack_is_full(instance), 0); + } + CU_ASSERT_EQUAL(stack_is_full(instance), 0); + // cleanup + stack_destroy(instance); +} + +static void test_silent(void) +{ + const char *out_txt[] = { + "--- RESULT ---\n", + "dec=3\n", + "hex=0x00000003\n", + "oct=000000000003\n", + "bin=0b00000000000000000000000000000011\n", + }; + const char *err_txt[] = { + NULL, + }; + + // arrange & act & assert + CU_ASSERT_EQUAL_FATAL(WEXITSTATUS(system("echo '1 + 2' | " XSTR(TARGET) " 0 >" OUTFILE " 2>" ERRFILE)), OK); + assert_lines(OUTFILE, out_txt, sizeof(out_txt)/sizeof(*out_txt)); + assert_lines(ERRFILE, err_txt, sizeof(err_txt)/sizeof(*err_txt)); + // arrange & act & assert + CU_ASSERT_EQUAL_FATAL(WEXITSTATUS(system("echo '1 + 2' | " XSTR(TARGET) " >" OUTFILE " 2>" ERRFILE)), OK); + assert_lines(OUTFILE, out_txt, sizeof(out_txt)/sizeof(*out_txt)); + assert_lines(ERRFILE, err_txt, sizeof(err_txt)/sizeof(*err_txt)); +} + +static void test_verbose(void) +{ + const char *out_txt[] = { + "--- RESULT ---\n", + "dec=3\n", + "hex=0x00000003\n", + "oct=000000000003\n", + "bin=0b00000000000000000000000000000011\n", + }; + const char *err_txt[] = { + "hex=0x00000001\n", + "hex=0x00000002\n", + "OP_ADD\n", + }; + + + // arrange & act & assert + CU_ASSERT_EQUAL_FATAL(WEXITSTATUS(system("echo '1 + 2' | " XSTR(TARGET) " 1 >" OUTFILE " 2>" ERRFILE)), OK); + assert_lines(OUTFILE, out_txt, sizeof(out_txt)/sizeof(*out_txt)); + assert_lines(ERRFILE, err_txt, sizeof(err_txt)/sizeof(*err_txt)); +} + +static void test_all(void) +{ + const char *out_txt[] = { + "--- RESULT ---\n", + "dec=8\n", + "hex=0x00000008\n", + "oct=000000000010\n", + "bin=0b00000000000000000000000000001000\n", + }; + const char *err_txt[] = { + NULL, + }; + + + // arrange & act & assert + CU_ASSERT_EQUAL_FATAL(WEXITSTATUS(system("echo '(8 | 7 ^ 5) & 4 << 3 + 2 * (~1+(1>>0))' | " XSTR(TARGET) " >" OUTFILE " 2>" ERRFILE)), OK); + assert_lines(OUTFILE, out_txt, sizeof(out_txt)/sizeof(*out_txt)); + assert_lines(ERRFILE, err_txt, sizeof(err_txt)/sizeof(*err_txt)); +} + +static void test_error(void) +{ + const char *out_txt[] = { + NULL, + }; + const char *err_txt[] = { + "ERROR: at or near position 5: number or '(' ... ')' expected\n", + }; + + // arrange & act & assert + CU_ASSERT_EQUAL_FATAL(WEXITSTATUS(system("echo '1 + ' | " XSTR(TARGET) " >" OUTFILE " 2>" ERRFILE)), FAIL); + assert_lines(OUTFILE, out_txt, sizeof(out_txt)/sizeof(*out_txt)); + assert_lines(ERRFILE, err_txt, sizeof(err_txt)/sizeof(*err_txt)); +} + +/** + * @brief Registers and runs the tests. + * @returns success (0) or one of the CU_ErrorCode (>0) + */ +int main(void) +{ + // setup, run, teardown + TestMainBasic("lab test", setup, teardown + , test_stack_new + , test_stack_push + , test_stack_top + , test_stack_pop + , test_stack_is_empty + , test_stack_is_full + , test_silent + , test_verbose + , test_all + , test_error + ); +}