43 Commits

Author SHA1 Message Date
800a097903 swab(1): uses default for promises 2024-09-10 17:14:02 -06:00
1ccdc65d30 rpn(1): uses default for promises 2024-09-10 17:13:20 -06:00
df0a236f81 mm(1): uses default for Promises 2024-09-10 17:12:14 -06:00
e385d873ec intcmp(1): uses default value for Promises 2024-09-10 17:11:22 -06:00
73a75a32df hru(1): uses default Promises value for child processes 2024-09-10 17:10:26 -06:00
b8e5901d97 libopenbsd.rs(3): adds default value for Promises 2024-09-10 17:09:46 -06:00
62ce288524 rpn(1): makes rounding more efficient 2024-09-04 22:12:44 -06:00
731e62ee7e rpn(1): made evaluation function more readable 2024-08-28 18:24:49 -06:00
f50bfeea92 fop(1): fixes panic on no arguments 2024-08-28 18:05:56 -06:00
8d00fa0afd fop(1): minor formatting change 2024-08-28 00:46:32 -06:00
9f520df82b strcmp(1): adds null unveil 2024-08-28 00:31:02 -06:00
41982c2f73 str(1): adds null unveil 2024-08-28 00:30:45 -06:00
6166a3ca36 npc(1): adds null unveil 2024-08-28 00:26:26 -06:00
efedcd3ae4 true(1), false(1): adds null unveils 2024-08-28 00:24:35 -06:00
8fd5bdf5a6 swab(1): adds null unveil 2024-08-28 00:15:15 -06:00
b946469da5 rpn(1): adds null unveil 2024-08-28 00:13:21 -06:00
06384c72fb intcmp(1): adds null unveil 2024-08-28 00:09:40 -06:00
9aeadd8f8f hru(1): adds null unveil 2024-08-27 23:57:12 -06:00
5b666d6858 fop(1): adds null unveil 2024-08-27 23:54:39 -06:00
b875aa1058 rpn(1): more improvements 2024-08-24 19:00:01 -06:00
cc53dab035 rpn(1): handles errors when writing to stdout 2024-08-24 18:18:27 -06:00
232629f4d3 hru(1): makes more efficient 2024-08-24 17:55:00 -06:00
ff4ff825bd swab(1): changes disparate error handling functions to one function 2024-08-24 17:26:26 -06:00
5eb71e90a3 tests/bonsai: rpn.mk: tests the standard input 2024-08-24 17:22:57 -06:00
a0138be79e rpn(1): refactor to make code more efficient, readable, and maintainable 2024-08-24 17:22:02 -06:00
150fa22f35 fop(1): changes casts to calls to .into() 2024-08-24 15:57:55 -06:00
f104598164 mm(1): updates error-handling functions and uncovers issue with error reporting 2024-08-24 15:53:58 -06:00
722679247a hru(1): uses new sysexits 2024-08-23 14:31:02 -06:00
4db4160019 swab(1): uses new sysexits 2024-08-23 14:23:11 -06:00
928dce0234 fop(1): uses new sysexits 2024-08-23 14:22:11 -06:00
1fffd3df52 Makefile: sets bindgen to output exit codes as u8 2024-08-23 14:21:43 -06:00
a14b8fb9d7 fop(1): adds functions for error handling 2024-08-23 13:35:30 -06:00
dd39aeff02 mm(1): adds ioerr(), usage(), and oser() functions 2024-08-22 00:22:05 -06:00
fbacfecce8 Merge branch 'linux-fix' (closes #158) 2024-08-21 22:43:29 -06:00
DTB
579ff65b67 tests: bonsai/npc.mk: drop redundant tab removal 2024-08-21 21:59:22 -06:00
DTB
334433536b tests: bonsai/npc.mk: fix harrowing ordeal of a Linux error 2024-08-21 21:57:10 -06:00
e9058803d3 mm(1): fixes to pledge(2) now; tests: bonsai/mm.mk: adds test for regression 2024-08-17 14:58:56 -06:00
71d4d6ba05 tests: bonsai/dj.mk, bonsai/rpn.mk: fixes testing on linux 2024-08-17 01:57:16 -06:00
821f5d09e9 fop(1), hru(1), intcmp(1), mm(1), rpn(1), swab(1): fixes conditional compilation 2024-08-17 01:51:25 -06:00
a4a556a5b6 mm(1): fixes extra file arguments not being unveil(2)ed 2024-08-16 18:43:51 -06:00
DTB
e2c03842a3 intcmp(1): remove extra newline 2024-08-13 12:13:47 -06:00
DTB
0fd66bff38 false(1), true(1): complete main prototype 2024-08-13 12:04:42 -06:00
DTB
0c4923016e Makefile: replace include/None.mk with /dev/null 2024-08-13 11:56:09 -06:00
23 changed files with 503 additions and 448 deletions

View File

@@ -19,7 +19,7 @@ PREFIX ?= /usr/local
# for conditionally compiling OS features
OS != uname
OS_INCLUDE != test -e include/$(OS).mk && printf 'include/$(OS).mk\n' \
|| include/None.mk
|| printf '/dev/null\n'
# normalized prefix
PREFIX_N != dirname $(PREFIX)/.
@@ -42,7 +42,7 @@ BIN = build/bin
default: all test
.PHONY: all
all: dj false fop hru intcmp mm npc rpn scrut strcmp stris swab true
all: dj false fop hru intcmp mm npc rpn scrut str strcmp swab true
# keep build/include until bindgen(1) has stdin support
# https://github.com/rust-lang/rust-bindgen/issues/2703
@@ -95,7 +95,7 @@ build/o/libstrerror.rlib: build src/libstrerror.rs
src/libstrerror.rs
build/o/libsysexits.rlib: build/include/sysexits.h
bindgen --default-macro-constant-type signed --use-core --formatter=none \
bindgen --fit-macro-constant-types --default-macro-constant-type unsigned --use-core --formatter=none \
build/include/sysexits.h | $(RUSTC) $(RUSTFLAGS) --crate-type lib -o $@ -
# bandage solution until bindgen(1) gets stdin support
@@ -147,10 +147,10 @@ scrut: build/bin/scrut
build/bin/scrut: src/scrut.c build
$(CC) $(CFLAGS) -o $@ src/scrut.c
.PHONY: stris
stris: build/bin/stris
build/bin/stris: src/stris.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) -o $@ src/stris.rs
.PHONY: str
str: build/bin/str
build/bin/str: src/str.c build
$(CC) $(CFLAGS) -o $@ src/str.c
.PHONY: strcmp
strcmp: build/bin/strcmp

