diff options
| author | Mattias Andrée <m@maandree.se> | 2026-05-23 20:53:27 +0200 |
|---|---|---|
| committer | Mattias Andrée <m@maandree.se> | 2026-05-23 21:09:19 +0200 |
| commit | 41305ac7747516f86d03d0b750816c02e624ac4c (patch) | |
| tree | 152c00464676b95fdbb60ed29dd6526bbaed93cf | |
| download | semicolons-41305ac7747516f86d03d0b750816c02e624ac4c.tar.gz semicolons-41305ac7747516f86d03d0b750816c02e624ac4c.tar.bz2 semicolons-41305ac7747516f86d03d0b750816c02e624ac4c.tar.xz | |
First commit1.0
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to '')
| -rw-r--r-- | .gitignore | 19 | ||||
| -rw-r--r-- | LICENSE | 15 | ||||
| -rw-r--r-- | Makefile | 45 | ||||
| -rw-r--r-- | README | 31 | ||||
| -rw-r--r-- | arg.h | 88 | ||||
| -rw-r--r-- | config-coverage-gcc.mk | 14 | ||||
| -rw-r--r-- | config.mk | 16 | ||||
| -rw-r--r-- | semicolons.1 | 51 | ||||
| -rw-r--r-- | semicolons.c | 199 |
9 files changed, 478 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..762db5a --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +*\#* +*~ +*.o +*.a +*.t +*.f +*.lo +*.to +*.fo +*.su +*.so +*.so.* +*.dll +*.dylib +*.gch +*.gcov +*.gcno +*.gcda +/semicolons @@ -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..68831a2 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +.POSIX: + +CONFIGFILE = config.mk +include $(CONFIGFILE) + +OBJ =\ + semicolons.o + +HDR =\ + arg.h + + +ALL_CPPFLAGS = $(CPPFLAGS) $(FUZZED_CPPFLAGS) +ALL_CFLAGS = $(CFLAGS) $(FUZZED_CFLAGS) +ALL_LDFLAGS = $(LDFLAGS) $(FUZZED_LDFLAGS) + + +all: semicolons +$(OBJ): $(HDR) + +.c.o: + $(CC) -c -o $@ $< $(ALL_CFLAGS) $(GOV_CFLAGS) $(ALL_CPPFLAGS) $(GOV_CPPFLAGS) + +semicolons: $(OBJ) + $(CC) -o $@ $(OBJ) $(ALL_LDFLAGS) $(COV_LDFLAGS) + +install: semicolons + mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" + mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1/" + cp -- semicolons "$(DESTDIR)$(PREFIX)/bin/" + cp -- semicolons.1 "$(DESTDIR)$(MANPREFIX)/man1/" + +uninstall: + -rm -f -- "$(DESTDIR)$(PREFIX)/bin/semicolons" + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/semicolons.1" + +clean: + -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.gch + -rm -f -- *.gcov *.gcno *.gcda *.t *.to *.f *.fo + -rm -f -- semicolons + +.SUFFIXES: +.SUFFIXES: .o .c + +.PHONY: all install uninstall clean @@ -0,0 +1,31 @@ +NAME + semicolons - count number of semicolons in code + +SYNOPSIS + semicolons [-s] [file] ... + +DESCRIPTION + semicolons counts the number of semicolons in code for each specified + file. semicolons 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>, "", <#semicolons in code> + + The total for all processed files is printed on the last line + with the format + + "%zu\n", <total #semicolons 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 + braces(1) @@ -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/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 diff --git a/semicolons.1 b/semicolons.1 new file mode 100644 index 0000000..a3cd082 --- /dev/null +++ b/semicolons.1 @@ -0,0 +1,51 @@ +.TH SEMICOLONS 1 SEMICOLONS +.SH NAME +semicolons - count number of semicolons in code + +.SH SYNOPSIS +.B semicolons +[-s] +.RI [ file "] ..." + +.SH DESCRIPTION +.B semicolons +counts the number of semicolons in code for each specified +file. +.B semicolons +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#semicolons 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 #semicolons 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 SEE ALSO +.BR braces (1) diff --git a/semicolons.c b/semicolons.c new file mode 100644 index 0000000..6a9fbbc --- /dev/null +++ b/semicolons.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; +} |
