aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--LICENSE14
-rw-r--r--Makefile18
-rw-r--r--main.l171
4 files changed, 205 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0836f23
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+ordinal
+lex.yy.c
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..276994d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,14 @@
+BSD Zero Clause License
+
+Copyright © 2023 Thomas Voss
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+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..ee0b462
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,18 @@
+target = ordinal
+
+CC = cc
+LEX = flex
+CFLAGS = \
+ -Wall -Wextra -Wpedantic -Werror \
+ -O3 -march=native -mtune=native -pipe
+LDLIBS = -lfl
+
+all: ${target}
+${target}: lex.yy.c
+ ${CC} ${CFLAGS} ${LDLIBS} -o $@ $<
+
+lex.yy.c: main.l
+ ${LEX} $<
+
+clean:
+ rm -f ${target} lex.yy.c
diff --git a/main.l b/main.l
new file mode 100644
index 0000000..9478c16
--- /dev/null
+++ b/main.l
@@ -0,0 +1,171 @@
+%{
+#include <err.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define YY_USER_ACTION ECHO;
+
+const char empty_field[] = "argument to the -p option contains an empty field";
+const char invalid_char[] = "argument to the -p option must be a "
+ "comma-seperated list of positive integers";
+const char final_comma[] = "argument to the -p option contains trailing comma";
+
+const char *argv0;
+unsigned pos;
+struct {
+ unsigned *ps;
+ size_t len;
+} positions;
+
+static void usage(void);
+static void append_positions(char *s);
+static int ucmp(const void *, const void *);
+static unsigned atou(const char *);
+static bool print_ordinal(void);
+%}
+
+%option nodefault noinput nounput
+
+%%
+
+[0-9]*(1[0-9]|[04-9]) { pos++; if (print_ordinal()) fputs("th", stdout); }
+[0-9]*1 { pos++; if (print_ordinal()) fputs("st", stdout); }
+[0-9]*2 { pos++; if (print_ordinal()) fputs("nd", stdout); }
+[0-9]*3 { pos++; if (print_ordinal()) fputs("rd", stdout); }
+
+\n { pos = 0; }
+[^0-9\n]+ ;
+
+%%
+
+void
+usage(void)
+{
+ fprintf(stderr, "Usage: %s [-p indicies ...] [file ...]\n", argv0);
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char **argv)
+{
+ int opt, rv;
+ struct option longopts[] = {
+ {"positions", required_argument, 0, 'p'},
+ { NULL, 0, 0, 0 }
+ };
+
+ argv0 = argv[0];
+ rv = EXIT_SUCCESS;
+
+ while ((opt = getopt_long(argc, argv, "p:", longopts, NULL)) != -1) {
+ switch (opt) {
+ case 'p':
+ append_positions(optarg);
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ yylex();
+ for (int i = 0; i < argc; i++) {
+ if (strcmp(argv[i], "-") == 0)
+ yyin = stdin;
+ else if ((yyin = fopen(argv[i], "r")) == NULL) {
+ warn("fopen: %s", argv[i]);
+ rv = EXIT_FAILURE;
+ continue;
+ }
+ yylex();
+ if (yyin != stdin)
+ fclose(yyin);
+ }
+
+ return rv;
+}
+
+void
+append_positions(char *raw)
+{
+ bool comma = true;
+ const char *s;
+
+ if (positions.ps != NULL) {
+ warnx("the -p option should only be provided once");
+ usage();
+ }
+
+ for (s = raw, positions.len++; *s; s++) {
+ switch (*s) {
+ case ',':
+ if (comma)
+ errx(EXIT_FAILURE, empty_field);
+ comma = true;
+ positions.len++;
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ comma = false;
+ break;
+ default:
+ errx(EXIT_FAILURE, invalid_char);
+ }
+ }
+
+ if (comma == true)
+ errx(EXIT_FAILURE, final_comma);
+
+ if ((positions.ps = malloc(sizeof(unsigned *) * positions.len)) == NULL)
+ err(EXIT_FAILURE, "malloc");
+
+ /* Iterate over each position, parse it, and sort the results. It’s
+ safe to use strtok() and atou() here since the input string has been
+ pre-validated. */
+ s = strtok(raw, ",");
+ for (size_t i = 0; s != NULL; i++) {
+ positions.ps[i] = atou(s);
+ s = strtok(NULL, ",");
+ }
+ qsort(positions.ps, positions.len, sizeof(unsigned), ucmp);
+}
+
+int
+ucmp(const void *a, const void *b)
+{
+ unsigned a_ = *(unsigned *)a,
+ b_ = *(unsigned *)b;
+ return a_ > b_ ? 1 : b_ < a_ ? -1 : 0;
+}
+
+unsigned
+atou(const char *s)
+{
+ unsigned n = 0;
+ for (; *s; s++)
+ n = n * 10 + *s - '0';
+ return n;
+}
+
+bool
+print_ordinal(void)
+{
+ /* Basic binary search */
+ for (size_t l = 0, r = positions.len - 1, m; l <= r;) {
+ m = l + (r - l) / 2;
+ if (positions.ps[m] == pos)
+ return true;
+ if (positions.ps[m] > pos)
+ r = m - 1;
+ else
+ l = m + 1;
+ }
+
+ return false;
+}