commit 45f493cefffe7e206f70510bbc55c5c3a0aad166 Author: Neil Date: Sun Mar 21 11:11:08 2021 -0700 Things. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7a1240e --- /dev/null +++ b/Makefile @@ -0,0 +1,181 @@ +# GNU Make 3.81; MacOSX gcc 4.2.1; MacOSX MinGW 4.3.0 + +# https://stackoverflow.com/questions/18136918/how-to-get-current-relative-directory-of-your-makefile +mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) +current_dir := $(notdir $(patsubst %/,%,$(dir $(mkfile_path)))) + +project := $(current_dir) + +# dirs +src := src +test := test +build := build +bin := bin +backup := backup +doc := doc +media := media +#lemon := lemon +PREFIX := /usr/local + +# files in $(bin) +install := $(project)-`date +%Y-%m-%d` + +# extra stuff we should back up +extra := $(project).xcodeproj + +# John Graham-Cumming: rwildcard is a recursive wildcard +rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) \ +$(filter $(subst *,%,$2),$d)) + +java_srcs := $(call rwildcard, $(src), *.java) +c_srcs := $(call rwildcard, $(src), *.c) +h_srcs := $(call rwildcard, $(src), *.h) +c_re_srcs := $(call rwildcard, $(src), *.c.re) +c_rec_srcs := $(call rwildcard, $(src), *.c.re_c) +y_srcs := $(call rwildcard, $(src), *.y) +c_tests := $(call rwildcard, $(test), *.c) +h_tests := $(call rwildcard, $(test), *.h) +icons := $(call rwildcard, $(media), *.ico) + +# combinations +all_h := $(h_srcs) $(h_tests) +all_srcs := $(java_srcs) $(c_srcs) $(c_re_srcs) $(c_rec_srcs) $(y_srcs) +all_tests := $(c_tests) +all_icons := $(icons) + +java_class := $(patsubst $(src)/%.java, $(build)/%.class, $(java_srcs)) +c_objs := $(patsubst $(src)/%.c, $(build)/%.o, $(c_srcs)) +# must not conflict, eg, foo.c.re and foo.c would go to the same thing +c_re_builds := $(patsubst $(src)/%.c.re, $(build)/%.c, $(c_re_srcs)) +c_rec_builds := $(patsubst $(src)/%.c.re_c, $(build)/%.c, $(c_rec_srcs)) +c_y_builds := $(patsubst $(src)/%.y, $(build)/%.c, $(y_srcs)) +# together .re/.re_c/.y +c_other_objs := $(patsubst $(build)/%.c, $(build)/%.o, $(c_re_builds) \ +$(c_rec_builds) $(c_y_builds)) +test_c_objs := $(patsubst $(test)/%.c, $(build)/$(test)/%.o, $(c_tests)) +html_docs := $(patsubst $(src)/%.c, $(doc)/%.html, $(c_srcs)) + +cdoc := cdoc +re2c := re2c +mkdir := mkdir -p +cat := cat +zip := zip +bison := bison +#lemon := lemon + +CC := clang #gcc +CF := -Wall -Wextra -Wno-format-y2k -Wstrict-prototypes \ +-Wmissing-prototypes -Wpointer-arith -Wreturn-type -Wcast-qual -Wwrite-strings \ +-Wswitch -Wshadow -Wcast-align -Wbad-function-cast -Wchar-subscripts -Winline \ +-Wnested-externs -Wredundant-decls -Wfatal-errors -O3 -ffast-math \ +-funroll-loops -pedantic -ansi -g # or -std=c99 -mwindows +OF := -O3 # -framework OpenGL -framework GLUT or -lglut -lGLEW + +# Jakob Borg and Eldar Abusalimov +# $(ARGS) is all the extra arguments; $(BRGS) is_all_the_extra_arguments +EMPTY := +SPACE := $(EMPTY) $(EMPTY) +ifeq (backup, $(firstword $(MAKECMDGOALS))) + ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) + BRGS := $(subst $(SPACE),_,$(ARGS)) + ifneq (,$(BRGS)) + BRGS := -$(BRGS) + endif + $(eval $(ARGS):;@:) +endif + +###### +# compiles the programme by default + +default: $(bin)/$(project) + # . . . success; executable is in $(bin)/$(project) + +docs: $(html_docs) + +# linking +$(bin)/$(project): $(c_objs) $(c_other_objs) $(test_c_objs) + # linking rule + @$(mkdir) $(bin) + $(CC) $(OF) -o $@ $^ + +# compiling +#$(lemon)/$(bin)/$(lem): $(lemon)/$(src)/lemon.c +# # compiling lemon +# @$(mkdir) $(lemon)/$(bin) +# $(CC) $(CF) -o $@ $< + +$(c_objs): $(build)/%.o: $(src)/%.c $(all_h) + # c_objs rule + @$(mkdir) $(build) + $(CC) $(CF) -c -o $@ $< + +$(c_other_objs): $(build)/%.o: $(build)/%.c $(all_h) + # c_other_objs rule + $(CC) $(CF) -c -o $@ $< + +$(test_c_objs): $(build)/$(test)/%.o: $(test)/%.c $(all_h) + # test_c_objs rule + @$(mkdir) $(build) + @$(mkdir) $(build)/$(test) + $(CC) $(CF) -c -o $@ $< + +$(c_re_builds): $(build)/%: $(src)/%.re + # *.re build rule + @$(mkdir) $(build) + $(re2c) -W -T -o $@ $< + +$(c_rec_builds): $(build)/%: $(src)/%.re_c + # *.re_c (conditions) build rule + @$(mkdir) $(build) + $(re2c) -W -T -c -o $@ $< + +$(c_y_builds): $(build)/%.c: $(src)/%.y # $(lemon)/$(bin)/$(lem) + # .y rule + @$(mkdir) $(build) + $(bison) -o $@ $< + +$(html_docs): $(doc)/%.html: $(src)/%.c $(src)/%.h + # docs rule + @$(mkdir) $(doc) + cat $^ | $(cdoc) > $@ + +###### +# phoney targets + +.PHONY: setup clean backup icon install uninstall test docs + +clean: + -rm -f $(c_objs) $(test_c_objs) $(c_other_objs) $(c_re_builds) \ +$(c_rec_builds) $(html_docs) + -rm -rf $(bin)/$(test) + +backup: + @$(mkdir) $(backup) + $(zip) $(backup)/$(project)-`date +%Y-%m-%dT%H%M%S`$(BRGS).zip \ +readme.txt Makefile $(all_h) $(all_srcs) $(all_tests) $(all_icons) + +icon: default + # . . . setting icon on a Mac. + cp $(media)/$(icon) $(bin)/$(icon) + -sips --addIcon $(bin)/$(icon) + -DeRez -only icns $(bin)/$(icon) > $(bin)/$(RSRC) + -Rez -append $(bin)/$(RSRC) -o $(bin)/$(project) + -SetFile -a C $(bin)/$(project) + +setup: default icon + @$(mkdir) $(bin)/$(install) + cp $(bin)/$(project) readme.txt $(bin)/$(install) + rm -f $(bin)/$(install)-MacOSX.dmg + # or rm -f $(BDIR)/$(INST)-Win32.zip + hdiutil create $(bin)/$(install)-MacOSX.dmg -volname "$(project)" -srcfolder $(bin)/$(install) + # or zip $(BDIR)/$(INST)-Win32.zip -r $(BDIR)/$(INST) + rm -R $(bin)/$(install) + +install: default + @$(mkdir) -p $(DESTDIR)$(PREFIX)/bin + cp $(bin)/$(project) $(DESTDIR)$(PREFIX)/bin/$(project) + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/$(project) + +docs: $(html_docs) diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..031ae45 --- /dev/null +++ b/src/main.c @@ -0,0 +1,65 @@ +#include /* EXIT_ strtol */ +#include /* FILE fopen fclose fread */ +#include /* errno */ +#include /* isdigit */ +#include /* LONG_ INT_ _MIN _MAX */ +#include /* assert */ +#include "min_array.h" + +MIN_ARRAY(char, char) +MIN_ARRAY(num, int) + +/** Concatenates the contents of `fp` after the file pointer to `string`. The + file pointer is modified. Zeros are preserved, as well as appending a + null-terminator. @return Success, otherwise `string` may have read a partial + read and may not be terminated. @throws[fread, malloc] */ +static int cat_fp_to_str(FILE *const fp, struct char_array *const string) { + const size_t granularity = 1024; + size_t nread; + char *cursor; + assert(fp && string); + do { + if(!(cursor = char_array_buffer(string, granularity))) return 0; + char_array_emplace(string, nread = fread(cursor, 1, granularity, fp)); + } while(nread == granularity); assert(nread < granularity); + if(ferror(fp) || !(cursor = char_array_new(string))) return 0; + *cursor = '\0'; + return 1; +} + +int main(void) { + int success = EXIT_FAILURE; + struct char_array str = MIN_ARRAY_IDLE; + struct num_array nums = MIN_ARRAY_IDLE; + int *num; + long big_num; + char *a, *anum = 0; + size_t i; + errno = 0; /* In case we are running it as part of another editor. */ + if(!cat_fp_to_str(stdin, &str)) goto catch; + /*printf("<<<%s>>>\n", str.data);*/ + /* The first sentinel '\0' it stops, even though it could have more data, + and we could conceivably use the length to continue; it's simple. */ + for(a = str.data; a = strpbrk(a, "-0123456789"); ) { + if(*a == '-') { anum = a++; continue; } + if(!(num = num_array_new(&nums))) goto catch; + if(!anum || anum != a - 1) anum = a; /* Wasn't negative. */ + big_num = strtol(anum, &a, 0); + if((!big_num || big_num == LONG_MIN || big_num == LONG_MAX) && errno) + goto catch; /* Long conversion failed. */ + if(big_num < INT_MIN || big_num > INT_MAX) + { errno = ERANGE; goto catch; } + *num = (int)big_num; /* Safe now. */ + } + for(i = 0; i < nums.size; i++) printf("Extracted %d.\n", nums.data[i]); + success = EXIT_SUCCESS; + goto finally; +catch: + perror("stdin"); +finally: + printf("Freeeee!! str %lu, nums %lu\n", str.capacity, nums.capacity); + char_array_(&str); + num_array_(&nums); + printf("yessss.\n"); + return success; +} diff --git a/src/min_array.h b/src/min_array.h new file mode 100644 index 0000000..cfdca11 --- /dev/null +++ b/src/min_array.h @@ -0,0 +1,78 @@ +/** X-macro for a minimal dynamic array. `MIN_ARRAY(name, type)`, where `name` + is an identifier prefix that satisfies `C` naming conventions when mangled and + `type` is defined tag-type associated therewith. When expanding the array, + resizing may be necessary and incurs amortised cost; any pointers to this + memory may become stale. */ + +#include /* size_t realloc free */ +#include /* memmove strcmp memcpy */ +#include /* errno */ +#include /* assert */ + +#define MIN_ARRAY_IDLE { 0, 0, 0 } +#define MIN_ARRAY(name, type) \ +struct name##_array { type *data; size_t size, capacity; }; \ +/** Initialises `a` to idle. */ \ +static void name##_array(struct name##_array *const a) \ + { assert(a), a->data = 0, a->capacity = a->size = 0; } \ +/** Destroys `a` and returns it to idle. */ \ +static void name##_array_(struct name##_array *const a) \ + { assert(a), free(a->data), name##_array(a); } \ +/** Ensures `min_capacity` of `a`. @param[min_capacity] If zero, does nothing. +@return Success; otherwise, `errno` will be set. +@throws[ERANGE] Tried allocating more then can fit in `size_t` or `realloc` +doesn't follow POSIX. @throws[realloc, ERANGE] */ \ +static int name##_array_reserve(struct name##_array *const a, \ + const size_t min_capacity) { \ + size_t c0; \ + type *data; \ + const size_t max_size = (size_t)-1 / sizeof *a->data; \ + assert(a); \ + if(a->data) { \ + if(min_capacity <= a->capacity) return 1; \ + c0 = a->capacity; \ + } else { /* Idle. */ \ + if(!min_capacity) return 1; \ + c0 = 1; \ + } \ + if(min_capacity > max_size) return errno = ERANGE, 0; \ + /* `c_n = a1.625^n`, approximation golden ratio `\phi ~ 1.618`. */ \ + while(c0 < min_capacity) { \ + size_t c1 = c0 + (c0 >> 1) + (c0 >> 3) + 1; \ + if(c0 > c1) { c0 = max_size; break; } \ + c0 = c1; \ + } \ + if(!(data = realloc(a->data, sizeof *a->data * c0))) \ + { if(!errno) errno = ERANGE; return 0; } \ + a->data = data, a->capacity = c0; \ + return 1; \ +} \ +/** Adds at least `buffer` un-initialised and uncounted elements at the back of + `a`. @return A pointer to the start of `buffer` elements, namely + `data + size`. If `a` is idle and `buffer` is zero, a null pointer is + returned, otherwise this indicates an error. @throws[realloc, ERANGE] */ \ +static type *name##_array_buffer(struct name##_array *const a, \ + const size_t buffer) { \ + assert(a); \ + if(a->size > (size_t)-1 - buffer) \ + { errno = ERANGE; return 0; } /* Unlikely. */ \ + if(!name##_array_reserve(a, a->size + buffer)) return 0; \ + return a->data ? a->data + a->size : 0; \ +} \ +/** Emplaces `n` elements on `a`. `n` must be smaller than or equal to the + highest remaining _array_buffer>. */ \ +static void name##_array_emplace(struct name##_array *const a, \ + const size_t n) { \ + assert(a && a->capacity >= a->size && n <= a->capacity - a->size); \ + a->size += n; \ +} \ +/** @return Push back a new un-initialized datum of `a`. +@throws[realloc, ERANGE] */ \ +static type *name##_array_new(struct name##_array *const a) { \ + type *const data = name##_array_buffer(a, 1); \ + return data ? name##_array_emplace(a, 1), data : 0; \ +} \ +/* They don't have to all be used, (destructor is important.) */ \ +static void name##_unused_coda(void); static void name##_unused(void) { \ + name##_array(0); name##_array_new(0); name##_unused_coda(); } \ +static void name##_unused_coda(void) { name##_unused(); }