commit a0d6bf48a185026589288fd9aa94506b321301d8
parent 93107ebd417e75efed4e2173feeea1030ce6cd02
Author: Louis Burda <quent.burda@gmail.com>
Date: Tue, 4 May 2021 11:07:45 +0200
further improved parsing and related tests
Diffstat:
7 files changed, 152 insertions(+), 65 deletions(-)
diff --git a/service/src/msgs/welcome b/service/src/msgs/welcome
@@ -1,2 +1,2 @@
Welcome to PrintDoc!
-Upload stl files and see all the interesting details!
+Upload an stl file and we'll analyze it!
diff --git a/service/src/printdoc.c b/service/src/printdoc.c
@@ -138,7 +138,7 @@ submit_cmd(char *arg)
lastrun.valid = parse_file(&lastrun, contents, len);
- // if (lastrun.valid) dump(lastrun.infopath);
+ if (lastrun.valid && lastrun.infopath) dump(lastrun.infopath);
free(contents);
}
@@ -203,4 +203,5 @@ main()
}
printf("see you later!\n");
+ free_info(&lastrun);
}
diff --git a/service/src/stlfile.c b/service/src/stlfile.c
@@ -2,6 +2,18 @@
#include "util.h"
static const char wsset[] = " \r\n\t";
+static const struct {
+ int code;
+ const char *str;
+} kwmap[] = {
+ { KW_SOLID_BEGIN, "solid" },
+ { KW_SOLID_END, "endsolid" },
+ { KW_LOOP_BEGIN, "outer loop" },
+ { KW_LOOP_END, "endloop" },
+ { KW_FACET_BEGIN, "facet normal" },
+ { KW_FACET_END, "endfacet" },
+ { KW_VERTEX, "vertex" },
+};
void
stack_init(struct stack *stack)
@@ -27,7 +39,7 @@ int
stack_pop(struct stack *stack)
{
if (stack->count == 0)
- die("popping empty stack!\n");
+ return -1;
stack->count--;
return stack->data[stack->count];
@@ -39,6 +51,16 @@ stack_free(struct stack *stack)
free(stack->data);
}
+int
+stack_ind(struct stack *stack, int search)
+{
+ int i;
+ for (i = 0; i < stack->count; i++)
+ if (stack->data[i] == search)
+ return i;
+ return -1;
+}
+
long
skip_set(char *start, const char *set)
{
@@ -63,101 +85,117 @@ consume_arg(char **start)
int
consume_keyword(char **start)
{
- static const struct {
- int code;
- const char *str;
- } mapping[] = {
- { KW_SOLID_BEGIN, "solid" },
- { KW_SOLID_END, "endsolid" },
- { KW_LOOP_BEGIN, "outer loop" },
- { KW_LOOP_END, "endloop" },
- { KW_FACET_BEGIN, "facet normal" },
- { KW_FACET_END, "endfacet" },
- { KW_VERTEX, "vertex" },
- };
- char *bp, *nsep;
+ char *bp;
int i, len;
bp = *start + skip_set(*start, wsset);
- for (i = 0; i < ARRSIZE(mapping); i++) {
- len = strlen(mapping[i].str);
- if (!strncmp(mapping[i].str, bp, len) && strchr(wsset, *(bp + len))) {
- printf("GOT: %s\n", mapping[i].str);
+ for (i = 0; i < ARRSIZE(kwmap); i++) {
+ len = strlen(kwmap[i].str);
+ if (!strncmp(kwmap[i].str, bp, len) && strchr(wsset, *(bp + len))) {
+ printf("GOT: %s\n", kwmap[i].str);
*start = bp + len + (bp[len] ? 1 : 0);
- return mapping[i].code;
+ return kwmap[i].code;
}
}
return KW_UNKNOWN;
}
+#define PARSE_FAIL(...) \
+ do { fprintf(stderr, "FORMAT ERR: " __VA_ARGS__); goto fail; } while (0)
+
int
parse_file_ascii(struct parseinfo *info, char *buf, size_t len)
{
- char *bp, *nsep, *prev;
+ char *bp, *arg, *prev, *tmp;
struct stack states;
+ float farg;
int i, kw;
stack_init(&states);
info->type = TYPE_ASCII;
+ info->loopcount = 0;
bp = prev = buf;
while ((kw = consume_keyword(&bp))) {
switch (kw) {
case KW_SOLID_BEGIN:
- /* TODO: save solid name */
stack_push(&states, STATE_SOLID);
- consume_arg(&bp);
+ if (stack_ind(&states, KW_SOLID_BEGIN) != -1)
+ PARSE_FAIL("multiple nested solids!\n");
+ tmp = bp;
+ if (!consume_keyword(&bp) && (arg = consume_arg(&bp))) {
+ memcpy(info->extra, arg, MIN(strlen(arg), 80));
+ } else {
+ bp = tmp;
+ }
+ info->namehash = checkp(strdup(mhash(info->extra, 80)));
break;
case KW_SOLID_END:
- /* TODO: check that name matches */
- if (stack_pop(&states) != STATE_SOLID)
- goto unknown;
- consume_arg(&bp);
+ if ((kw = stack_pop(&states)) != STATE_SOLID)
+ PARSE_FAIL("Improper nesting, parent: %s\n", kwmap[kw].str);
+ tmp = bp;
+ if (*info->extra && !consume_keyword(&bp)
+ && (arg = consume_arg(&bp))) {
+ if (strncmp(info->extra, arg, 80))
+ PARSE_FAIL("solid end/begin names do not match!\n");
+ } else {
+ bp = tmp;
+ }
break;
case KW_LOOP_BEGIN:
- /* TODO: ensure only one loop */
stack_push(&states, STATE_LOOP);
+ if (stack_ind(&states, KW_LOOP_BEGIN) != -1)
+ PARSE_FAIL("multiple nested loops!\n");
break;
case KW_LOOP_END:
- if (stack_pop(&states) != STATE_LOOP)
- goto unknown;
+ if ((kw = stack_pop(&states)) != STATE_LOOP)
+ PARSE_FAIL("Improper nesting, parent: %s\n", kwmap[kw].str);
+ info->loopcount++;
break;
case KW_FACET_BEGIN:
- /* TODO: check if args are integers */
stack_push(&states, STATE_FACET);
- for (i = 0; i < 3 && consume_arg(&bp); i++);
+ if (stack_ind(&states, KW_LOOP_BEGIN) != -1)
+ PARSE_FAIL("multiple nested facets!\n");
+ for (i = 0; i < 3; i++) {
+ if (!(arg = consume_arg(&bp)))
+ PARSE_FAIL("Facet with less than 3 args!\n");
+ farg = strtof(arg, &tmp);
+ if (tmp && *tmp)
+ PARSE_FAIL("Facet with invalid arg '%s'!\n", arg);
+ }
break;
case KW_FACET_END:
- if (stack_pop(&states) != STATE_FACET)
- goto unknown;
+ if ((kw = stack_pop(&states)) != STATE_FACET)
+ PARSE_FAIL("Improper nesting, parent: %s\n", kwmap[kw].str);
break;
case KW_VERTEX:
- /* TODO: check if args are integers */
- for (i = 0; i < 3 && consume_arg(&bp); i++);
+ /* TODO: calc bounding box */
+ for (i = 0; i < 3; i++) {
+ if (!(arg = consume_arg(&bp)))
+ PARSE_FAIL("Vertex with less than 3 args!\n");
+ farg = strtof(arg, &tmp);
+ if (tmp && *tmp)
+ PARSE_FAIL("Vertex with invalid arg '%s'!\n", arg);
+ }
break;
-unknown:
case KW_UNKNOWN:
prev += skip_set(prev, wsset);
- fprintf(stderr, "Expected keyword, got:\n%.*s...\n", 30, prev);
- return FAIL;
+ PARSE_FAIL("Expected keyword, got:\n%.*s...\n", 30, prev);
}
prev = bp;
}
- if (states.count) {
- fprintf(stderr, "Expected keyword, got:\n%.*s...\n", 30, bp);
- return FAIL;
- }
+ if (states.count)
+ PARSE_FAIL("Expected keyword, got:\n%.*s...\n", 30, bp);
stack_free(&states);
return OK;
fail:
stack_free(&states);
- free(info);
return FAIL;
}
@@ -167,10 +205,10 @@ parse_file_bin(struct parseinfo *info, char *buf, size_t len)
char *bp, *end = buf + len;
unsigned int i;
- if (len < 84) {
- fprintf(stderr, "Truncated data! (header missing)\n");
- return FAIL;
- }
+ info->type = TYPE_BIN;
+
+ if (len < 84)
+ PARSE_FAIL("Truncated data! (header missing)\n");
/* treat header as model name */
info->namehash = strdup(mhash(buf, 80));
@@ -179,31 +217,45 @@ parse_file_bin(struct parseinfo *info, char *buf, size_t len)
info->loopcount = le32toh(*(uint32_t*)bp);
for (i = 0; i < info->loopcount; i++) {
- if (bp + 50 > end) {
- fprintf(stderr, "Truncated data! (loops missing)\n");
- return FAIL;
- }
+ if (bp + 50 > end)
+ PARSE_FAIL("Truncated data! (loops missing)\n");
+ /* TODO: load floats and calc bounding box */
bp += 50;
}
return OK;
+
+fail:
+ return FAIL;
}
int
parse_file(struct parseinfo *info, char *buf, size_t len)
{
+ int status;
char *bp;
- if (info->valid) {
- NULLFREE(info->infopath);
- NULLFREE(info->namehash);
- NULLFREE(info->stlpath);
- }
+ if (info->valid)
+ free_info(info);
/* check bin vs ascii */
for (bp = buf; strchr(wsset, *bp); bp++);
- return !strncmp("solid ", bp, 6)
+ status = !strncmp("solid ", bp, 6)
? parse_file_ascii(info, buf, len)
: parse_file_bin(info, buf, len);
+ if (status == FAIL) return FAIL;
+
+ printf("LOOPS: %i\n", info->loopcount);
+ /* TODO: create new dir and write parseinfo to files */
+
+ return OK;
+}
+
+void
+free_info(struct parseinfo *info)
+{
+ NULLFREE(info->namehash);
+ NULLFREE(info->stlpath);
+ NULLFREE(info->infopath);
}
diff --git a/service/src/stlfile.h b/service/src/stlfile.h
@@ -7,6 +7,7 @@
#include <endian.h>
enum {
+ KW_INVALID = -1,
KW_UNKNOWN,
KW_SOLID_BEGIN,
KW_SOLID_END,
@@ -34,14 +35,14 @@ struct stack {
};
struct parseinfo {
- char fmtbuf[256];
- char header[80];
- int type, valid;
+ char extra[80];
unsigned int loopcount;
- float bbw, bbh;
+ float bbmin[3], bbmax[3];
+ int type, valid;
char *namehash, *infopath, *stlpath;
};
int parse_file(struct parseinfo *info, char *buf, size_t len);
+void free_info(struct parseinfo *info);
#endif /* STLFILE_H */
diff --git a/service/src/test.sh b/service/src/test.sh
@@ -1,9 +1,42 @@
#!/bin/sh
+set -e
+
+announce() {
+ count=$(echo "$1" | wc -c)
+ python3 -c "
+import math
+s = '$1'
+c = 80
+print()
+print('#'*c)
+print('#' + ' '*math.floor((c - len(s))/2-1) + s + ' '*math.ceil((c - len(s))/2-1) + '#')
+print('#'*c)
+print()
+ "
+}
+
+checkleaks() {
+ valgrind --leak-check=full ./printdoc 2>&1 | tee /tmp/testlog
+ if [ -z "$(grep "no leaks are possible" /tmp/testlog)" ]; then
+ echo "Valgrind exited with errors!"
+ exit 1
+ fi
+}
+
+announce "Trying ASCII STL"
(
echo "help"
echo "submit"
cat tests/sample-ascii.stl | wc -c
cat tests/sample-ascii.stl
-) | valgrind --leak-check=full ./printdoc
+) | checkleaks
+
+announce "Trying BIN STL"
+(
+ echo "help"
+ echo "submit"
+ cat tests/sample-binary.stl | wc -c
+ cat tests/sample-binary.stl
+) | checkleaks
diff --git a/service/src/tests/sample-ascii.stl b/service/src/tests/sample-ascii.stl
@@ -1,5 +1,5 @@
solid test
- facet normal 0 0 1
+ facet normal 0 0 1.0
outer loop
vertex 1 0 0
vertex 1 1 0
diff --git a/service/src/tests/sample-binary.stl b/service/src/tests/sample-binary.stl
Binary files differ.