mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-22 05:01:07 +08:00
Port random to rust
This commit is contained in:
parent
bc7c29d597
commit
4fd1458d85
|
@ -107,7 +107,7 @@ set(FISH_BUILTIN_SRCS
|
|||
src/builtins/eval.cpp src/builtins/fg.cpp
|
||||
src/builtins/function.cpp src/builtins/functions.cpp src/builtins/history.cpp
|
||||
src/builtins/jobs.cpp src/builtins/math.cpp src/builtins/printf.cpp src/builtins/path.cpp
|
||||
src/builtins/pwd.cpp src/builtins/random.cpp src/builtins/read.cpp
|
||||
src/builtins/pwd.cpp src/builtins/read.cpp
|
||||
src/builtins/realpath.cpp src/builtins/set.cpp
|
||||
src/builtins/set_color.cpp src/builtins/source.cpp src/builtins/status.cpp
|
||||
src/builtins/string.cpp src/builtins/test.cpp src/builtins/type.cpp src/builtins/ulimit.cpp
|
||||
|
|
37
fish-rust/Cargo.lock
generated
37
fish-rust/Cargo.lock
generated
|
@ -358,6 +358,7 @@ dependencies = [
|
|||
"nix",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"unixstring",
|
||||
"widestring",
|
||||
"widestring-suffix",
|
||||
|
@ -658,6 +659,12 @@ version = "0.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.1.23"
|
||||
|
@ -710,6 +717,36 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
|
|
|
@ -15,6 +15,7 @@ libc = "0.2.137"
|
|||
nix = { version = "0.25.0", default-features = false, features = [] }
|
||||
num-traits = "0.2.15"
|
||||
once_cell = "1.17.0"
|
||||
rand = { version = "0.8.5", features = ["small_rng"] }
|
||||
unixstring = "0.2.7"
|
||||
widestring = "1.0.2"
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ pub mod shared;
|
|||
|
||||
pub mod echo;
|
||||
pub mod emit;
|
||||
mod exit;
|
||||
pub mod random;
|
||||
pub mod r#return;
|
||||
pub mod wait;
|
||||
mod exit;
|
||||
|
|
188
fish-rust/src/builtins/random.rs
Normal file
188
fish-rust/src/builtins/random.rs
Normal file
|
@ -0,0 +1,188 @@
|
|||
use libc::c_int;
|
||||
|
||||
use crate::builtins::shared::{
|
||||
builtin_missing_argument, builtin_print_help, builtin_unknown_option, io_streams_t,
|
||||
STATUS_CMD_OK, STATUS_INVALID_ARGS,
|
||||
};
|
||||
use crate::ffi::parser_t;
|
||||
use crate::wchar::{widestrs, wstr};
|
||||
use crate::wgetopt::{wgetopter_t, wopt, woption, woption_argument_t};
|
||||
use crate::wutil::{self, fish_wcstoi_radix_all, format::printf::sprintf, wgettext_fmt};
|
||||
use num_traits::PrimInt;
|
||||
use once_cell::sync::Lazy;
|
||||
use rand::rngs::SmallRng;
|
||||
use rand::{Rng, SeedableRng};
|
||||
use std::sync::Mutex;
|
||||
|
||||
static seeded_engine: Lazy<Mutex<SmallRng>> = Lazy::new(|| Mutex::new(SmallRng::from_entropy()));
|
||||
|
||||
#[widestrs]
|
||||
pub fn random(
|
||||
parser: &mut parser_t,
|
||||
streams: &mut io_streams_t,
|
||||
argv: &mut [&wstr],
|
||||
) -> Option<c_int> {
|
||||
let cmd = argv[0];
|
||||
let argc = argv.len();
|
||||
let print_hints = false;
|
||||
|
||||
const shortopts: &wstr = "+:h"L;
|
||||
const longopts: &[woption] = &[wopt("help"L, woption_argument_t::no_argument, 'h')];
|
||||
|
||||
let mut w = wgetopter_t::new(shortopts, longopts, argv);
|
||||
while let Some(c) = w.wgetopt_long() {
|
||||
match c {
|
||||
'h' => {
|
||||
builtin_print_help(parser, streams, cmd);
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1], print_hints);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
'?' => {
|
||||
builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1], print_hints);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
_ => {
|
||||
panic!("unexpected retval from wgeopter.next()");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut engine = seeded_engine.lock().unwrap();
|
||||
let mut start = 0;
|
||||
let mut end = 32767;
|
||||
let mut step = 1;
|
||||
let arg_count = argc - w.woptind;
|
||||
let i = w.woptind;
|
||||
if arg_count >= 1 && argv[i] == "choice" {
|
||||
if arg_count == 1 {
|
||||
streams
|
||||
.err
|
||||
.append(wgettext_fmt!("%ls: nothing to choose from\n", cmd,));
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
let rand = engine.gen_range(0..arg_count - 1);
|
||||
streams.out.append(sprintf!("%ls\n"L, argv[i + 1 + rand]));
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
fn parse<T: PrimInt>(
|
||||
streams: &mut io_streams_t,
|
||||
cmd: &wstr,
|
||||
num: &wstr,
|
||||
) -> Result<T, wutil::Error> {
|
||||
let res = fish_wcstoi_radix_all(num.chars(), None, true);
|
||||
if res.is_err() {
|
||||
streams
|
||||
.err
|
||||
.append(wgettext_fmt!("%ls: %ls: invalid integer\n", cmd, num,));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
match arg_count {
|
||||
0 => {
|
||||
// Keep the defaults
|
||||
}
|
||||
1 => {
|
||||
// Seed the engine persistently
|
||||
let num = parse::<i64>(streams, cmd, argv[i]);
|
||||
match num {
|
||||
Err(_) => return STATUS_INVALID_ARGS,
|
||||
Ok(x) => *engine = SmallRng::seed_from_u64(x as u64),
|
||||
}
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
2 => {
|
||||
// start is first, end is second
|
||||
match parse::<i64>(streams, cmd, argv[i]) {
|
||||
Err(_) => return STATUS_INVALID_ARGS,
|
||||
Ok(x) => start = x,
|
||||
}
|
||||
|
||||
match parse::<i64>(streams, cmd, argv[i + 1]) {
|
||||
Err(_) => return STATUS_INVALID_ARGS,
|
||||
Ok(x) => end = x,
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
// start, step, end
|
||||
match parse::<i64>(streams, cmd, argv[i]) {
|
||||
Err(_) => return STATUS_INVALID_ARGS,
|
||||
Ok(x) => start = x,
|
||||
}
|
||||
|
||||
// start, step, end
|
||||
match parse::<u64>(streams, cmd, argv[i + 1]) {
|
||||
Err(_) => return STATUS_INVALID_ARGS,
|
||||
Ok(0) => {
|
||||
streams
|
||||
.err
|
||||
.append(wgettext_fmt!("%ls: STEP must be a positive integer\n", cmd,));
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
Ok(x) => step = x,
|
||||
}
|
||||
|
||||
match parse::<i64>(streams, cmd, argv[i + 2]) {
|
||||
Err(_) => return STATUS_INVALID_ARGS,
|
||||
Ok(x) => end = x,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
streams
|
||||
.err
|
||||
.append(wgettext_fmt!("%ls: too many arguments\n", cmd,));
|
||||
return Some(1);
|
||||
}
|
||||
}
|
||||
|
||||
if end <= start {
|
||||
streams
|
||||
.err
|
||||
.append(wgettext_fmt!("%ls: END must be greater than START\n", cmd,));
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
// Possibilities can be abs(i64::MIN) + i64::MAX,
|
||||
// so we do this as i128
|
||||
let possibilities = (end as i128 - start as i128) / (step as i128);
|
||||
|
||||
if possibilities == 0 {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
"%ls: range contains only one possible value\n",
|
||||
cmd,
|
||||
));
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
let rand = engine.gen_range(0..=possibilities);
|
||||
|
||||
let result = start as i128 + rand as i128 * step as i128;
|
||||
|
||||
// We do our math as i128,
|
||||
// and then we check if it fits in 64 bit - signed or unsigned!
|
||||
match i64::try_from(result) {
|
||||
Ok(x) => {
|
||||
streams.out.append(sprintf!("%d\n"L, x));
|
||||
return STATUS_CMD_OK;
|
||||
},
|
||||
Err(_) => {
|
||||
match u64::try_from(result) {
|
||||
Ok(x) => {
|
||||
streams.out.append(sprintf!("%d\n"L, x));
|
||||
return STATUS_CMD_OK;
|
||||
},
|
||||
Err(_) => {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
"%ls: range contains only one possible value\n",
|
||||
cmd,
|
||||
));
|
||||
return STATUS_INVALID_ARGS;
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
|
@ -118,6 +118,7 @@ pub fn run_builtin(
|
|||
RustBuiltin::Echo => super::echo::echo(parser, streams, args),
|
||||
RustBuiltin::Emit => super::emit::emit(parser, streams, args),
|
||||
RustBuiltin::Exit => super::exit::exit(parser, streams, args),
|
||||
RustBuiltin::Random => super::random::random(parser, streams, args),
|
||||
RustBuiltin::Return => super::r#return::r#return(parser, streams, args),
|
||||
RustBuiltin::Wait => wait::wait(parser, streams, args),
|
||||
}
|
||||
|
|
|
@ -106,11 +106,19 @@ where
|
|||
negative = false;
|
||||
}
|
||||
let consumed_all = chars.peek() == None;
|
||||
Ok(ParseResult { result, negative, consumed_all })
|
||||
Ok(ParseResult {
|
||||
result,
|
||||
negative,
|
||||
consumed_all,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse some iterator over Chars into some Integer type, optionally with a radix.
|
||||
fn fish_wcstoi_impl<Int, Chars>(src: Chars, mradix: Option<u32>, consume_all: bool) -> Result<Int, Error>
|
||||
fn fish_wcstoi_impl<Int, Chars>(
|
||||
src: Chars,
|
||||
mradix: Option<u32>,
|
||||
consume_all: bool,
|
||||
) -> Result<Int, Error>
|
||||
where
|
||||
Chars: Iterator<Item = char>,
|
||||
Int: PrimInt,
|
||||
|
@ -120,7 +128,10 @@ where
|
|||
let signed = Int::min_value() < Int::zero();
|
||||
|
||||
let ParseResult {
|
||||
result, negative, consumed_all, ..
|
||||
result,
|
||||
negative,
|
||||
consumed_all,
|
||||
..
|
||||
} = fish_parse_radix(src, mradix)?;
|
||||
|
||||
if !signed && negative {
|
||||
|
@ -169,7 +180,11 @@ where
|
|||
fish_wcstoi_impl(src, Some(radix), false)
|
||||
}
|
||||
|
||||
pub fn fish_wcstoi_radix_all<Int, Chars>(src: Chars, radix: Option<u32>, consume_all: bool) -> Result<Int, Error>
|
||||
pub fn fish_wcstoi_radix_all<Int, Chars>(
|
||||
src: Chars,
|
||||
radix: Option<u32>,
|
||||
consume_all: bool,
|
||||
) -> Result<Int, Error>
|
||||
where
|
||||
Chars: Iterator<Item = char>,
|
||||
Int: PrimInt,
|
||||
|
|
|
@ -50,7 +50,6 @@
|
|||
#include "builtins/path.h"
|
||||
#include "builtins/printf.h"
|
||||
#include "builtins/pwd.h"
|
||||
#include "builtins/random.h"
|
||||
#include "builtins/read.h"
|
||||
#include "builtins/realpath.h"
|
||||
#include "builtins/set.h"
|
||||
|
@ -401,7 +400,7 @@ static constexpr builtin_data_t builtin_datas[] = {
|
|||
{L"path", &builtin_path, N_(L"Handle paths")},
|
||||
{L"printf", &builtin_printf, N_(L"Prints formatted text")},
|
||||
{L"pwd", &builtin_pwd, N_(L"Print the working directory")},
|
||||
{L"random", &builtin_random, N_(L"Generate random number")},
|
||||
{L"random", &implemented_in_rust, N_(L"Generate random number")},
|
||||
{L"read", &builtin_read, N_(L"Read a line of input into variables")},
|
||||
{L"realpath", &builtin_realpath, N_(L"Show absolute path sans symlinks")},
|
||||
{L"return", &implemented_in_rust, N_(L"Stop the currently evaluated function")},
|
||||
|
@ -534,6 +533,9 @@ static maybe_t<RustBuiltin> try_get_rust_builtin(const wcstring &cmd) {
|
|||
if (cmd == L"exit") {
|
||||
return RustBuiltin::Exit;
|
||||
}
|
||||
if (cmd == L"random") {
|
||||
return RustBuiltin::Random;
|
||||
}
|
||||
if (cmd == L"wait") {
|
||||
return RustBuiltin::Wait;
|
||||
}
|
||||
|
|
|
@ -112,7 +112,8 @@ enum RustBuiltin : int32_t {
|
|||
Echo,
|
||||
Emit,
|
||||
Exit,
|
||||
Wait,
|
||||
Random,
|
||||
Return,
|
||||
Wait,
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -1,160 +0,0 @@
|
|||
// Implementation of the random builtin.
|
||||
#include "config.h" // IWYU pragma: keep
|
||||
|
||||
#include "random.h"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <cwchar>
|
||||
#include <random>
|
||||
|
||||
#include "../builtin.h"
|
||||
#include "../common.h"
|
||||
#include "../fallback.h" // IWYU pragma: keep
|
||||
#include "../io.h"
|
||||
#include "../maybe.h"
|
||||
#include "../wutil.h" // IWYU pragma: keep
|
||||
|
||||
/// \return a random-seeded engine.
|
||||
static std::minstd_rand get_seeded_engine() {
|
||||
std::minstd_rand engine;
|
||||
// seed engine with 2*32 bits of random data
|
||||
// for the 64 bits of internal state of minstd_rand
|
||||
std::random_device rd;
|
||||
std::seed_seq seed{rd(), rd()};
|
||||
engine.seed(seed);
|
||||
return engine;
|
||||
}
|
||||
|
||||
/// The random builtin generates random numbers.
|
||||
maybe_t<int> builtin_random(parser_t &parser, io_streams_t &streams, const wchar_t **argv) {
|
||||
const wchar_t *cmd = argv[0];
|
||||
int argc = builtin_count_args(argv);
|
||||
help_only_cmd_opts_t opts;
|
||||
|
||||
int optind;
|
||||
int retval = parse_help_only_cmd_opts(opts, &optind, argc, argv, parser, streams);
|
||||
if (retval != STATUS_CMD_OK) return retval;
|
||||
|
||||
if (opts.print_help) {
|
||||
builtin_print_help(parser, streams, cmd);
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
// We have a single engine which we lazily seed. Lock it here.
|
||||
static owning_lock<std::minstd_rand> s_engine{get_seeded_engine()};
|
||||
auto engine_lock = s_engine.acquire();
|
||||
std::minstd_rand &engine = *engine_lock;
|
||||
|
||||
int arg_count = argc - optind;
|
||||
long long start, end;
|
||||
unsigned long long step;
|
||||
bool choice = false;
|
||||
if (arg_count >= 1 && !std::wcscmp(argv[optind], L"choice")) {
|
||||
if (arg_count == 1) {
|
||||
streams.err.append_format(L"%ls: nothing to choose from\n", cmd);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
choice = true;
|
||||
start = 1;
|
||||
step = 1;
|
||||
end = arg_count - 1;
|
||||
} else {
|
||||
bool parse_error = false;
|
||||
auto parse_ll = [&](const wchar_t *str) {
|
||||
long long ll = fish_wcstoll(str);
|
||||
if (errno) {
|
||||
streams.err.append_format(BUILTIN_ERR_NOT_NUMBER, cmd, str);
|
||||
parse_error = true;
|
||||
}
|
||||
return ll;
|
||||
};
|
||||
auto parse_ull = [&](const wchar_t *str) {
|
||||
unsigned long long ull = fish_wcstoull(str);
|
||||
if (errno) {
|
||||
streams.err.append_format(BUILTIN_ERR_NOT_NUMBER, cmd, str);
|
||||
parse_error = true;
|
||||
}
|
||||
return ull;
|
||||
};
|
||||
if (arg_count == 0) {
|
||||
start = 0;
|
||||
end = 32767;
|
||||
step = 1;
|
||||
} else if (arg_count == 1) {
|
||||
long long seed = parse_ll(argv[optind]);
|
||||
if (parse_error) return STATUS_INVALID_ARGS;
|
||||
engine.seed(static_cast<uint32_t>(seed));
|
||||
return STATUS_CMD_OK;
|
||||
} else if (arg_count == 2) {
|
||||
start = parse_ll(argv[optind]);
|
||||
step = 1;
|
||||
end = parse_ll(argv[optind + 1]);
|
||||
} else if (arg_count == 3) {
|
||||
start = parse_ll(argv[optind]);
|
||||
step = parse_ull(argv[optind + 1]);
|
||||
end = parse_ll(argv[optind + 2]);
|
||||
} else {
|
||||
streams.err.append_format(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
if (parse_error) {
|
||||
return STATUS_INVALID_ARGS;
|
||||
} else if (start >= end) {
|
||||
streams.err.append_format(L"%ls: END must be greater than START\n", cmd);
|
||||
return STATUS_INVALID_ARGS;
|
||||
} else if (step == 0) {
|
||||
streams.err.append_format(L"%ls: STEP must be a positive integer\n", cmd);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
}
|
||||
|
||||
// only for negative argument
|
||||
auto safe_abs = [](long long ll) -> unsigned long long {
|
||||
return -static_cast<unsigned long long>(ll);
|
||||
};
|
||||
long long real_end;
|
||||
if (start >= 0 || end < 0) {
|
||||
// 0 <= start <= end
|
||||
long long diff = end - start;
|
||||
// 0 <= diff <= LL_MAX
|
||||
real_end = start + static_cast<long long>(diff / step);
|
||||
} else {
|
||||
// start < 0 <= end
|
||||
unsigned long long abs_start = safe_abs(start);
|
||||
unsigned long long diff = (end + abs_start);
|
||||
real_end = diff / step - abs_start;
|
||||
}
|
||||
|
||||
if (!choice && start == real_end) {
|
||||
streams.err.append_format(L"%ls: range contains only one possible value\n", cmd);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
std::uniform_int_distribution<long long> dist(start, real_end);
|
||||
long long random = dist(engine);
|
||||
long long result;
|
||||
if (start >= 0) {
|
||||
// 0 <= start <= random <= end
|
||||
long long diff = random - start;
|
||||
// 0 < step * diff <= end - start <= LL_MAX
|
||||
result = start + static_cast<long long>(diff * step);
|
||||
} else if (random < 0) {
|
||||
// start <= random < 0
|
||||
long long diff = random - start;
|
||||
result = diff * step - safe_abs(start);
|
||||
} else {
|
||||
// start < 0 <= random
|
||||
unsigned long long abs_start = safe_abs(start);
|
||||
unsigned long long diff = (random + abs_start);
|
||||
result = diff * step - abs_start;
|
||||
}
|
||||
|
||||
if (choice) {
|
||||
streams.out.append_format(L"%ls\n", argv[optind + result]);
|
||||
} else {
|
||||
streams.out.append_format(L"%lld\n", result);
|
||||
}
|
||||
return STATUS_CMD_OK;
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
// Prototypes for executing builtin_random function.
|
||||
#ifndef FISH_BUILTIN_RANDOM_H
|
||||
#define FISH_BUILTIN_RANDOM_H
|
||||
|
||||
#include "../maybe.h"
|
||||
|
||||
class parser_t;
|
||||
struct io_streams_t;
|
||||
|
||||
maybe_t<int> builtin_random(parser_t &parser, io_streams_t &streams, const wchar_t **argv);
|
||||
#endif
|
|
@ -40,7 +40,7 @@ random choic a b c
|
|||
#CHECKERR: random: too many arguments
|
||||
|
||||
function check_boundaries
|
||||
if not test $argv[1] -ge $argv[2] -a $argv[1] -le $argv[3]
|
||||
if not test "$argv[1]" -ge "$argv[2]" -a "$argv[1]" -le "$argv[3]"
|
||||
printf "Unexpected: %s <= %s <= %s not verified\n" $argv[2] $argv[1] $argv[3] >&2
|
||||
return 1
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue
Block a user