add pages

This commit is contained in:
stsh
2022-02-17 14:51:10 +01:00
parent 38b7891b86
commit 903e42182c
137 changed files with 28159 additions and 6 deletions
@@ -0,0 +1,88 @@
/*****************************************************************************
M. Thaler, Jan. 2000
Datei read.java
Funktion: Unsigned int Zahl via Bytestream von stdin einlesen.
Es wird zuerst eine Zeile eingelesen und dann konvertiert.
Die eingelesene Zeile darf nur eine Zahl enthalten
(mit optionalen Leerzeichen vor/nach der Zahl).
Returns: Die konvertierte Zahl
oder -1 (PARSE_ERROR) wenn keine Zahl oder zu gross
oder -2 (READ_ERROR) wenn Fehler beim einlesen.
Korrekturen: - Maerz 2002: M. Thaler, H. Fierz, Mar. 2002
liest bis EOL oder EOF, korrekter Rueckgabewert
- Sept 2016: A. Gieriet
Refactored (sprechende Variablen-Namen,
MS Windows Support, 0 Wert erlaubt,
Leerzeichen Support,
"magic" Numbers durch Symbole ersetzt,
maxResult Parameter, etc.)
******************************************************************************/
public class read {
public int getInt(int maxResult)
throws java.io.IOException
{
// end of input
int EOF = -1; // end of file
int EOL = 10; // end of line
// abnormal return values
int PARSE_ERROR = -1;
int READ_ERROR = -2;
// ASCII Codes
int ASCII_SPACE = 32; // ' '
int ASCII_DIGIT_0 = 48; // '0'
int ASCII_DIGIT_9 = 57; // '9'
// conversion buffer
int NO_POS = -1;
int BUFFERSIZE = 10;
byte[] buffer = new byte[BUFFERSIZE];
int result = 0;
// read line: up to EOL or EOF (i.e. error while reading)
int bytes = 0;
int input = System.in.read();
while ((input != EOL) && (input != EOF)) { // read whole line
if (bytes < BUFFERSIZE) { // only buffer first n characters
buffer[bytes] = (byte)input;
bytes++;
} else {
result = PARSE_ERROR; // exceed buffer size, continue read line
}
input = System.in.read();
}
if (input == EOF) {
result = READ_ERROR;
}
// check for numbers: skip leading and trailing spaces
// (i.e. this includes all control chars below the space ASCII code)
int pos = 0;
while((pos < bytes) && (buffer[pos] <= ASCII_SPACE)) pos++; // skip SP
int posOfFirstDigit = pos;
int posOfLastDigit = NO_POS;
while ((pos < bytes)
&& (buffer[pos] >= ASCII_DIGIT_0)
&& (buffer[pos] <= ASCII_DIGIT_9))
{
posOfLastDigit = pos;
pos++;
}
while((pos < bytes) && (buffer[pos] <= ASCII_SPACE)) pos++; // skip SP
// produce return value
if (result != 0) {
// previously detected read or parse error given
} else if ((pos != bytes) || (posOfLastDigit == NO_POS)) {
result = PARSE_ERROR;
} else { // convert number
for(int i = posOfFirstDigit; i <= posOfLastDigit; i++) {
result = result * 10 + (buffer[i] - ASCII_DIGIT_0);
if (result > maxResult) {
result = PARSE_ERROR;
break;
}
}
}
return result;
}
}
@@ -0,0 +1,32 @@
/*****************************************************************************
M. Thaler, Jan. 2000
Datei: rectang.java
Funktion: Bestimmt, ob Dreieck rechtwinklig ist.
Returns: true wenn rechtwinklig, sonst false.
Korrekturen: - Sept 2016, A. Gieriet
Refactored (sprechende Variablen Namen, etc.)
******************************************************************************/
public class rectang {
public boolean Rectangular(int a, int b, int c) {
int aS = a*a;
int bS = b*b;
int cS = c*c;
boolean isRightAngled;
if ((a == 0) && (b == 0) && (c == 0))
isRightAngled = false;
else if ((aS + bS) == cS)
isRightAngled = true;
else if ((aS + cS) == bS)
isRightAngled = true;
else if ((bS + cS) == aS)
isRightAngled = true;
else
isRightAngled = false;
return isRightAngled;
}
}
@@ -0,0 +1,73 @@
/*****************************************************************************
M. Thaler, Jan. 2000
Datei: triangle.java
Funktion: Die drei Seiten eines Dreiecks einlesen und bestimmen ob
das Dreieck rechtwinklig ist.
Returns: Nichts.
Korrekturen: - Maerz 2002, M. Thaler, H. Fierz
Abfrage bei unkorrekter Eingabe wiederholen
- Sept 2016, A. Gieriet
Refactored (sprechende Variablen-Namen,
"magic" Numbers durch Symbole ersetzt, etc.)
******************************************************************************/
class triangle {
public static void main(String[] args)
throws java.io.IOException
{
int READ_ERROR = -2;
int MAX_NUMBER = 1000;
read ReadInt = new read();
rectang Rect = new rectang();
while (true) {
System.out.println("\nDreiecksbestimmung (CTRL-C: Abbruch)\n");
int word = 0;
int a = 0;
int b = 0;
int c = 0;
do {
System.out.print("Seite a: ");
word = ReadInt.getInt(MAX_NUMBER);
}
while ((word < 0) && (word != READ_ERROR));
if (word >= 0)
a = word;
else
break;
do {
System.out.print("Seite b: ");
word = ReadInt.getInt(MAX_NUMBER);
}
while ((word < 0) && (word != READ_ERROR));
if (word >= 0)
b = word;
else
break;
do {
System.out.print("Seite c: ");
word = ReadInt.getInt(MAX_NUMBER);
}
while ((word < 0) && (word != READ_ERROR));
if (word >= 0)
c = word;
else
break;
if (Rect.Rectangular(a, b, c) == true)
System.out.println("-> Dreieck " + a + "-" + b + "-" + c
+ " ist rechtwinklig");
else
System.out.println("-> Dreieck " + a + "-" + b + "-" + c
+ " ist nicht rechtwinklig");
System.out.println("\n");
}
System.out.println("\n\nbye bye\n");
}
}
@@ -0,0 +1,94 @@
/* ----------------------------------------------------------------------------
* -- _____ ______ _____ -
* -- |_ _| | ____|/ ____| -
* -- | | _ __ | |__ | (___ Institute of Embedded Systems -
* -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur -
* -- _| |_| | | | |____ ____) | (University of Applied Sciences) -
* -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland -
* ----------------------------------------------------------------------------
*/
/**
* @file
* @brief Lab implementation
*/
#include <stdio.h>
#include <stdlib.h>
#include "read.h"
#include "trace.h"
//#define EOF (-1) // end of file
/// end-of-line
#define EOL 10 // end of line
// abnormal return values
/// parse-error
#define PARSE_ERROR (-1)
// ASCII Codes
/// ascii code for space
#define ASCII_SPACE ' '
/// ascii code for 0
#define ASCII_DIGIT_0 '0'
/// ascii code for 9
#define ASCII_DIGIT_9 '9'
// conversion buffer
/// no buffer position sentry
#define NO_POS (-1)
/// buffer size
#define BUFFERSIZE (10)
int getInt(int maxResult)
{
TRACE("getInt(%d)", maxResult);
char buffer[BUFFERSIZE] = { 0 };
int result = 0;
// read line: up to EOL or EOF (i.e. error while reading)
int bytes = 0;
int input = getchar();
while ((input != EOL) && (input != EOF)) { // read whole line
if (bytes < BUFFERSIZE) { // only buffer first n characters
buffer[bytes] = (char)input;
bytes++;
} else {
result = PARSE_ERROR; // exceed buffer size, continue read line
}
input = getchar();
}
if (input == EOF) {
result = READ_ERROR;
}
// check for numbers: skip leading and trailing spaces
// (i.e. this includes all control chars below the space ASCII code)
int pos = 0;
while((pos < bytes) && (buffer[pos] <= ASCII_SPACE)) pos++; // skip SP
int posOfFirstDigit = pos;
int posOfLastDigit = NO_POS;
while ((pos < bytes)
&& (buffer[pos] >= ASCII_DIGIT_0)
&& (buffer[pos] <= ASCII_DIGIT_9))
{
posOfLastDigit = pos;
pos++;
}
while((pos < bytes) && (buffer[pos] <= ASCII_SPACE)) pos++; // skip SP
// produce return value
if (result != 0) {
// previously detected read or parse error given
} else if ((pos != bytes) || (posOfLastDigit == NO_POS)) {
result = PARSE_ERROR;
} else { // convert number
for(int i = posOfFirstDigit; i <= posOfLastDigit; i++) {
result = result * 10 + (buffer[i] - ASCII_DIGIT_0);
if (result > maxResult) {
result = PARSE_ERROR;
break;
}
}
}
return result;
}
@@ -0,0 +1,27 @@
/* ----------------------------------------------------------------------------
* -- _____ ______ _____ -
* -- |_ _| | ____|/ ____| -
* -- | | _ __ | |__ | (___ Institute of Embedded Systems -
* -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur -
* -- _| |_| | | | |____ ____) | (University of Applied Sciences) -
* -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland -
* ----------------------------------------------------------------------------
*/
/**
* @file
* @brief Lab implementation
*/
#ifndef READ_H
#define READ_H
/// I/O error which leads to aborting.
#define READ_ERROR (-2)
/**
* @brief Reads a line from stdin and tries to parse it as number (with optional leading and trailing spaces).
* @param[in] maxResult gives the limit for the number to be read.
* @returns the succesfully read and parsed number or a negative value if in error (READ_ERROR in case of I/O error).
*/
int getInt(int maxResult);
#endif
@@ -0,0 +1,38 @@
/* ----------------------------------------------------------------------------
* -- _____ ______ _____ -
* -- |_ _| | ____|/ ____| -
* -- | | _ __ | |__ | (___ Institute of Embedded Systems -
* -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur -
* -- _| |_| | | | |____ ____) | (University of Applied Sciences) -
* -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland -
* ----------------------------------------------------------------------------
*/
/**
* @file
* @brief Lab implementation
*/
#include "rectang.h"
#include "trace.h"
int rectangular(int a, int b, int c)
{
TRACE("rectangular(%d, %d, %d)", a, b, c);
int aS = a*a;
int bS = b*b;
int cS = c*c;
int isRightAngled;
if ((a == 0) && (b == 0) && (c == 0))
isRightAngled = 0;
else if ((aS + bS) == cS)
isRightAngled = 1;
else if ((aS + cS) == bS)
isRightAngled = 1;
else if ((bS + cS) == aS)
isRightAngled = 1;
else
isRightAngled = 0;
return isRightAngled;
}
@@ -0,0 +1,26 @@
/* ----------------------------------------------------------------------------
* -- _____ ______ _____ -
* -- |_ _| | ____|/ ____| -
* -- | | _ __ | |__ | (___ Institute of Embedded Systems -
* -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur -
* -- _| |_| | | | |____ ____) | (University of Applied Sciences) -
* -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland -
* ----------------------------------------------------------------------------
*/
/**
* @file
* @brief Lab implementation
*/
#ifndef RECTANG_H
#define RECTANG_H
/**
* @brief checks if the three lengths give a right-angled triangle.
* @param[in] a 1st side
* @param[in] b 2nd side
* @param[in] c 3rd side
* @returns 1 if right-angled, 0 otherwise
*/
int rectangular(int a, int b, int c);
#endif
@@ -0,0 +1,26 @@
/* ----------------------------------------------------------------------------
* -- _____ ______ _____ -
* -- |_ _| | ____|/ ____| -
* -- | | _ __ | |__ | (___ Institute of Embedded Systems -
* -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur -
* -- _| |_| | | | |____ ____) | (University of Applied Sciences) -
* -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland -
* ----------------------------------------------------------------------------
*/
/**
* @file
* @brief Lab implementation
*/
#ifndef TRACE_H
#define TRACE_H
#include <stdio.h>
/**
* @brief TRACE macro.
* @param[in] MSG "..." format string literal.
* @param[in] ... optional arguments to the format string.
*/
#define TRACE(MSG, ...) do { (void)fprintf(stderr, "TRACE: " MSG "\n", ##__VA_ARGS__); } while(0)
#endif
@@ -0,0 +1,78 @@
/* ----------------------------------------------------------------------------
* -- _____ ______ _____ -
* -- |_ _| | ____|/ ____| -
* -- | | _ __ | |__ | (___ Institute of Embedded Systems -
* -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur -
* -- _| |_| | | | |____ ____) | (University of Applied Sciences) -
* -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland -
* ----------------------------------------------------------------------------
*/
/**
* @file
* @brief Lab implementation
*/
#include <stdio.h>
#include <stdlib.h>
#include "read.h"
#include "rectang.h"
#include "trace.h"
/// max side length
#define MAX_NUMBER 1000
/**
* @brief Main entry point.
* @returns Returns EXIT_SUCCESS (=0) on success, EXIT_FAILURE (=1) on failure.
*/
int main(void)
{
TRACE("main()");
while (1) {
(void)printf("\nDreiecksbestimmung (CTRL-C: Abbruch)\n\n");
int word = 0;
int a = 0;
int b = 0;
int c = 0;
do {
(void)printf("Seite a: ");
word = getInt(MAX_NUMBER);
}
while ((word < 0) && (word != READ_ERROR));
if (word >= 0)
a = word;
else
break;
do {
(void)printf("Seite b: ");
word = getInt(MAX_NUMBER);
}
while ((word < 0) && (word != READ_ERROR));
if (word >= 0)
b = word;
else
break;
do {
(void)printf("Seite c: ");
word = getInt(MAX_NUMBER);
}
while ((word < 0) && (word != READ_ERROR));
if (word >= 0)
c = word;
else
break;
if (rectangular(a, b, c))
(void)printf("-> Dreieck %d-%d-%d ist rechtwinklig\n", a, b, c);
else
(void)printf("-> Dreieck %d-%d-%d ist nicht rechtwinklig\n", a, b, c);
(void)printf("\n\n");
}
(void)printf("\n\nbye bye\n\n");
return EXIT_SUCCESS;
}
@@ -0,0 +1,12 @@
a
a
a
3
4444
4444
44444444444444
4
4
5
5
@@ -0,0 +1,12 @@
3
4
6
5
4
4
3
5
5
33
43
55
@@ -0,0 +1,12 @@
3
4
5
5
4
3
3
5
4
33
44
55
@@ -0,0 +1,205 @@
/* ----------------------------------------------------------------------------
* -- _____ ______ _____ -
* -- |_ _| | ____|/ ____| -
* -- | | _ __ | |__ | (___ Institute of Embedded Systems -
* -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur -
* -- _| |_| | | | |____ ____) | (University of Applied Sciences) -
* -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland -
* ----------------------------------------------------------------------------
*/
/**
* @file
* @brief Lab implementation
*/
#include <stdio.h>
#include <stdlib.h>
#include "CUnit/Basic.h"
#include "test_utils.h"
#ifndef TARGET // must be given by the make file --> see test target
#error missing TARGET define
#endif
/// @brief The name of the STDOUT text file.
#define OUTFILE "stdout.txt"
/// @brief The name of the STDERR text file.
#define ERRFILE "stderr.txt"
/// @brief The stimulus for the right-angled triangles
#define INFILE_RIGHT_ANGLED "stim-right-angled.input"
/// @brief The stimulus for the not right-angled triangles
#define INFILE_NOT_RIGHT_ANGLED "stim-not-right-angled.input"
/// @brief The stimulus for input errors
#define INFILE_ERROR "stim-error.input"
// 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_right_angled(void)
{
// arrange
const char *out_txt[] = {
"\n",
"Dreiecksbestimmung (CTRL-C: Abbruch)\n",
"\n",
"Seite a: Seite b: Seite c: -> Dreieck 3-4-5 ist rechtwinklig\n",
"\n",
"\n",
"\n",
"Dreiecksbestimmung (CTRL-C: Abbruch)\n",
"\n",
"Seite a: Seite b: Seite c: -> Dreieck 5-4-3 ist rechtwinklig\n",
"\n",
"\n",
"\n",
"Dreiecksbestimmung (CTRL-C: Abbruch)\n",
"\n",
"Seite a: Seite b: Seite c: -> Dreieck 3-5-4 ist rechtwinklig\n",
"\n",
"\n",
"\n",
"Dreiecksbestimmung (CTRL-C: Abbruch)\n",
"\n",
"Seite a: Seite b: Seite c: -> Dreieck 33-44-55 ist rechtwinklig\n",
"\n",
"\n",
"\n",
"Dreiecksbestimmung (CTRL-C: Abbruch)\n",
"\n",
"Seite a: \n",
"\n",
"bye bye\n",
"\n",
};
// act
int exit_code = system(XSTR(TARGET) " 1>" OUTFILE " 2>" ERRFILE " <" INFILE_RIGHT_ANGLED);
// assert
CU_ASSERT_EQUAL(exit_code, 0);
assert_lines(OUTFILE, out_txt, sizeof(out_txt)/sizeof(*out_txt));
}
static void test_not_right_angled(void)
{
// arrange
const char *out_txt[] = {
"\n",
"Dreiecksbestimmung (CTRL-C: Abbruch)\n",
"\n",
"Seite a: Seite b: Seite c: -> Dreieck 3-4-6 ist nicht rechtwinklig\n",
"\n",
"\n",
"\n",
"Dreiecksbestimmung (CTRL-C: Abbruch)\n",
"\n",
"Seite a: Seite b: Seite c: -> Dreieck 5-4-4 ist nicht rechtwinklig\n",
"\n",
"\n",
"\n",
"Dreiecksbestimmung (CTRL-C: Abbruch)\n",
"\n",
"Seite a: Seite b: Seite c: -> Dreieck 3-5-5 ist nicht rechtwinklig\n",
"\n",
"\n",
"\n",
"Dreiecksbestimmung (CTRL-C: Abbruch)\n",
"\n",
"Seite a: Seite b: Seite c: -> Dreieck 33-43-55 ist nicht rechtwinklig\n",
"\n",
"\n",
"\n",
"Dreiecksbestimmung (CTRL-C: Abbruch)\n",
"\n",
"Seite a: \n",
"\n",
"bye bye\n",
"\n",
};
// act
int exit_code = system(XSTR(TARGET) " 1>" OUTFILE " 2>" ERRFILE " <" INFILE_NOT_RIGHT_ANGLED);
// assert
CU_ASSERT_EQUAL(exit_code, 0);
assert_lines(OUTFILE, out_txt, sizeof(out_txt)/sizeof(*out_txt));
}
static void test_trace(void)
{
// arrange
const char *err_txt[] = {
"TRACE: main()\n",
"TRACE: getInt(1000)\n",
"TRACE: getInt(1000)\n",
"TRACE: getInt(1000)\n",
"TRACE: rectangular(3, 4, 6)\n",
"TRACE: getInt(1000)\n",
"TRACE: getInt(1000)\n",
"TRACE: getInt(1000)\n",
"TRACE: rectangular(5, 4, 4)\n",
"TRACE: getInt(1000)\n",
"TRACE: getInt(1000)\n",
"TRACE: getInt(1000)\n",
"TRACE: rectangular(3, 5, 5)\n",
"TRACE: getInt(1000)\n",
"TRACE: getInt(1000)\n",
"TRACE: getInt(1000)\n",
"TRACE: rectangular(33, 43, 55)\n",
"TRACE: getInt(1000)\n",
};
// act
int exit_code = system(XSTR(TARGET) " 1>" OUTFILE " 2>" ERRFILE " <" INFILE_NOT_RIGHT_ANGLED);
// assert
CU_ASSERT_EQUAL(exit_code, 0);
assert_lines(ERRFILE, err_txt, sizeof(err_txt)/sizeof(*err_txt));
}
static void test_error(void)
{
// arrange
const char *out_txt[] = {
"\n",
"Dreiecksbestimmung (CTRL-C: Abbruch)\n",
"\n",
"Seite a: Seite a: Seite a: Seite a: Seite a: Seite b: Seite b: Seite b: Seite b: Seite b: Seite c: Seite c: -> Dreieck 3-4-5 ist rechtwinklig\n",
"\n",
"\n",
"\n",
"Dreiecksbestimmung (CTRL-C: Abbruch)\n",
"\n",
"Seite a: \n",
"\n",
"bye bye\n",
"\n",
};
// act
int exit_code = system(XSTR(TARGET) " 1>" OUTFILE " 2>" ERRFILE " <" INFILE_ERROR);
// assert
CU_ASSERT_EQUAL(exit_code, 0);
assert_lines(OUTFILE, out_txt, sizeof(out_txt)/sizeof(*out_txt));
}
/**
* @brief Registers and runs the tests.
*/
int main(void)
{
// setup, run, teardown
TestMainBasic("Triangle", setup, teardown
, test_right_angled
, test_not_right_angled
, test_trace
, test_error
);
}
@@ -0,0 +1,57 @@
SNP_SHARED_MAKEFILE := $(if $(SNP_SHARED_MAKEFILE),$(SNP_SHARED_MAKEFILE),"~/snp/shared.mk")
TARGET := bin/dep2dot
SOURCES := src/main.c src/data.c src/output.c
TSTSOURCES := tests/tests.c
include $(SNP_SHARED_MAKEFILE)
# DEPFILES := ... define a list of png file names: %.c -> %.c.png
# BEGIN-STUDENTS-TO-ADD-CODE
DEPFILES := $(SOURCES:%.c=%.c.png)
# END-STUDENTS-TO-ADD-CODE
# define dep target as .PHONEY
# BEGIN-STUDENTS-TO-ADD-CODE
.PHONY: dep
# BEGIN-STUDENTS-TO-ADD-CODE
# define dep target depending on FULLTARGET and DEPFILES above
# action: echo some text telling that the target is done using $@ - the echo command shall not be echoed before execution
# BEGIN-STUDENTS-TO-ADD-CODE
dep: $(FULLTARGET) $(DEPFILES)
@echo "### $@ done ###"
# BEGIN-STUDENTS-TO-ADD-CODE
# define new suffix rule for %.png depending on %.dot
# action: dot -Tpng $< >$@ || $(RM) $@
# BEGIN-STUDENTS-TO-ADD-CODE
%.png: %.dot
dot -Tpng $< >$@ || $(RM) $@
# BEGIN-STUDENTS-TO-ADD-CODE
# define new suffix rule for %.dot depending on %.dep
# action: call $(TARGET) $(@:.dot=) <$< >$@ || $(RM) $@
# BEGIN-STUDENTS-TO-ADD-CODE
%.dot: %.dep
$(TARGET) $(@:.dot=) <$< >$@ || $(RM) $@
# BEGIN-STUDENTS-TO-ADD-CODE
# converts any .c file into a .c.dep file by means of GCC -H switch
# note: it removes intermediate files which were created as side effect
%.c.dep: %.c
$(COMPILE.c) -H -o $@.x $< 2>$@ && $(RM) $@.x $@.d
# cleanup all results, including the ones od creating the dependencies
dep-clean: clean
$(RM) $(DEPFILES) $(wildcard src/*.dep src/*.dot)
@@ -0,0 +1,8 @@
/**
* @mainpage SNP - P04 Modularisation
*
* @section Purpose
*
* This is a lab for splitting functionality into multiple modules.
*
*/
@@ -0,0 +1,149 @@
/**
* @file
* @brief Implementation of the dependency file access.
*/
#include "data.h"
#include "error.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>
#include <assert.h>
#define MAX_PATH_LEN 512 ///< @brief Arbitrarily chosen maximum accepted path lenght.
#define MAX_LINE_LEN 512 ///< @brief Arbitrarily chosen maximum accepted line length
#define MAX_DIRS 64 ///< @brief Arbitrarily chosen maximum number of supported individual directories per dependency file.
#define MAX_FILES 256 ///< @brief Arbitrarily chosen maximum number of supported individual denendency entries.
/**
* @brief Declaration of POSIX (but not C99) function.
* @param s [IN] The string to duplicate on the heap memory.
* @return The duplicated string.
* @remark Since the Makefile calls gcc with -std=c99, non-C99 POSIX and GNU extensions are excluded - the glibc, though, provides the function to the linker.
*/
char *strdup(const char *s); // not stdc99, but available in the glibc
/**
* @brief Initialized the data structure before the data is to be read from th edependency file.
* @param data [INOUT] The address of the instance to initialize.
*/
static void init(data_t *data)
{
assert(data);
memset(data, 0, sizeof(data_t));
data->dirs = malloc(MAX_DIRS * sizeof(dir_t));
if (!data->dirs) FATAL("no memory left");
data->files = malloc(MAX_FILES * sizeof(file_t));
if (!data->files) FATAL("no memory left");
}
/**
* @brief Updates the directory list with the given data.
* @param data [INOUT] The instance to update.
* @param path [IN] The file path of a dependency entry as given by the dependency file.
* @return The index of the directory entry (either an existing matching one or a newly added one).
* @remark Extracts the directory part by means of dirname() from the given path and looks up an existing entry or adds a new one.
*/
static size_t get_or_add_dir(data_t *data, const char *path)
{
assert(data);
assert(path);
// The function dirname() gives no guarantee to not modify the parameter, therefore, need to produce a copy before calling dirname().
// Likewise, the returned value may refer to the passed paremater, therefore, a copy is made from the return value.
char *dup = strdup(path);
if (!dup) FATAL("no memory left");
char *name = strdup(dirname(dup));
if (!name) FATAL("no memory left");
free(dup);
// search for a matching entry...
size_t i = 0;
while(i < data->n_dirs && strcmp(data->dirs[i].name, name) != 0) {
i++;
}
if (i >= MAX_DIRS) FATAL("too many directories");
if (i == data->n_dirs) { // no match found: add
// handover the allocated name to the owning new directory entry
dir_t dir = { .name = name };
// append the new directory entry
data->dirs[data->n_dirs] = dir;
data->n_dirs++;
} else {
// release the name since match found, and therefore, no need to keep the allocated name anymore
free(name);
}
return i;
}
/**
* @brief Add a file entry from the dependency file to the data structure.
* @param data [INOUT] The data container instance.
* @param path [IN] The path of one file entry from the dependency file.
* @param level [IN] The dependency level of the file entry from the dependency file.
* @remark The sequence of entries in the dependency file is relevant - it implies direct dependencies.
*/
static void add_file(data_t *data, const char *path, size_t level)
{
assert(data);
assert(path);
// The function basename() gives no guarantee to not modify the parameter, therefore, need to produce a copy before calling basename().
// Likewise, the returned value may refer to the passed paremater, therefore, a copy is made from the return value.
char *dup = strdup(path);
if (!dup) FATAL("no memory left");
char *name = strdup(basename(dup));
if (!name) FATAL("no memory left");
free(dup);
if (data->n_files >= MAX_FILES) FATAL("too many files");
// produce a file entry
file_t file = { .name = name, .dir = get_or_add_dir(data, path), .level = level };
data->files[data->n_files] = file;
data->n_files++;
}
/**
* @brief Processes one dependency line of the dependency file.
* @param data [INOUT] The data container instance.
* @param line [IN] The line to parse and store in data.
*/
static void process_line(data_t *data, const char line[])
{
assert(data);
size_t len = strlen(line);
assert(len > 0);
assert(line[0] == '.');
// read level
size_t i = strspn(line, ".");
size_t level = i;
// skip spaces
i += strspn(line+i, " \t");
// take rest as path and add the file to the records
add_file(data, line+i, level);
}
/*
* The public interface.
*/
const data_t data_read_all(const char *root)
{
data_t data;
init(&data);
// add as first file the root for the given dependencies
add_file(&data, root, 0);
char line[MAX_LINE_LEN] = { 0 };
// read all stdin line and only process dependency lines (those starting on a '.')
clearerr(stdin);
while(fgets(line, MAX_LINE_LEN, stdin)) {
size_t len = strlen(line);
if (len > 0 && line[len-1] == '\n' && line[0] == '.') { // only dependency lines
line[len-1] = '\0';
process_line(&data, line);
}
}
return data;
}
@@ -0,0 +1,72 @@
/**
* @file
* @brief Access to the GCC produced dependency data (via -H command line option).
*/
// begin of include guard
// BEGIN-STUDENTS-TO-ADD-CODE
#ifndef _DATA_H_
#define _DATA_H_
// END-STUDENTS-TO-ADD-CODE
// includes which are needed in this header file
// BEGIN-STUDENTS-TO-ADD-CODE
#include <stddef.h>
// END-STUDENTS-TO-ADD-CODE
/**
* @brief Directory container for file entries of the dependency file.
*/
// BEGIN-STUDENTS-TO-ADD-CODE
typedef struct {
const char *name; ///< @brief the path name of the directory as given by the GCC produced dependency file.
} dir_t;
// END-STUDENTS-TO-ADD-CODE
/**
* @brief File container for the file entries of the dependency file.
*/
// BEGIN-STUDENTS-TO-ADD-CODE
typedef struct {
const char *name; ///< @brief The base name of the file from the GGC produced dependency file (i.e. the plain name, without any directory path).
size_t dir; ///< @brief The index of the directory entry which represents the path as given by the dependency file.
size_t level; ///< @brief The level as read out from the dependecy file.
} file_t;
// END-STUDENTS-TO-ADD-CODE
/**
* @brief Overall container for all directories and all files from the dependency file.
*/
// BEGIN-STUDENTS-TO-ADD-CODE
typedef struct {
size_t n_dirs; ///< @brief The number of valid entries in the dirs list.
dir_t *dirs; ///< @brief The list of directories.
size_t n_files; ///< @brief The number of valid entries in the files list.
file_t *files; ///< @brief The list of files from the dependency file (the sequence is relevant to determine the dependencies).
} data_t;
// END-STUDENTS-TO-ADD-CODE
/**
* @brief Entry function to read the deendency data from stdin.
* @param root [IN] The name of the root file (the deoendency file does not mention the root file, so, it has to be passed from outside).
* @return The container of the read data from stdin. See the documentation on gcc -H for details on the dependencies, etc.
*/
// BEGIN-STUDENTS-TO-ADD-CODE
const data_t data_read_all(const char *root);
// END-STUDENTS-TO-ADD-CODE
// end of include guard
// BEGIN-STUDENTS-TO-ADD-CODE
#endif // _DATA_H_
// END-STUDENTS-TO-ADD-CODE
@@ -0,0 +1,17 @@
/**
* @file
* @brief Error handling convenience functions.
*/
#ifndef _ERROR_H_
#define _ERROR_H_
#include <stdio.h>
#include <stddef.h>
/**
* @brief Prints the message to stderr and terminates with EXIT_FAILURE.
* @param MSG [IN] The "..." *string literal* to emit as error - no format parameters nor variables supported.
*/
#define FATAL(MSG) do { fprintf(stderr, "ERROR: %s\n", MSG); exit(EXIT_FAILURE); } while(0)
#endif // _ERROR_H_
@@ -0,0 +1,36 @@
/* ----------------------------------------------------------------------------
* -- _____ ______ _____ -
* -- |_ _| | ____|/ ____| -
* -- | | _ __ | |__ | (___ Institute of Embedded Systems -
* -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur -
* -- _| |_| | | | |____ ____) | (University of Applied Sciences) -
* -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland -
* ----------------------------------------------------------------------------
*/
/**
* @file
* @brief Lab P04 dep2dot
*/
#include <stdio.h>
#include <stdlib.h>
#include "error.h"
#include "data.h"
#include "output.h"
/**
* @brief main function
* @param argc [in] number of entries in argv
* @param argv [in] program name plus command line arguments
* @returns returns success if valid date is given, failure otherwise
* @remark Prerequisit to convert the resulting DOT file on the shell: sodo apt install graphviz
* @remark Convert: gcc -H ... file.c ... 2>file.dep ; dep2dot file.c <file.dep >file.dot && dot -Tpng file.dot >file.png
*/
int main(int argc, const char *argv[])
{
if (argc < 2) FATAL("missing arguments\nusage: dep2dot file.c <file.dep >file.dot # from gcc -H ... file.c ... 2>file.dep\n");
output_dot(data_read_all(argv[1]));
return EXIT_SUCCESS;
}
@@ -0,0 +1,95 @@
/**
* @file
* @brief Provides output functions for various file formats.
*/
#include "output.h"
#include <stdio.h>
#include <assert.h>
#include <string.h>
/**
* @brief Writes the node name of the given file.
* @param file [IN] The file for which to write the node name.
* @remark The dependency data contain duplicates of file entries - the node name must be unique for the path and the *basename* of the files.
*/
static void print_node(file_t file)
{
printf("\"%s (cluster_c%zd)\"", file.name, file.dir);
}
/**
* @brief Recursively writes the individual direct dependencies for the file given by curr.
* @param files [IN] The array of all files - the sequence is relevant.
* @param len [IN] The lenght of the files array, i.e. the upper limit for curr values and the subsequent index values.
* @param curr [IN] The index into files for the current root for dependencies: curr -> x, curr -> y, ...
* @return Returns the index into files for the next file to process (i.e. curr value for the next call to this function).
* @remark For a given *curr* file, all following files are with depth level + 1 are direct include files.
* @remark All files with a higher level are *indirect* include files, thus *direct* includes from files processed by recursive calls.
* @remark The list of direct includes to the *curr* file terminates with a level equal of less the the *curr* one (or when the list is exchausted).
*/
static size_t dependencies(file_t files[], size_t len, size_t curr)
{
assert(curr < len);
size_t level = files[curr].level;
size_t file = curr + 1;
while(file < len && files[file].level > level) {
if (files[file].level == level + 1) {
// Write to stdout " file -> include;\n" where file and include are the DOT node names of the respective files
// BEGIN-STUDENTS-TO-ADD-CODE
printf(" ");
print_node(files[curr]);
printf(" -> ");
print_node(files[file]);
printf(";\n");
// END-STUDENTS-TO-ADD-CODE
file = dependencies(files, len, file);
} else {
file++;
}
}
return file;
}
/*
* Public interface
*/
void output_dot(const data_t data)
{
printf("digraph dep {\n");
// nodes
printf(" node [shape=box]\n");
for (size_t file = 0; file < data.n_files; file++) {
// Write to stdout " file [label=\"name\"];\n" where file is the DOT node name and name is the file name
// BEGIN-STUDENTS-TO-ADD-CODE
printf(" ");
print_node(data.files[file]);
printf(" [label=\"%s\"];\n", data.files[file].name);
// END-STUDENTS-TO-ADD-CODE
}
// directory clusters
for (size_t dir = 0; dir < data.n_dirs; dir++) {
printf(" subgraph cluster_c%zd {\n", dir);
printf(" label=\"%s\"; %s\n", data.dirs[dir].name, strncmp(data.dirs[dir].name, "/usr/", 5) == 0 ? "style=filled; color=lightgrey;" : "color=black;");
for (size_t file = 0; file < data.n_files; file++) {
if (data.files[file].dir == dir) {
// Write to stdout " file;\n" where file is the DOT node name
// BEGIN-STUDENTS-TO-ADD-CODE
printf(" ");
print_node(data.files[file]);
printf(";\n");
// END-STUDENTS-TO-ADD-CODE
}
}
printf(" }\n");
}
// dependencies
size_t curr = 0;
do {
curr = dependencies(data.files, data.n_files, curr);
} while(curr < data.n_files);
printf("}\n");
}
@@ -0,0 +1,20 @@
/**
* @file
* @brief Provides output functions for various file formats.
*/
// define proper header file here, with include gaurd, etc.
// BEGIN-STUDENTS-TO-ADD-CODE
#ifndef _OUTPUT_H_
#define _OUTPUT_H_
#include "data.h"
/**
* @brief Produces DOT output of the dependencies given in data.
* @param data [IN] Container of the dependenciy data.
*/
void output_dot(const data_t data);
#endif // _OUTPUT_H_
// END-STUDENTS-TO-ADD-CODE
@@ -0,0 +1,7 @@
Test File
. dir1/h1_1
.. dir1/h1_1_2
. dir1/h1_2
. dir2/h2_1
.. dir1/h1_1
Done
@@ -0,0 +1,2 @@
Test File
Done
@@ -0,0 +1,140 @@
/* ----------------------------------------------------------------------------
* -- _____ ______ _____ -
* -- |_ _| | ____|/ ____| -
* -- | | _ __ | |__ | (___ Institute of Embedded Systems -
* -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur -
* -- _| |_| | | | |____ ____) | (University of Applied Sciences) -
* -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland -
* ----------------------------------------------------------------------------
*/
/**
* @file
* @brief Test suite for the given package.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <time.h>
#include <assert.h>
#include <CUnit/Basic.h>
#include "test_utils.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"
/// @brief test data file
#define IN_NO_DEP "no_dep.input"
/// @brief test data file
#define IN_DEP "dep.input"
// 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_fail_no_arg(void)
{
// arrange & act & assert
CU_ASSERT_EQUAL(WEXITSTATUS(system(XSTR(TARGET) " >" OUTFILE " 2>" ERRFILE)), FAIL);
}
static void test_no_dep(void)
{
// arrange
const char *out_txt[] = {
"digraph dep {\n",
" node [shape=box]\n",
" \"root (cluster_c0)\" [label=\"root\"];\n",
" subgraph cluster_c0 {\n",
" label=\".\"; color=black;\n",
" \"root (cluster_c0)\";\n",
" }\n",
"}\n",
};
// act & assert
CU_ASSERT_EQUAL(WEXITSTATUS(system(XSTR(TARGET) " root <" IN_NO_DEP " >" OUTFILE " 2>" ERRFILE)), OK);
// assert
assert_lines(OUTFILE, out_txt, sizeof(out_txt)/sizeof(*out_txt));
}
static void test_dep(void)
{
// arrange
const char *out_txt[] = {
"digraph dep {\n",
" node [shape=box]\n",
" \"root (cluster_c0)\" [label=\"root\"];\n",
" \"h1_1 (cluster_c1)\" [label=\"h1_1\"];\n",
" \"h1_1_2 (cluster_c1)\" [label=\"h1_1_2\"];\n",
" \"h1_2 (cluster_c1)\" [label=\"h1_2\"];\n",
" \"h2_1 (cluster_c2)\" [label=\"h2_1\"];\n",
" \"h1_1 (cluster_c1)\" [label=\"h1_1\"];\n",
" subgraph cluster_c0 {\n",
" label=\".\"; color=black;\n",
" \"root (cluster_c0)\";\n",
" }\n",
" subgraph cluster_c1 {\n",
" label=\"dir1\"; color=black;\n",
" \"h1_1 (cluster_c1)\";\n",
" \"h1_1_2 (cluster_c1)\";\n",
" \"h1_2 (cluster_c1)\";\n",
" \"h1_1 (cluster_c1)\";\n",
" }\n",
" subgraph cluster_c2 {\n",
" label=\"dir2\"; color=black;\n",
" \"h2_1 (cluster_c2)\";\n",
" }\n",
" \"root (cluster_c0)\" -> \"h1_1 (cluster_c1)\";\n",
" \"h1_1 (cluster_c1)\" -> \"h1_1_2 (cluster_c1)\";\n",
" \"root (cluster_c0)\" -> \"h1_2 (cluster_c1)\";\n",
" \"root (cluster_c0)\" -> \"h2_1 (cluster_c2)\";\n",
" \"h2_1 (cluster_c2)\" -> \"h1_1 (cluster_c1)\";\n",
"}\n",
};
// act & assert
CU_ASSERT_EQUAL(WEXITSTATUS(system(XSTR(TARGET) " root <" IN_DEP " >" OUTFILE " 2>" ERRFILE)), OK);
// assert
assert_lines(OUTFILE, out_txt, sizeof(out_txt)/sizeof(*out_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_fail_no_arg
, test_no_dep
, test_dep
);
}