commit 1eed681548cab75223cd0500b5db32f01ca59a0e
parent 9a9e44aaf1947567ba1461c5e9430c864d979a43
Author: Byron Torres <b@torresjrjr.com>
Date: Thu, 28 Apr 2022 23:00:12 +0100
nl: implement regex line matching
For the -h, -b, and -f options; the p<pattern> behaviour is implemented.
Note, ERE from the stdlib's regex module is used, not BRE as specified
in POSIX nl(1).
Diffstat:
M | nl.ha | | | 144 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------- |
1 file changed, 103 insertions(+), 41 deletions(-)
diff --git a/nl.ha b/nl.ha
@@ -7,22 +7,28 @@ use getopt;
use io;
use main;
use os;
+use regex;
use strconv;
use strings;
use types;
+type style = (all | nonempty | none | regex::regex);
+
+// Number all lines.
+type all = void;
+
+// Number lines which are non-empty.
+type nonempty = void;
+
+// Number no lines.
+type none = void;
+
type section = enum {
HEAD,
BODY,
FOOT,
};
-type style = enum {
- ALL, // number all lines
- TXT, // number lines containing non-whitespace text
- NON, // number no lines
-};
-
type context = struct {
linenum: int,
conblanks: uint, // counter of consecutive blank lines
@@ -54,9 +60,9 @@ export fn utilmain() (void | main::error) = {
static const delim_buf: [2]u8 = [0, 0];
let delim = "\\:";
- let head_style = style::NON;
- let body_style = style::TXT;
- let foot_style = style::NON;
+ let head_style: style = none;
+ let body_style: style = nonempty;
+ let foot_style: style = none;
let paged_numbering = true;
let startnum = 1;
@@ -80,15 +86,25 @@ export fn utilmain() (void | main::error) = {
case 'p' =>
paged_numbering = false;
case 'b' =>
- body_style = switch (opt.1) {
- case "a" =>
- yield style::ALL;
- case "t" =>
- yield style::TXT;
- case "n" =>
- yield style::NON;
- case =>
- usage(help, 'b');
+ body_style = if (strings::hasprefix(opt.1, "p")) {
+ const pat = strings::trimprefix(opt.1, "p");
+ yield match(regex::compile(pat)) {
+ case let re: regex::regex =>
+ yield re;
+ case let err: regex::error =>
+ fmt::fatal("Error: -bp<string>: {}", err);
+ };
+ } else {
+ yield switch (opt.1) {
+ case "a" =>
+ yield all;
+ case "t" =>
+ yield nonempty;
+ case "n" =>
+ yield none;
+ case =>
+ usage(help, 'b');
+ };
};
case 'd' =>
delim = switch (len(opt.1)) {
@@ -100,26 +116,46 @@ export fn utilmain() (void | main::error) = {
usage(help, 'd');
};
case 'f' =>
- foot_style = switch (opt.1) {
- case "a" =>
- yield style::ALL;
- case "t" =>
- yield style::TXT;
- case "n" =>
- yield style::NON;
- case =>
- usage(help, 'f');
+ foot_style = if (strings::hasprefix(opt.1, "p")) {
+ const pat = strings::trimprefix(opt.1, "p");
+ yield match(regex::compile(pat)) {
+ case let re: regex::regex =>
+ yield re;
+ case let err: regex::error =>
+ fmt::fatal("Error: -fp<string>: {}", err);
+ };
+ } else {
+ yield switch (opt.1) {
+ case "a" =>
+ yield all;
+ case "t" =>
+ yield nonempty;
+ case "n" =>
+ yield none;
+ case =>
+ usage(help, 'f');
+ };
};
case 'h' =>
- head_style = switch (opt.1) {
- case "a" =>
- yield style::ALL;
- case "t" =>
- yield style::TXT;
- case "n" =>
- yield style::NON;
- case =>
- usage(help, 'h');
+ head_style = if (strings::hasprefix(opt.1, "p")) {
+ const pat = strings::trimprefix(opt.1, "p");
+ yield match(regex::compile(pat)) {
+ case let re: regex::regex =>
+ yield re;
+ case let err: regex::error =>
+ fmt::fatal("Error: -hp<string>: {}", err);
+ };
+ } else {
+ yield switch (opt.1) {
+ case "a" =>
+ yield all;
+ case "t" =>
+ yield nonempty;
+ case "n" =>
+ yield none;
+ case =>
+ usage(help, 'h');
+ };
};
case 'i' =>
ctx.incr = match (strconv::stoi(opt.1)) {
@@ -169,6 +205,18 @@ export fn utilmain() (void | main::error) = {
usage(help, void);
};
+ defer {
+ if (head_style is regex::regex) {
+ regex::regex_finish(&(head_style as regex::regex));
+ };
+ if (body_style is regex::regex) {
+ regex::regex_finish(&(body_style as regex::regex));
+ };
+ if (foot_style is regex::regex) {
+ regex::regex_finish(&(foot_style as regex::regex));
+ };
+ };
+
const use_file = len(cmd.args) == 1 && cmd.args[0] != "-";
const input: io::handle = if (use_file)
match (os::open(cmd.args[0])) {
@@ -241,9 +289,9 @@ export fn utilmain() (void | main::error) = {
};
};
-fn println(line: str, style: style, ctx: *context) (void | io::error) = {
- switch (style) {
- case style::ALL =>
+fn println(line: str, s: style, ctx: *context) (void | io::error) = {
+ match (s) {
+ case all =>
if (!isblank(line)) {
fmt::printf("{%}", ctx.linenum, ctx.mod)?;
fmt::print(ctx.sep)?;
@@ -258,13 +306,27 @@ fn println(line: str, style: style, ctx: *context) (void | io::error) = {
ctx.conblanks = 0;
};
};
- case style::TXT =>
+ case nonempty =>
if (!isblank(line)) {
fmt::printf("{%}", ctx.linenum, ctx.mod)?;
fmt::print(ctx.sep)?;
ctx.linenum += ctx.incr;
};
- case style::NON => void;
+ case none =>
+ fmt::printf("{%}", "", ctx.mod)?;
+ fmt::print(ctx.sep)?;
+ case let re: regex::regex =>
+ match (regex::find(re, line)) {
+ case []regex::matchgroup =>
+ fmt::printf("{%}", ctx.linenum, ctx.mod)?;
+ fmt::print(ctx.sep)?;
+ ctx.linenum += ctx.incr;
+ case void =>
+ fmt::printf("{%}", "", ctx.mod)?;
+ fmt::print(ctx.sep)?;
+ case let err: regex::error =>
+ fmt::fatal("Error: regex: {}", err);
+ };
};
fmt::println(line)?;
};