From e90a213afd92e8bd5db19629c181c0fd572faf42 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Sat, 23 May 2026 20:53:27 +0200 Subject: First commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- .gitignore | 19 +++++ LICENSE | 15 ++++ Makefile | 45 +++++++++++ README | 31 ++++++++ arg.h | 88 ++++++++++++++++++++++ braces.1 | 54 ++++++++++++++ braces.c | 199 +++++++++++++++++++++++++++++++++++++++++++++++++ config-coverage-gcc.mk | 14 ++++ config.mk | 16 ++++ 9 files changed, 481 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100644 arg.h create mode 100644 braces.1 create mode 100644 braces.c create mode 100644 config-coverage-gcc.mk create mode 100644 config.mk diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a23a4ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +*\#* +*~ +*.o +*.a +*.t +*.f +*.lo +*.to +*.fo +*.su +*.so +*.so.* +*.dll +*.dylib +*.gch +*.gcov +*.gcno +*.gcda +/braces diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..884dfaa --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +ISC License + +© 2018, 2026 Mattias Andrée + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2bff896 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +.POSIX: + +CONFIGFILE = config.mk +include $(CONFIGFILE) + +OBJ =\ + braces.o + +HDR =\ + arg.h + + +ALL_CPPFLAGS = $(CPPFLAGS) $(FUZZED_CPPFLAGS) +ALL_CFLAGS = $(CFLAGS) $(FUZZED_CFLAGS) +ALL_LDFLAGS = $(LDFLAGS) $(FUZZED_LDFLAGS) + + +all: braces +$(OBJ): $(HDR) + +.c.o: + $(CC) -c -o $@ $< $(ALL_CFLAGS) $(GOV_CFLAGS) $(ALL_CPPFLAGS) $(GOV_CPPFLAGS) + +braces: $(OBJ) + $(CC) -o $@ $(OBJ) $(ALL_LDFLAGS) $(COV_LDFLAGS) + +install: braces + mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" + mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1/" + cp -- braces "$(DESTDIR)$(PREFIX)/bin/" + cp -- braces.1 "$(DESTDIR)$(MANPREFIX)/man1/" + +uninstall: + -rm -f -- "$(DESTDIR)$(PREFIX)/bin/braces" + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/braces.1" + +clean: + -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.gch + -rm -f -- *.gcov *.gcno *.gcda *.t *.to *.f *.fo + -rm -f -- braces + +.SUFFIXES: +.SUFFIXES: .o .c + +.PHONY: all install uninstall clean diff --git a/README b/README new file mode 100644 index 0000000..d8a6663 --- /dev/null +++ b/README @@ -0,0 +1,31 @@ +NAME + braces - count number of braces in code + +SYNOPSIS + braces [-s] [file] ... + +DESCRIPTION + braces counts the number of braces in code for each specified + file. braces will treat each file as a C source code or + C header file. If file is -, or if a file is not specified, + the standard input is read. + + If more than one file is specified, each successfully + processed file will have its line count output in the format + + "%s:%*s%zu\n", , , "", <#braces in code> + + The total for all processed files is printed on the last line + with the format + + "%zu\n", + + If exactly one or no files are specified, this will be the + only line output. + +OPTIONS + -s + Only output the last line. (The total over all files.) + +SEE ALSO + semicolons(1) diff --git a/arg.h b/arg.h new file mode 100644 index 0000000..b280e69 --- /dev/null +++ b/arg.h @@ -0,0 +1,88 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][0] == '-') {\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +/* Handles obsolete -NUM syntax */ +#define ARGNUM case '0':\ + case '1':\ + case '2':\ + case '3':\ + case '4':\ + case '5':\ + case '6':\ + case '7':\ + case '8':\ + case '9' + +#define ARGALT(SYMBOL) }\ + } else if (argv[0][0] == SYMBOL) {\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +#define ARGEND }\ + } else {\ + break;\ + }\ + } + +#define ARGC() argc_ + +#define ARGNUMF() (brk_ = 1, estrtonum(argv[0], 0, INT_MAX)) + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define UARGF() EARGF(usage()) + +#define LNGARG() &argv[0][0] + +#define UNOFLAGS(...) ARGBEGIN {\ + default:\ + usage();\ + } ARGEND;\ + if (__VA_ARGS__)\ + usage() + + +#endif diff --git a/braces.1 b/braces.1 new file mode 100644 index 0000000..f70e430 --- /dev/null +++ b/braces.1 @@ -0,0 +1,54 @@ +.TH BRACES 1 BRACES +.SH NAME +braces - count number of brace pairs in code + +.SH SYNOPSIS +.B braces +[-s] +.RI [ file "] ..." + +.SH DESCRIPTION +.B braces +counts the number of brace pairs in code for each specified +file. +.B braces +will treat each file as a C source code or +C header file. If +.I file +is +.BR - , +or if a +.I file +is not specified, the standard input is read. +.PP +If more than one file is specified, each successfully +processed file will have its line count output in the format +.RS +.nf + +\fB\(dq%s:%*s%zu\en\(dq,\fP <\fIfile\fP>\fB,\fP <\fIsome positive integer\fP>\fB, \(dq\(dq,\fP <\fI#brace pairs in code\fP> + +.fi +.RE +The total for all processed files is printed on the last line +with the format +.RS +.nf + +\fB\(dq%zu\en\(dq,\fP <\fItotal #braces in code\fP> + +.fi +.RE +If exactly one or no files are specified, this will be the +only line output. + +.SH OPTIONS +.TP +.B -s +Only output the last line. (The total over all files.) + +.SH NOTES +The result is unspecified for cases where there are unmatched braces. + +.SH SEE ALSO +.BR semicolons (1) diff --git a/braces.c b/braces.c new file mode 100644 index 0000000..8c464b2 --- /dev/null +++ b/braces.c @@ -0,0 +1,199 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arg.h" + + +struct result { + ssize_t n; + int width; +}; + + +char *argv0; + + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-s] [file] ...\n", argv0); + exit(1); +} + + +static ssize_t +count(int fd, const char *fname) +{ + char buf[BUFSIZ], c; + ssize_t i, n, ret = 0; + char quote = 0; + char comment = 0; + signed char escape = 0; + signed char slash = 0; + signed char asterisk = 0; + + for (;;) { + n = read(fd, buf, sizeof(buf)); + if (n <= 0) { + if (!n) + break; + if (errno == EINTR) + continue; + fprintf(stderr, "%s: %s :%s\n", argv0, fname, strerror(errno)); + return -1; + } + + for (i = 0; i < n; i++) { + c = buf[i]; + + again: + if (slash) { + slash = 0; + if (c == '*' || c == '/') + comment = c; + else + goto again; + } else if (comment == '/') { + if (c == '\n') + comment = 0; + } else if (comment == '*') { + if (c == '*') { + asterisk = 1; + } else if (asterisk) { + asterisk = 0; + if (c == '/') + comment = 0; + } + } else if (escape) { + escape = 0; + } else if (quote) { + if (c == '\\') + escape = 1; + else if (c == quote) + quote = 0; + } else if (c == '/') { + slash = 1; + } else if (c == '"' || c == '\'') { + quote = c; + } else if (c == '{') { + ret += 1; + } + } + } + + return ret; +} + + +static ssize_t +open_and_count(const char *path) +{ + ssize_t r; + int fd; + + if (!path || !strcmp(path, "-")) { + r = count(STDIN_FILENO, ""); + } else { + fd = open(path, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "%s: %s: %s\n", argv0, path, strerror(errno)); + return -1; + } + r = count(fd, path); + close(fd); + } + + return r; +} + + +static int +strwidth(const char *s) +{ + size_t wn, n = strlen(s) + 1u; + wchar_t *ws = calloc(n, sizeof(*ws)); + int r = -1; + + if (!ws) { + fprintf(stderr, "%s: %s\n", argv0, strerror(errno)); + exit(1); + } + + wn = mbstowcs(ws, s, n); + if (wn != (size_t)-1) + r = wcswidth(ws, wn); + + free(ws); + return r < 0 ? (int)n - 1 : r; +} + + +int +main(int argc, char *argv[]) +{ + struct result *results; + int sum_only = 0; + int ret = 0; + ssize_t r; + size_t total = 0u; + int i, left, right; + int maxwidth = 0; + + ARGBEGIN { + case 's': + sum_only = 1; + break; + default: + usage(); + } ARGEND; + + setlocale(LC_ALL, ""); + + if (argc < 2) { + r = open_and_count(argv[0]); + if (r < 0) + return 1; + printf("%zi\n", r); + } else { + results = calloc((size_t)argc, sizeof(*results)); + if (!results) { + fprintf(stderr, "%s: %s\n", argv0, strerror(errno)); + exit(1); + } + for (i = 0; i < argc; i++) { + results[i].n = open_and_count(argv[0]); + if (results[i].n < 0) { + ret = 1; + continue; + } + left = strwidth(argv[i]); + right = snprintf(NULL, 0u, "%zi", results[i].n); + results[i].width = left + right; + if (maxwidth < results[i].width) + maxwidth = results[i].width; + } + for (i = 0; i < argc; i++) { + if (results[i].n < 0) + continue; + total += (size_t)results[i].n; + if (sum_only) + continue; + printf("%s:%*s %zi\n", argv[i], maxwidth - results[i].width, "", results[i].n); + } + free(results); + printf("%zu\n", total); + } + + if (fflush(stdout) || ferror(stdout) || fclose(stdout)) { + fprintf(stderr, "%s: : %s\n", argv0, strerror(errno)); + exit(1); + } + return ret; +} diff --git a/config-coverage-gcc.mk b/config-coverage-gcc.mk new file mode 100644 index 0000000..60f81f8 --- /dev/null +++ b/config-coverage-gcc.mk @@ -0,0 +1,14 @@ +CONFIGFILE_PROPER = config.mk +include $(CONFIGFILE_PROPER) + +CC = $(CC_PREFIX)gcc -std=c99 +GCOV = gcov + +COV_CPPFLAGS = -DCOVERAGE_TEST +COV_CFLAGS = --coverage -g -O0 +COV_LDFLAGS = --coverage -g -O0 + +G = + +coverage: check + $(GCOV) -pr -- *.gcda 2>&1 diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..0d98cb2 --- /dev/null +++ b/config.mk @@ -0,0 +1,16 @@ +PREFIX = /usr +MANPREFIX = $(PREFIX)/share/man + +CC = c99 + +COMMON_SANITIZE = -fsanitize=alignment,shift,signed-integer-overflow,object-size,null,undefined,bounds,address +CLANG_SANITIZE = -O1 $(COMMON_SANITIZE),cfi -flto -fvisibility=hidden -fno-sanitize-trap=cfi +GCC_SANITIZE = -O1 $(COMMON_SANITIZE) +#SANITIZE = $(CLANG_SANITIZE) +#SANITIZE = $(GCC_SANITIZE) + +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE +CFLAGS = $(SANITIZE) +LDFLAGS = $(SANITIZE) + +G = -g -- cgit v1.3.1