aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2026-05-23 20:53:27 +0200
committerMattias Andrée <m@maandree.se>2026-05-23 21:09:31 +0200
commite90a213afd92e8bd5db19629c181c0fd572faf42 (patch)
tree79cefe9439504d7eac8ba431d972028802fdda0d
downloadbraces-1.0.tar.gz
braces-1.0.tar.bz2
braces-1.0.tar.xz
First commit1.0
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to '')
-rw-r--r--.gitignore19
-rw-r--r--LICENSE15
-rw-r--r--Makefile45
-rw-r--r--README31
-rw-r--r--arg.h88
-rw-r--r--braces.154
-rw-r--r--braces.c199
-rw-r--r--config-coverage-gcc.mk14
-rw-r--r--config.mk16
9 files changed, 481 insertions, 0 deletions
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 <m@maandree.se>
+
+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", <file>, <some positive integer>, "", <#braces in code>
+
+ The total for all processed files is printed on the last line
+ with the format
+
+ "%zu\n", <total #braces in code>
+
+ 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 <errno.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wchar.h>
+
+#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, "<stdin>");
+ } 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: <stdout>: %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