59
docs/str.1 Normal file
View File

@@ -0,0 +1,59 @@
.\" Copyright (c) 20232024 DTB <trinity@trinity.moe>
.\" Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\"
.TH STR 1 2024-06-17 "Harakit X.X.X"
.SH NAME
str \(en test string arguments
.\"
.SH SYNOPSIS
str
.B type string...
.\"
.SH DESCRIPTION
Test the character types of string arguments.
The tests in this program are equivalent to the functions with the same names in
.BR ctype.h (0p)
and are the methods by which string arguments are tested.
.\"
.SH DIAGNOSTICS
If all tests pass, the program will exit successfully. If any of the tests fail,
the program will exit unsuccessfully with an error code of 1.
When invoked incorrectly, a debug message will be printed and the program will
exit with the appropriate
.BR sysexits.h (3)
error code.
.\"
.SH CAVEATS
None of an empty string\(cqs contents pass any of the tests, so the program will
exit unsuccessfully if one is specified.
There\(cqs no way of knowing which argument failed the test without re-testing
arguments individually.
If a character in a string isn\(cqt valid ASCII, the program will exit
unsuccessfully.
.\"
.SH AUTHOR
Written by DTB
.MT trinity@trinity.moe
.ME .
.\"
.SH COPYRIGHT
Copyright \(co 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/gpl.html>.
.\"
.SH SEE ALSO
.BR ctype (3p),
.BR strcmp(1),
.BR ascii(7)

View File

@@ -1,104 +0,0 @@
.\" Copyright (c) 20232024 DTB <trinity@trinity.moe>
.\" Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
.\"
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
.\"
.TH STRIS 1 2024-10-22 "Harakit X.X.X"
.SH NAME
stris \(en test the character types of string arguments
.\"
.SH SYNOPSIS
stris
.RB [ -7bcdlu ]
.RB [ -i\ inclusions ]
.RB strings...
.\"
.SH DESCRIPTION
Test each character in any number of string arguments, ensuring each meets any
of the parameters specified by program options.
.\"
.SH OPTIONS
.IP \fB-7\fP
Tests if the character encoding is ASCII.
.IP \fB-b\fP
Tests if characters are whitespace.
.IP \fB-c\fP
Tests if characters are control characters.
.IP \fB-d\fP
Tests if characters are numeric.
.IP \fB-i\fP \fIinclusions...\fP
In addition to specified options, also permits characters in
.IR inclusions .
.IP \fB-l\fP
Tests if characters are in lower case.
.IP \fB-u\fP
Tests if characters are in upper case.
.\"
.SH DIAGNOSTICS
If no test cases pass for a character in
.IR strings ,
the program will exit unsuccessfully.
.SH CAVEATS
If no options are specified, the program will exit successfully as long as the
input
.I strings
are legibly encoded.
Neither which test failed nor which of the
.I strings
failed a test cannot be known without further invocations of the program.
Characters that can be encoded losslessly into ASCII from UTF-8 but which
are in an \(lqoverlong encoding\(rq, where the character was encoded with
unnecessary leading zeroes causing it to span multiple bytes, won\(cqt be
detected as ASCII.
.\"
.SH EXAMPLES
This is an
.BR sh (1p)
snippet which tests if an environment variable is an ASCII digit.
.RS
stris -7 "$v" && stris -d "$v" && echo ASCII digit.
.RE
This is an
.BR sh (1p)
snippet that tests if an environment variable is a hexadecimal number.
.RS
stris -7 "$v" && stris -di ABCDEFabcdef "$v" && echo Hexadecimal number.
.RE
.\"
.SH AUTHOR
Written by DTB
.MT trinity@trinity.moe
.ME .
.\"
.SH HISTORY
This program replaces the former str(1) which took the name of a function from
.BR ctype (3p)
as its first argument and checked the following strings against it;
.BR str (1)
exited unsuccessfully when it encountered any non-ASCII characters and could
only have one parameter specified.
.\"
.SH COPYRIGHT
Copyright \(co 2023\(en2024 DTB. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/gpl.html>.
.\"
.SH SEE ALSO
.BR ascii (7),
.BR ctype (3p),
.BR strcmp (1)

View File

View File

@@ -1,5 +1,6 @@
/*
* Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
* Copyright (c) 2024 DTB <trinity@trinity.moe>
* SPDX-License-Identifier: CC0
*
* This work is marked with CC0 1.0. To view a copy of this license, visit
@@ -7,12 +8,13 @@
*/
#ifdef __OpenBSD__
# include <unistd.h> /* pledge(2) */
# include <unistd.h> /* NULL, pledge(2), unveil(2) */
#endif
int main() {
int main(void) {
#ifdef __OpenBSD__
pledge(NULL, NULL);
(void)pledge(NULL, NULL);
(void)unveil(NULL, NULL);
#endif
return 1;
return 1;
}

View File

@@ -18,8 +18,8 @@
use std::{
env::args,
io::{ Read, stdin, stdout, Write },
process::{ Command, exit, Stdio },
io::{ Error, Read, Write, stdin, stdout },
process::{ Command, ExitCode, Stdio, exit },
};
extern crate getopt;
@@ -32,25 +32,36 @@ use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge };
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
fn main() {
fn err(argv0: &String, e: Error) {
eprintln!("{}: {}", argv0, e.strerror());
}
fn usage(argv0: &String) -> u8 {
eprintln!("Usage: {} [-d delimiter] index command [args...]", argv0);
EX_USAGE
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
let mut d = '\u{1E}'.to_string(); /* ASCII record separator */
let mut optind = 1;
if cfg!(target_os="openbsd") {
let promises = Promises::new("stdio proc exec");
#[cfg(target_os="openbsd")] {
let promises = Promises::new("exec proc stdio unveil");
if let Err(e) = pledge(Some(promises), None) {
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_OSERR);
err(&argv[0], e);
return ExitCode::from(EX_OSERR);
}
if let Err(e) = unveil(None, None) {
err(&argv[0], e);
return ExitCode::from(EX_OSERR);
}
}
let usage = format!(
"Usage: {} [-d delimiter] index command [args...]",
argv[0],
);
if argv.len() == 1 { return ExitCode::from(usage(&argv[0])); }
while let Some(opt) = argv.getopt("d:") {
match opt.opt() {
@@ -60,8 +71,7 @@ fn main() {
optind = opt.ind();
},
_ => {
eprintln!("{}", usage);
exit(EX_USAGE);
return ExitCode::from(usage(&argv[0]));
}
};
}
@@ -69,7 +79,7 @@ fn main() {
/* parse the specified index as a number we can use */
let index = argv[optind].parse::<usize>().unwrap_or_else(|e| {
eprintln!("{}: {}: {}", argv[0], argv[1], e);
exit(EX_DATAERR);
exit(EX_DATAERR.into());
});
/* index of the argv[0] for the operator command */
@@ -77,15 +87,14 @@ fn main() {
/* argv[0] of the operator command */
let operator = argv.get(command_arg).unwrap_or_else(|| {
eprintln!("{}", usage);
exit(EX_USAGE);
exit(usage(&argv[0]).into());
});
/* read entire standard input into memory */
let mut buf = String::new();
if let Err(e) = stdin().read_to_string(&mut buf) {
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_IOERR);
err(&argv[0], e);
exit(EX_IOERR.into());
};
/* split the buffer by the delimiter (by default, '\u{1E}') */
@@ -105,18 +114,14 @@ fn main() {
.stdout(Stdio::piped()) /* piped stdout to handle output ourselves */
.spawn()
.unwrap_or_else( |e| {
eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror());
exit(EX_UNAVAILABLE);
err(&argv[0], e);
exit(EX_UNAVAILABLE.into());
});
/* get field we want to pipe into spawned program */
let field = fields.get(index).unwrap_or_else(|| {
eprintln!(
"{}: {}: No such index in input",
argv[0],
index.to_string(),
);
exit(EX_DATAERR);
eprintln!("{}: {}: no such index in input", argv[0], index);
exit(EX_DATAERR.into());
});
/* get the stdin of the newly spawned program and feed it the field val */
@@ -126,8 +131,8 @@ fn main() {
}
let output = spawned.wait_with_output().unwrap_or_else(|e| {
eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror());
exit(EX_IOERR);
err(&argv[0], e);
exit(EX_IOERR.into());
});
/* get the output with which the original field will be replaced */
@@ -143,8 +148,8 @@ fn main() {
/* convert the output of the program to UTF-8 */
let new_field = String::from_utf8(replace).unwrap_or_else(|e| {
eprintln!("{}: {}: {}", argv[0], argv[command_arg], e);
exit(EX_IOERR);
eprintln!("{}: {}", argv[0], e);
exit(EX_IOERR.into());
});
/* store the new field in the old fields vector */
@@ -154,7 +159,9 @@ fn main() {
stdout().write_all(
fields.join(&d.to_string()).as_bytes()
).unwrap_or_else(|e| {
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_IOERR);
err(&argv[0], e);
exit(EX_IOERR.into());
});
ExitCode::SUCCESS
}

View File

@@ -19,7 +19,7 @@
use std::{
cmp::Ordering,
env::args,
io::{ stdin, stdout, Write },
io::{ Write, stdin, stdout },
process::{ ExitCode, exit },
};
@@ -31,7 +31,7 @@ use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE, EX_USAGE };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge };
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
/* list of SI prefixes */
const LIST: [(u32, &str); 10] = [
@@ -47,6 +47,16 @@ const LIST: [(u32, &str); 10] = [
(30, "Q"), /* quetta */
];
fn err(argv0: &String, message: String, code: Option<u8>) -> ExitCode {
eprintln!("{}: {}", argv0, message);
ExitCode::from(code.unwrap_or(1 /* unknown error */))
}
fn usage(argv0: &String) -> ExitCode {
eprintln!("Usage: {}", argv0);
ExitCode::from(EX_USAGE)
}
fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> {
/* preserve decimal places in output by casting to a float */
let mut out = (input as f64, (0_u32, ""));
@@ -81,53 +91,48 @@ fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> {
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
if let Some(_) = argv.get(1) {
eprintln!("Usage: {}", argv[0]);
return ExitCode::from(EX_USAGE as u8);
}
if let Some(_) = argv.get(1) { return usage(&argv[0]); }
if cfg!(target_os="openbsd") {
let promises = Promises::new("stdio");
if let Err(e) = pledge(Some(promises), None) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
#[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio unveil");
if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
return err(&argv[0], e.strerror(), Some(EX_OSERR));
}
if let Err(e) = unveil(None, None) {
return err(&argv[0], e.strerror(), Some(EX_OSERR));
}
}
let mut buf = String::new();
while let Ok(_) = stdin().read_line(&mut buf) {
if buf.is_empty() { return ExitCode::SUCCESS; }
if let Err(e) = stdin().read_line(&mut buf) {
return err(&argv[0], e.strerror(), Some(EX_IOERR));
}
let n: u128 = match buf.trim().parse() {
Ok(f) => {
buf.clear();
f
},
Err(err) => {
eprintln!("{}: {}", argv[0], err);
return ExitCode::from(EX_DATAERR as u8);
},
};
if buf.is_empty() { return ExitCode::SUCCESS; }
let (number, prefix) = match convert(n) {
Ok(x) => x,
Err(err) => {
eprintln!("{}: {}", argv[0], err);
return ExitCode::from(EX_SOFTWARE as u8);
},
};
let n: u128 = match buf.trim().parse() {
Ok(f) => {
buf.clear();
f
},
Err(e) => return err(&argv[0], e.to_string(), Some(EX_DATAERR)),
};
let si_prefix = format!("{}B", prefix.1);
let (number, prefix) = convert(n).unwrap_or_else(|e| {
let _ = err(&argv[0], e.to_string(), None);
exit(EX_SOFTWARE.into());
});
/* round output number to one decimal place */
let out = ((number * 10.0).round() / 10.0).to_string();
let si_prefix = prefix.1.to_owned() + "B";
stdout().write_all(format!("{} {}\n", out, si_prefix).as_bytes())
.unwrap_or_else(|e| {
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_IOERR);
});
/* round output number to one decimal place */
let rounded = (number * 10.0).round() / 10.0;
let out = rounded.to_string() + " " + &si_prefix + &'\n'.to_string();
if let Err(e) = stdout().write_all(out.as_bytes()) {
return err(&argv[0], e.strerror(), Some(EX_IOERR));
}
ExitCode::SUCCESS

View File

@@ -26,28 +26,35 @@ extern crate getopt;
extern crate sysexits;
use getopt::GetOpt;
use sysexits::EX_USAGE;
use sysexits::{ EX_DATAERR, EX_USAGE };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] extern crate strerror;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge };
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
#[cfg(target_os="openbsd")] use strerror::StrError;
fn err(argv0: &String, e: String, code: u8) -> ExitCode {
eprintln!("{}: {}", argv0, e);
ExitCode::from(code)
}
fn usage(s: &str) -> ExitCode {
eprintln!("Usage: {} [-egl] integer integer...", s);
ExitCode::from(EX_USAGE as u8)
fn usage(argv0: &str) -> ExitCode {
eprintln!("Usage: {} [-egl] integer integer...", argv0);
ExitCode::from(EX_USAGE)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
if cfg!(target_os="openbsd") {
let promises = Promises::new("stdio");
if let Err(e) = pledge(Some(promises), None) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
#[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio unveil");
if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
return err(&argv[0], e.strerror(), EX_OSERR);
}
if let Err(e) = unveil(None, None) {
return err(&argv[0], e.strerror(), EX_OSERR);
}
}
@@ -79,8 +86,8 @@ fn main() -> ExitCode {
match arg.parse::<usize>() { /* parse current operand */
Ok(n) => currn = n,
Err(e) => {
eprintln!("{}: {}: {}", &argv[0], arg, e);
return ExitCode::from(EX_USAGE as u8);
let error = arg.to_owned() + ": " + &e.to_string();
return err(&argv[0], error, EX_DATAERR);
}
}

View File

@@ -43,6 +43,10 @@ impl Promises {
}
}
impl Default for Promises {
fn default() -> Self { Promises::new("") }
}
pub fn pledge(
promises: Option<Promises>, execpromises: Option<Promises>
) -> Result<(), Error> {

View File

@@ -20,9 +20,9 @@
use std::{
env::args,
fs::File,
io::{ stdin, stdout, stderr, BufWriter, Read, Write },
io::{ Error, BufWriter, Read, Write, stderr, stdin, stdout },
os::fd::{ AsRawFd, FromRawFd },
process::{ exit, ExitCode },
process::{ ExitCode, exit},
};
extern crate getopt;
@@ -47,15 +47,23 @@ use ArgMode::*;
enum ArgMode { In, Out }
fn err(argv0: &String, e: Error, code: Option<u8>) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(code.unwrap_or(1 /* unknown error */))
}
fn usage(argv0: &String) -> ExitCode {
eprintln!("Usage: {} [-aetu] [-i input] [-o output]", argv0);
ExitCode::from(EX_USAGE)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<_>>();
let usage = format!("Usage: {} [-aetu] [-i input] [-o output]", argv[0]);
if cfg!(target_os="openbsd") {
let promises = Promises::new("rpath stdio unveil");
if let Err(e) = pledge(Some(promises), None) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
#[cfg(target_os="openbsd")] {
let promises = Promises::new("cpath rpath stdio unveil wpath");
if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
return err(&argv[0], e, Some(EX_OSERR));
}
}
@@ -76,35 +84,16 @@ fn main() -> ExitCode {
Ok("t") => t = false,
Ok("i") => { /* add inputs */
let input = opt.arg().unwrap();
if cfg!(target_os="openbsd") {
let perms = UnveilPerms::new(vec!['r']);
if let Err(e) = unveil(Some(&input), Some(perms)) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
}
}
ins.push(input);
mode = Some(In); /* latest argument == -i */
},
Ok("o") => { /* add output */
let output = opt.arg().unwrap();
if cfg!(target_os="openbsd") {
let perms = UnveilPerms::new(vec!['w', 'c']);
if let Err(e) = unveil(Some(&output), Some(perms)) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
}
}
let output = opt.arg().unwrap();
outs.push(output);
mode = Some(Out); /* latest argument == -o */
},
Err(_) | Ok(_) => {
eprintln!("{}", usage);
return ExitCode::from(EX_USAGE as u8);
return usage(&argv[0]);
},
};
@@ -124,16 +113,30 @@ fn main() -> ExitCode {
}
}
if cfg!(target_os="openbsd") {
#[cfg(target_os="openbsd")] {
for input in &ins {
let perms = UnveilPerms::new(vec!['r']);
if let Err(e) = unveil(Some(&input), Some(perms)) {
return err(&argv[0], e, Some(EX_OSERR));
}
}
for output in &outs {
let perms = UnveilPerms::new(vec!['c', 'w']);
if let Err(e) = unveil(Some(&output), Some(perms)) {
return err(&argv[0], e, Some(EX_OSERR));
}
}
if let Err(e) = unveil(None, None) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
return err(&argv[0], e, Some(EX_OSERR));
}
}
if ins.is_empty() && outs.is_empty() && argv.len() > optind {
eprintln!("Usage: {}", usage);
return ExitCode::from(EX_USAGE as u8);
return usage(&argv[0]);
}
/* use stdin if no inputs are specified */
@@ -153,8 +156,8 @@ fn main() -> ExitCode {
match File::open(file) {
Ok(f) => f,
Err(e) => {
eprintln!("{}: {}: {}", argv[0], file, e.strerror());
exit(EX_IOERR);
let _ = err(&(argv[0].clone() + ": " + file), e, None);
exit(EX_IOERR.into());
},
}
}).collect::<Vec<_>>();
@@ -180,8 +183,8 @@ fn main() -> ExitCode {
match options {
Ok(f) => return f,
Err(e) => {
eprintln!("{}: {}: {}", argv[0], file, e.strerror());
exit(EX_IOERR);
let _ = err(&(argv[0].clone() + ": " + file), e, None);
exit(EX_IOERR.into());
},
};
}).collect::<Vec<_>>();
@@ -205,21 +208,19 @@ fn main() -> ExitCode {
for file in inputs {
for byte in file.bytes().map(|b| {
b.unwrap_or_else(|e| {
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_IOERR);
let _ = err(&argv[0], e, None);
exit(EX_IOERR.into());
})
}) {
for out in &mut outputs {
if let Err(e) = out.write(&[byte]) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_IOERR as u8);
return err(&argv[0], e, Some(EX_IOERR));
}
if u {
/* immediately flush the output for -u */
if let Err(e) = out.flush() {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_IOERR as u8);
return err(&argv[0], e, Some(EX_IOERR));
}
}
}

View File

@@ -20,7 +20,7 @@
#include <stdio.h> /* fprintf(3), fputs(3), getc(3), perror(3), putc(3), stdin,
* stdout, EOF */
#include <sysexits.h> /* EX_IOERR, EX_OK, EX_OSERR, EX_USAGE */
#include <unistd.h> /* pledge(2), getopt(3) */
#include <unistd.h> /* NULL, getopt(3), pledge(2), unveil(2) */
char *program_name = "npc";
@@ -44,7 +44,7 @@ int main(int argc, char *argv[]) {
char showtab = 0; /* prints tab characters in caret notation */
#ifdef __OpenBSD__
if (pledge("stdio", NULL) == -1) {
if (pledge("stdio unveil", NULL) == -1 || unveil(NULL, NULL)) {
perror(argv[0] == NULL ? program_name : argv[0]);
return EX_OSERR;
}

View File

@@ -46,7 +46,7 @@ use std::{
collections::VecDeque,
env::args,
fmt::{ self, Display, Formatter },
io::stdin,
io::{ Error, Write, stdin, stdout },
process::ExitCode,
};
@@ -54,13 +54,13 @@ use CalcType::*;
extern crate sysexits;
use sysexits::EX_DATAERR;
use sysexits::{ EX_DATAERR, EX_IOERR };
#[cfg(target_os="openbsd")] use sysexits::EX_OSERR;
#[cfg(target_os="openbsd")] extern crate strerror;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] use strerror::StrError;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge };
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
#[derive(Clone, PartialEq, PartialOrd, Debug)]
/* enum CalcType is a type containing operations used in the calculator */
@@ -120,12 +120,46 @@ impl Display for CalcType {
#[derive(Debug, Clone)]
struct EvaluationError {
message: String,
code: i32,
code: u8,
}
/* Im no math nerd but I want the highest possible approximation of 0.9
* repeating and it seems this can give it to me */
const PRECISION_MOD: f64 = 0.9 + f64::EPSILON * 100.0;
impl StrError for EvaluationError {
fn strerror(&self) -> String {
self.message.clone()
}
}
fn err<T: StrError>(argv0: &String, e: &T, code: Option<u8>) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(code.unwrap_or(1 /* unknown error */))
}
fn operate(
mut stack: VecDeque<f64>,
op: CalcType,
) -> Result<VecDeque<f64>, EvaluationError> {
let vals = (stack.pop_back(), stack.pop_back());
if let (Some(x), Some(y)) = vals {
match op {
Add => stack.push_back(y + x),
Subtract => stack.push_back(y - x),
Multiply => stack.push_back(y * x),
Divide => stack.push_back(y / x),
Power => stack.push_back(y.powf(x)),
Floor => stack.push_back((y / x).floor()),
Modulo => stack.push_back(y % x),
_ => {},
};
} else {
return Err(EvaluationError {
message: format!("{}: unexpected operation", op),
code: EX_DATAERR,
})
}
Ok(stack)
}
fn eval(
input: &str,
@@ -145,6 +179,7 @@ fn eval(
.rev()
.map(|t| CalcType::from(t))
.collect();
let mut ops: VecDeque<CalcType> = VecDeque::new();
while let Some(n) = toks.pop_back() {
@@ -152,36 +187,15 @@ fn eval(
Val(v) => stack.push_back(v),
Invalid(i) => {
return Err(EvaluationError {
message: format!("{}: Invalid token", i),
message: format!("{}: invalid token", i),
code: EX_DATAERR,
})
},
op => {
ops.push_back(op.clone());
oper = true;
let vals = (
stack.pop_back(),
stack.pop_back(),
);
if let (Some(x), Some(y)) = vals {
match op {
Add => stack.push_back(y + x),
Subtract => stack.push_back(y - x),
Multiply => stack.push_back(y * x),
Divide => stack.push_back(y / x),
Power => stack.push_back(y.powf(x)),
Floor => stack.push_back((y / x).floor()),
Modulo => stack.push_back(y % x),
_ => {},
};
} else {
return Err(EvaluationError {
message: format!("{}: Unexpected operation", op),
code: EX_DATAERR,
})
}
oper = true; /* this is an operation */
return operate(stack, op).map(|s| (s, oper));
},
};
}
@@ -190,51 +204,46 @@ fn eval(
}
/* Round a float to the given precision level */
fn round_precise(value: &f64, precision: usize) -> f64 {
let multiplier = 10_f64.powi(precision as i32);
fn round_precise(value: &f64) -> f64 {
/* Set floating-point precision for correcting rounding errors based on
* machine epsilon */
let precision = (-f64::EPSILON.log10()).floor() as i32;
let multiplier = 10_f64.powi(precision);
(value * multiplier).round() / multiplier
}
/* print the stack and let the caller know if evaluation should continue */
fn unstack(stack: VecDeque<f64>, op: bool) -> Result<bool, Error> {
if let Some(val) = stack.iter().last() {
if !op { return Ok(true); }
let out = round_precise(val).to_string() + &'\n'.to_string();
return stdout().write_all(out.as_bytes()).map(|_| true);
} else {
return Ok(false);
}
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
if cfg!(target_os="openbsd") {
let promises = Promises::new("stdio");
if let Err(e) = pledge(Some(promises), None) {
eprintln!("{}: {}", argv[0], e.strerror());
return ExitCode::from(EX_OSERR as u8);
#[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio unveil");
if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
return err(&argv[0], &e, Some(EX_OSERR));
}
if let Err(e) = unveil(None, None) {
return err(&argv[0], &e, Some(EX_OSERR));
}
}
let mut stack = VecDeque::new();
let mut buf = String::new();
/* Set floating-point precision for correcting rounding errors based on
* machine epsilon */
let precision = (-f64::EPSILON.log10() * PRECISION_MOD).ceil() as usize;
if argv.get(1).is_none() { /* read from stdin */
while let Ok(_) = stdin().read_line(&mut buf) {
match eval(&buf.trim(), stack) {
Ok(s) => {
buf.clear();
stack = s.0.clone();
let val = match stack.iter().last() {
Some(v) => v,
None => break,
};
if s.1 == false { continue; }
println!("{}", round_precise(val, precision).to_string());
},
Err(err) => {
eprintln!("{}: {}", argv[0], err.message);
return ExitCode::from(err.code as u8);
},
};
}
} else { /* read from argv */
if argv.get(1).is_some() { /* read expressions from argv */
/* join argv into an owned String joined by spaces minus argv[0] */
let input = argv
.iter()
@@ -245,20 +254,44 @@ fn main() -> ExitCode {
match eval(&input, stack) {
Ok(s) => {
stack = s.0.clone();
/* we can ignore the return value of unstack() because we are
* not continually evaluating from stdin */
if let Err(e) = unstack(s.0.clone(), s.1.clone()) {
return err(&argv[0], &e, Some(EX_IOERR));
}
let val = match stack.iter().last() {
Some(v) => v,
None => return ExitCode::SUCCESS,
};
println!("{}", round_precise(val, precision).to_string())
return ExitCode::SUCCESS;
},
Err(err) => {
eprintln!("{}: {}", argv[0], err.message);
return ExitCode::from(err.code as u8);
Err(e) => {
return err(&argv[0], &e, Some(e.code));
},
};
}
/* else, read from stdin */
loop { /* take input until EOF */
if let Err(e) = stdin().read_line(&mut buf) {
return err(&argv[0], &e, Some(EX_IOERR));
}
match eval(&buf.trim(), stack) {
Ok(s) => {
buf.clear();
stack = s.0.clone(); /* out with the old, in with the new */
match unstack(s.0, s.1) {
Ok(b) if b => continue,
Ok(_) => break,
Err(e) => {
return err(&argv[0], &e, Some(EX_IOERR))
},
};
},
Err(e) => {
return err(&argv[0], &e, Some(e.code));
},
};
}
ExitCode::SUCCESS
}

96
src/str.c Normal file
View File

@@ -0,0 +1,96 @@
/*
* Copyright (c) 2023 DTB <trinity@trinity.moe>
* Copyright (c) 2023 Marceline Cramer <mars@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
#include <ctype.h>
#include <stddef.h> /* NULL */
#include <stdio.h> /* fprintf(3), perror(3) */
#include <stdlib.h> /* size_t, EXIT_FAILURE */
#include <string.h> /* strcmp(3) */
#include <sysexits.h> /* EX_OSERR, EX_USAGE */
#ifdef __OpenBSD__
# include <unistd.h> /* pledge(2), unveil(2) */
#endif
char *program_name = "str";
static struct {
char *name;
int (*f)(int);
} ctypes[] = {
{ "isalnum", isalnum },
{ "isalpha", isalpha },
{ "isblank", isblank },
{ "iscntrl", iscntrl },
{ "isdigit", isdigit },
{ "isxdigit", isxdigit },
{ "isgraph", isgraph },
{ "islower", islower },
{ "isprint", isprint },
{ "ispunct", ispunct },
{ "isspace", isspace },
{ "isupper", isupper },
{ NULL, NULL } /* marks end */
};
static int
usage(char *argv0) {
(void)fprintf(stderr, "Usage: %s type string...\n", argv0);
return EX_USAGE;
}
int main(int argc, char *argv[]) {
size_t ctype; // selected from ctypes.h; index of ctype
int retval; // initially fail but becomes success on the first valid char
program_name = argv[0] == NULL ? program_name : argv[0];
#ifdef __OpenBSD__
if (pledge("stdio unveil", NULL) == -1 || unveil(NULL, NULL) == -1) {
perror(program_name);
return EX_OSERR;
}
#endif
if (argc < 3) { return usage(program_name); }
for ( /* iterate ctypes */
ctype = 0;
ctypes[ctype].f != NULL /* break at the end of ctypes */
&& strcmp(argv[1], ctypes[ctype].name) != 0; /* break at match */
++ctype
);
if (ctypes[ctype].f == NULL) { return usage(argv[0]); }
/* iterate args */
for (argv += 2, retval = EXIT_FAILURE; *argv != NULL; ++argv) {
for (size_t i = 0; argv[0][i] != '\0'; ++i) { /* iterate arg bytes */
/* First checks if argv[0][i] is valid ASCII; ctypes(3) don't
* handle non-ASCII. This is bad. */
if(
(unsigned char)argv[0][i] < 0x80 // argv[0][i] is ASCII,
&& !ctypes[ctype].f(argv[0][i]) // so use ctypes(3)
) { return EXIT_FAILURE; }
else { retval = EXIT_SUCCESS; }
}
}
return retval;
}

View File

@@ -20,7 +20,7 @@
#include <sysexits.h> /* EX_OK, EX_OSERR, EX_USAGE */
#ifdef __OpenBSD__
# include <unistd.h> /* pledge(2) */
# include <unistd.h> /* pledge(2), unveil(2) */
#endif
char *program_name = "strcmp";
@@ -29,7 +29,7 @@ int main(int argc, char *argv[]) {
unsigned int i;
#ifdef __OpenBSD__
if (pledge("stdio", NULL) == -1) {
if (pledge("stdio unveil", NULL) == -1 || unveil(NULL, NULL) == -1) {
perror(argv[0] == NULL ? program_name : argv[0]);
return EX_OSERR;

View File

@@ -1,87 +0,0 @@
/*
* Copyright (c) 20232024 DTB <trinity@trinity.moe>
* Copyright (c) 2023 Marceline Cramer <mars@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
use std::{
env::args,
process::ExitCode,
};
extern crate getopt;
extern crate sysexits;
use getopt::GetOpt;
use sysexits::EX_USAGE;
struct Reqs {
ascii: bool, blank: bool, cntrl: bool, digit: bool, lower: bool,
upper: bool, inuse: bool, extra: String
}
fn usage(s: &str) -> ExitCode {
eprintln!("Usage: {} [-7bcdlu] [-i inclusions] [strings...]", s);
ExitCode::from(EX_USAGE as u8)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
let mut optind = 1;
let mut reqs = Reqs {
ascii: false, blank: false, cntrl: false, digit: false, lower: false,
upper: false, inuse: false, extra: String::default()
};
while let Some(opt) = argv.getopt("7bcdi:lu") {
match opt.opt() {
Ok("7") => reqs.ascii = true,
Ok("b") => reqs.blank = true,
Ok("c") => reqs.cntrl = true,
Ok("d") => reqs.digit = true,
Ok("i") => reqs.extra = opt.arg().unwrap(),
Ok("l") => reqs.lower = true,
Ok("u") => reqs.upper = true,
_ => { return usage(&argv[0]); }
}
optind = opt.ind();
reqs.inuse = true;
}
if argv.len() == optind { return usage(&argv[0]); }
drop(argv);
if reqs.inuse {
for arg in args().skip(optind) {
for c in arg.chars() {
if (reqs.ascii && c.is_ascii())
|| (reqs.blank && c.is_whitespace())
|| (reqs.cntrl && c.is_control())
|| (reqs.digit && c.is_numeric())
|| (reqs.lower && c.is_lowercase())
|| (reqs.upper && c.is_uppercase())
|| reqs.extra.contains(c) {
continue;
} else {
return ExitCode::FAILURE;
}
}
}
}
ExitCode::SUCCESS
}

View File

@@ -33,31 +33,29 @@ use sysexits::{ EX_IOERR, EX_OK, EX_OSERR, EX_USAGE };
use strerror::StrError;
#[cfg(target_os="openbsd")] extern crate openbsd;
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge };
#[cfg(target_os="openbsd")] use openbsd::{ Promises, pledge, unveil };
fn oserr(argv0: &str, e: Error) -> ExitCode {
fn err(argv0: &str, e: Error, code: u8) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(EX_OSERR as u8)
}
fn ioerr(argv0: &str, e: Error) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(EX_IOERR as u8)
ExitCode::from(code)
}
fn usage(s: &str) -> ExitCode {
eprintln!("Usage: {} [-w word_size]", s);
ExitCode::from(EX_USAGE as u8)
ExitCode::from(EX_USAGE)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
if cfg!(target_os="openbsd") {
let promises = Promises::new("stdio");
if let Err(e) = pledge(Some(promises), None) {
return oserr(&argv[0], e);
#[cfg(target_os="openbsd")] {
let promises = Promises::new("stdio unveil");
if let Err(e) = pledge(Some(promises), Some(Promises::default())) {
return err(&argv[0], e, EX_OSERR);
}
if let Err(e) = unveil(None, None) {
return err(&argv[0], e, EX_OSERR);
}
}
@@ -88,22 +86,22 @@ fn main() -> ExitCode {
loop {
match input.read(&mut buf) {
Ok(0) => break ExitCode::from(EX_OK as u8), // read nothing; bye
Ok(0) => break ExitCode::from(EX_OK), // read nothing; bye
Ok(v) if v == wordsize => { // read full block; swab
let (left, right) = buf.split_at(v/2);
if let Err(e) = output.write(&right)
.and_then(|_| output.write(&left)) {
break ioerr(&argv[0], e);
break err(&argv[0], e, EX_IOERR);
}
},
Ok(v) => { // partial read; partially write
if let Err(e) = output.write(&buf[..v]) {
break ioerr(&argv[0], e);
break err(&argv[0], e, EX_IOERR);
}
},
Err(e) => break oserr(&argv[0], e)
Err(e) => break err(&argv[0], e, EX_OSERR)
}
}
}

View File

@@ -1,5 +1,6 @@
/*
* Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
* Copyright (c) 2024 DTB <trinity@trinity.moe>
* SPDX-License-Identifier: CC0
*
* This work is marked with CC0 1.0. To view a copy of this license, visit
@@ -7,11 +8,12 @@
*/
#ifdef __OpenBSD__
# include <unistd.h> /* pledge(2) */
# include <unistd.h> /* NULL, pledge(2), unveil(2) */
#endif
int main() {
int main(void) {
#ifdef __OpenBSD__
pledge(NULL, NULL);
(void)pledge(NULL, NULL);
(void)unveil(NULL, NULL);
#endif
}

View File

@@ -19,7 +19,7 @@ dj_tests: dj_help dj_full dj_null # dj_skip_stdin
dj_full: $(BIN)/dj /dev/full
case "$$(uname)" in \
Linux) \
$(BIN)/dj -Hi /dev/zero -o /dev/full 2>&1 \
! $(BIN)/dj -Hi /dev/zero -o /dev/full 2>&1 \
| tee /dev/stderr \
| xargs -I out test '1+0 > 0+0; 1024 > 0' = out \
;; \

View File

@@ -24,6 +24,7 @@ fop_fail: $(BIN)/fop
! printf 'test\n' | $(BIN)/fop 1 cat
! printf 'test\n' | $(BIN)/fop 'test' cat
! printf 'test\n' | $(BIN)/fop -d'test' cat
! $(BIN)/fop
.PHONY: fop_functionality
fop_functionality: $(BIN)/fop

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2024 E$(NAME)a Tebibyte <e$(NAME)a@tebibyte.media>
# Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
# SPDX-License-Identifier: FSFAP
#
# Copying and distribution of this file, with or without modification, are
@@ -6,7 +6,7 @@
# notice are preserved. This file is offered as-is, without any warranty.
.PHONY: mm_tests
mm_tests: mm_args mm_help mm_stderr
mm_tests: mm_args mm_help mm_stderr mm_remaining mm_remaining_options
.PHONY: mm_none
mm_none: $(BIN)/mm
@@ -25,3 +25,16 @@ mm_help: $(BIN)/mm
# check if stderr is empty upon specifying -e
mm_stderr: $(BIN)/mm
test "$$(printf 'test\n' | $(BIN)/mm -e 2>&1 >/dev/null )" = "test"
.PHONY: mm_remaining
# check to make sure remaining arguments are used
mm_remaining: $(BIN)/mm
test "$$($(BIN)/mm -i README COPYING)" = "$$(cat README COPYING)"
$(BIN)/mm -i README -o /tmp/mm_test0 /tmp/mm_test1
diff /tmp/mm_test0 /tmp/mm_test1
.PHONY: mm_remaining_options
# check to make sure mm -i with trailing arguments interprets -o as one
mm_remaining_options:
! $(BIN)/mm -i README COPYING -o - 2>&1 | cut -d: -f2 \
| xargs test " -o" =

View File

@@ -32,11 +32,29 @@ npc_ascii: npc_ascii_controls npc_ascii_symbols npc_ascii_uppers # \
.PHONY: npc_ascii_controls
# (control characters)
npc_ascii_controls:
# The following test prints the bytes 0x00 (inclusive) through 0x20
# (exclusive) and pipes them through npc(1). npc(1) should then replace all
# non-printing, non-space (in the isspace(3p) sense) characters with their
# graphical carat-char counterparts (see the npc(1) man page). The head(1p)
# invocation then strips off everything past the first line (or past the
# first newline byte, 0x0A) and xargs(1p) is used to test(1p) the output
# against the known good answer.
# Immediately before that newline, 0x09 is printed - in ASCII, the
# horizontal tab. If xargs' -I option is used, tr(1p) should used to delete
# that tab. If the tab is left as part of input, OpenBSD's xargs(1)
# implementation has been observed to strip it along with the other
# trailing whitespace (the newline), but Busybox's and GNU's xargs(1)
# implementations have been observed to leave the tab in. All three
# implementations strip off the trailing tab if `-I` is not used. The POSIX
# specification for `-I` is ambiguous as to which behavior is correct.
# This comment is the result of much bewilderment and debugging.
# ASCII 0x00 to 0x0a (before the newline, due to xargs(1p) issues)
awk 'BEGIN{ for (i = 0; i < 32; ++i) printf("%c", i); }' \
| $(BIN)/npc \
| head -n 1 \
| xargs -I out test "^@^A^B^C^D^E^F^G^H" = out
| xargs test "^@^A^B^C^D^E^F^G^H" =
# ASCII 0x0a (otherwise the head|tail sequence won't work) to 0x1f
awk 'BEGIN{ for (i = 0; i < 32; ++i) printf("%c", i); print }' \

View File

@@ -6,7 +6,7 @@
# notice are preserved. This file is offered as-is, without any warranty.
.PHONY: rpn_tests
rpn_tests: rpn_help rpn_add rpn_sub rpn_mul rpn_div rpn_mod rpn_flr
rpn_tests: rpn_help rpn_add rpn_sub rpn_mul rpn_div rpn_mod rpn_flr rpn_stdin
.PHONY: rpn_help
rpn_help: $(BIN)/rpn
@@ -30,7 +30,7 @@ rpn_mul: $(BIN)/rpn
.PHONY: rpn_div
rpn_div: $(BIN)/rpn
test "$$($(BIN)/rpn 12 5 /)" = 2.4
test "$$($(BIN)/rpn 3 0 /)" -eq inf
test "$$($(BIN)/rpn 3 0 /)" = inf
.PHONY: rpn_mod
rpn_mod: $(BIN)/rpn
@@ -41,3 +41,8 @@ rpn_mod: $(BIN)/rpn
rpn_flr: $(BIN)/rpn
test "$$($(BIN)/rpn 12 5 //)" -eq 2
test "$$($(BIN)/rpn 9 4 //)" -eq 2
# done last because all operations have been tested
.PHONY: rpn_stdin
rpn_stdin: $(BIN)/rpn
test "$$(printf '1\n2\n+\n3\n-\n' | $(BIN)/rpn | tail -n1)" -eq 0

View File

@@ -7,19 +7,14 @@
.PRAGMA: command_comment
.PHONY: stris_tests
stris_tests: stris_help stris_7 stris_b
.PHONY: str_tests
str_tests: str_help str_isalpha
.PHONY: stris_help
stris_help: $(BIN)/stris
! $(BIN)/stris -h
.PHONY: str_help
str_help: $(BIN)/str
! $(BIN)/str -h
.PHONY: stris_7
stris_7: $(BIN)/stris
$(BIN)/stris -7 !1Aa' '
! $(BIN)/stris -7 今日は
.PHONY: stris_b
stris_b: $(BIN)/stris
$(BIN)/stris -b "$(printf ' \t\v\r\n')"
! $(BIN)/stris -b !1Aa
.PHONY: str_isalpha
str_isalpha: $(BIN)/str
$(BIN)/str isalpha c
! $(BIN)/str isalpha 3