use crate::value::Value;
use chrono::{Local, LocalResult, NaiveDate, TimeZone, Utc};
use std::cmp::Ordering;
pub trait AwkHost: Send {
fn field_get(&mut self, i: i64) -> Value {
let _ = i;
Value::str("")
}
fn field_set(&mut self, i: i64, v: Value) {
let _ = (i, v);
}
fn nf(&mut self) -> i64 {
0
}
fn set_record(&mut self, v: Value) {
let _ = v;
}
fn special_get(&mut self, name: &str) -> Value {
let _ = name;
Value::Undef
}
fn special_set(&mut self, name: &str, v: Value) {
let _ = (name, v);
}
fn print(&mut self, args: &[Value]) {
let _ = args;
}
fn printf(&mut self, fmt: &str, args: &[Value]) {
let _ = (fmt, args);
}
fn sprintf(&mut self, fmt: &str, args: &[Value]) -> Value {
let _ = (fmt, args);
Value::str("")
}
fn getline(&mut self, source: usize, operand: Option<&str>, var_name: Option<&str>) -> i64 {
let _ = (source, operand, var_name);
0
}
fn length(&mut self, s: Option<&Value>) -> i64 {
awk_length(s)
}
fn substr(&mut self, s: &Value, m: i64, n: Option<i64>) -> Value {
awk_substr(s, m, n)
}
fn index(&mut self, s: &Value, t: &Value) -> i64 {
awk_index(s, t)
}
fn split(&mut self, s: &Value, arr_name: &str, fs: Option<&Value>) -> i64 {
let _ = (s, arr_name, fs);
0
}
fn sub(&mut self, re: &Value, repl: &Value, target_ref: &AwkLvalue) -> i64 {
let _ = (re, repl, target_ref);
0
}
fn gsub(&mut self, re: &Value, repl: &Value, target_ref: &AwkLvalue) -> i64 {
let _ = (re, repl, target_ref);
0
}
fn match_re(&mut self, s: &Value, re: &Value) -> i64 {
let _ = (s, re);
0
}
fn gensub(&mut self, re: &Value, repl: &Value, how: &Value, target: Option<&Value>) -> Value {
let _ = (re, repl, how, target);
Value::str("")
}
fn tolower(&mut self, s: &Value) -> Value {
awk_tolower(s)
}
fn toupper(&mut self, s: &Value) -> Value {
awk_toupper(s)
}
fn int(&mut self, x: &Value) -> Value {
awk_int(x)
}
fn sqrt(&mut self, x: &Value) -> Value {
Value::Float(x.to_float().sqrt())
}
fn sin(&mut self, x: &Value) -> Value {
Value::Float(awk_canon_nan(x.to_float().sin()))
}
fn cos(&mut self, x: &Value) -> Value {
Value::Float(awk_canon_nan(x.to_float().cos()))
}
fn exp(&mut self, x: &Value) -> Value {
Value::Float(awk_canon_nan(x.to_float().exp()))
}
fn log(&mut self, x: &Value) -> Value {
Value::Float(x.to_float().ln())
}
fn atan2(&mut self, y: &Value, x: &Value) -> Value {
Value::Float(awk_canon_nan(y.to_float().atan2(x.to_float())))
}
fn and(&mut self, args: &[Value]) -> Value {
Value::Int(awk_fold_and(args))
}
fn or(&mut self, args: &[Value]) -> Value {
Value::Int(awk_fold_or(args))
}
fn xor(&mut self, args: &[Value]) -> Value {
Value::Int(awk_fold_xor(args))
}
fn compl(&mut self, v: &Value) -> Value {
Value::Int(awk_compl(v))
}
fn lshift(&mut self, v: &Value, n: &Value) -> Value {
Value::Int(awk_lshift(v, n))
}
fn rshift(&mut self, v: &Value, n: &Value) -> Value {
Value::Int(awk_rshift(v, n))
}
fn strtonum(&mut self, s: &Value) -> Value {
Value::Float(awk_strtonum(&s.to_str()))
}
fn systime(&mut self) -> Value {
Value::Float(awk_systime())
}
fn strftime(&mut self, args: &[Value]) -> Value {
awk_strftime(args)
}
fn mktime(&mut self, args: &[Value]) -> Value {
awk_mktime(args)
}
fn ord(&mut self, arg: &Value) -> Value {
awk_ord(arg)
}
fn chr(&mut self, arg: &Value) -> Value {
awk_chr(arg)
}
fn mkbool(&mut self, arg: &Value) -> Value {
awk_mkbool(arg)
}
fn intdiv(&mut self, a: &Value, b: &Value) -> Value {
awk_intdiv(a, b)
}
fn intdiv0(&mut self, a: &Value, b: &Value) -> Value {
awk_intdiv0(a, b)
}
fn array_get(&mut self, arr_name: &str, key: &Value) -> Value {
let _ = (arr_name, key);
Value::str("")
}
fn array_set(&mut self, arr_name: &str, key: &Value, v: Value) {
let _ = (arr_name, key, v);
}
fn array_exists(&mut self, arr_name: &str, key: &Value) -> bool {
let _ = (arr_name, key);
false
}
fn array_delete(&mut self, arr_name: &str, key: &Value) {
let _ = (arr_name, key);
}
fn array_clear(&mut self, arr_name: &str) {
let _ = arr_name;
}
fn array_len(&mut self, arr_name: &str) -> i64 {
let _ = arr_name;
0
}
fn compare(&mut self, a: &Value, b: &Value) -> Ordering {
let (sa, sb) = (a.to_str(), b.to_str());
let looks_num = |s: &str| s.trim().parse::<f64>().is_ok() || s.trim().is_empty();
if looks_num(&sa) && looks_num(&sb) {
a.to_float()
.partial_cmp(&b.to_float())
.unwrap_or(Ordering::Equal)
} else {
sa.cmp(&sb)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AwkLvalue {
Var(String),
Field(i64),
ArrayElem(String, String),
}
#[derive(Debug, Default, Clone, Copy)]
pub struct DefaultAwkHost;
impl AwkHost for DefaultAwkHost {}
pub fn awk_length(s: Option<&Value>) -> i64 {
s.map(|v| v.to_str().chars().count() as i64).unwrap_or(0)
}
pub fn awk_substr(s: &Value, m: i64, n: Option<i64>) -> Value {
let text: Vec<char> = s.to_str().chars().collect();
let len = text.len() as i64;
let start = m.max(1);
let end = match n {
Some(n) => (m + n).min(len + 1),
None => len + 1,
};
if start >= end || start > len {
return Value::str("");
}
let s0 = (start - 1) as usize;
let s1 = (end - 1).min(len) as usize;
Value::str(text[s0..s1].iter().collect::<String>())
}
pub fn awk_index(s: &Value, t: &Value) -> i64 {
let hay = s.to_str();
let needle = t.to_str();
match hay.find(&needle) {
Some(byte_off) => hay[..byte_off].chars().count() as i64 + 1,
None => 0,
}
}
pub fn awk_tolower(s: &Value) -> Value {
Value::str(s.to_str().to_lowercase())
}
pub fn awk_toupper(s: &Value) -> Value {
Value::str(s.to_str().to_uppercase())
}
pub fn awk_int(x: &Value) -> Value {
let t = x.to_float().trunc();
if t.is_finite() && t >= i64::MIN as f64 && t <= i64::MAX as f64 {
Value::Int(t as i64)
} else {
Value::Float(t)
}
}
#[inline]
pub fn awk_canon_nan(r: f64) -> f64 {
if r.is_nan() {
f64::NAN
} else {
r
}
}
#[inline]
fn awk_to_u64(n: f64) -> u64 {
n.trunc() as i64 as u64
}
pub fn awk_fold_and(args: &[Value]) -> i64 {
args.iter()
.map(|v| awk_to_u64(v.to_float()))
.reduce(|a, b| a & b)
.unwrap_or(0) as i64
}
pub fn awk_fold_or(args: &[Value]) -> i64 {
args.iter()
.map(|v| awk_to_u64(v.to_float()))
.reduce(|a, b| a | b)
.unwrap_or(0) as i64
}
pub fn awk_fold_xor(args: &[Value]) -> i64 {
args.iter()
.map(|v| awk_to_u64(v.to_float()))
.reduce(|a, b| a ^ b)
.unwrap_or(0) as i64
}
pub fn awk_compl(v: &Value) -> i64 {
(!awk_to_u64(v.to_float())) as i64
}
pub fn awk_lshift(v: &Value, n: &Value) -> i64 {
let x = awk_to_u64(v.to_float());
let s = (awk_to_u64(n.to_float()) & 0x3f) as u32;
(x << s) as i64
}
pub fn awk_rshift(v: &Value, n: &Value) -> i64 {
let x = awk_to_u64(v.to_float());
let s = (awk_to_u64(n.to_float()) & 0x3f) as u32;
(x >> s) as i64
}
pub fn awk_strtonum(s: &str) -> f64 {
let t = s.trim();
if t.is_empty() {
return 0.0;
}
let first = t.as_bytes()[0];
if !matches!(first, b'+' | b'-' | b'.' | b'0'..=b'9') {
return 0.0;
}
let unsigned_hex_or_octal = !matches!(first, b'+' | b'-');
if unsigned_hex_or_octal {
if t.starts_with("0x") || t.starts_with("0X") {
return u64::from_str_radix(&t[2..], 16)
.map(|v| v as f64)
.unwrap_or(0.0);
}
if t.len() > 1
&& t.starts_with('0')
&& !t.contains('.')
&& !t.contains('e')
&& !t.contains('E')
{
return i64::from_str_radix(t, 8).map(|v| v as f64).unwrap_or(0.0);
}
}
if let Some(prefix) = awk_longest_f64_prefix(t) {
if let Ok(v) = prefix.parse::<f64>() {
return v;
}
}
0.0
}
fn awk_longest_f64_prefix(s: &str) -> Option<&str> {
if s.is_empty() {
return None;
}
for end in (1..=s.len()).rev() {
let p = &s[..end];
if !awk_numeric_prefix_acceptable(p, end < s.len() && awk_next_byte_is_alnum(s, end)) {
continue;
}
if p.parse::<f64>().is_ok() {
return Some(p);
}
}
None
}
#[inline]
fn awk_next_byte_is_alnum(s: &str, end: usize) -> bool {
s.as_bytes()
.get(end)
.map(|c| c.is_ascii_alphanumeric())
.unwrap_or(false)
}
#[inline]
fn awk_numeric_prefix_acceptable(p: &str, has_trailing_alnum: bool) -> bool {
if p.bytes().any(|c| c.is_ascii_digit()) {
return true;
}
if has_trailing_alnum {
return false;
}
let b = p.as_bytes();
if b.len() != 4 {
return false;
}
if !matches!(b[0], b'+' | b'-') {
return false;
}
let tail = &p[1..];
tail.eq_ignore_ascii_case("inf") || tail.eq_ignore_ascii_case("nan")
}
pub fn awk_systime() -> f64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs_f64())
.unwrap_or(0.0)
}
pub fn awk_rand(seed: &mut u64) -> f64 {
*seed = seed.wrapping_mul(1103515245).wrapping_add(12345);
f64::from((*seed >> 16) as u32 & 0x7fff) / 32768.0
}
pub fn awk_srand(seed: &mut u64, n: Option<u64>) -> f64 {
let prev = *seed;
*seed = n.unwrap_or_else(|| {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() ^ (d.subsec_nanos() as u64))
.unwrap_or(1)
});
(prev & 0xffff_ffff) as f64
}
pub fn awk_strftime(args: &[Value]) -> Value {
let default_fmt = "%a %b %e %H:%M:%S %Z %Y";
let (fmt, ts, utc) = match args.len() {
0 => (default_fmt.to_string(), awk_systime(), false),
1 => (args[0].to_str(), awk_systime(), false),
2 => (args[0].to_str(), args[1].to_float(), false),
_ => (
args[0].to_str(),
args[1].to_float(),
args[2].to_float() != 0.0,
),
};
let secs = ts.floor() as i64;
let nsec = ((ts - secs as f64) * 1e9).round().clamp(0.0, 1e9 - 1.0) as u32;
let out = if utc {
match Utc.timestamp_opt(secs, nsec).single() {
Some(dt) => dt.format(&fmt).to_string(),
None => String::new(),
}
} else {
match Local.timestamp_opt(secs, nsec).single() {
Some(dt) => dt.format(&fmt).to_string(),
None => String::new(),
}
};
Value::str(out)
}
pub fn awk_mktime(args: &[Value]) -> Value {
let s = args.first().map(|v| v.to_str()).unwrap_or_default();
let utc = args.len() >= 2 && args[1].to_float() != 0.0;
Value::Float(awk_mktime_with_utc(&s, utc))
}
fn awk_mktime_with_utc(s: &str, utc: bool) -> f64 {
let parts: Vec<&str> = s.split_whitespace().collect();
if parts.len() < 6 {
return -1.0;
}
let y: i32 = match parts[0].parse() {
Ok(v) => v,
Err(_) => return -1.0,
};
let mo: u32 = match parts[1].parse() {
Ok(v) => v,
Err(_) => return -1.0,
};
let d: u32 = match parts[2].parse() {
Ok(v) => v,
Err(_) => return -1.0,
};
let h: u32 = match parts[3].parse() {
Ok(v) => v,
Err(_) => return -1.0,
};
let mi: u32 = match parts[4].parse() {
Ok(v) => v,
Err(_) => return -1.0,
};
let se: u32 = match parts[5].parse() {
Ok(v) => v,
Err(_) => return -1.0,
};
let naive = match NaiveDate::from_ymd_opt(y, mo, d) {
Some(date) => match date.and_hms_opt(h, mi, se) {
Some(n) => n,
None => return -1.0,
},
None => return -1.0,
};
if utc {
match Utc.from_local_datetime(&naive) {
LocalResult::Single(dt) => dt.timestamp() as f64,
LocalResult::Ambiguous(_, _) | LocalResult::None => -1.0,
}
} else {
match Local.from_local_datetime(&naive) {
LocalResult::Single(dt) => dt.timestamp() as f64,
LocalResult::Ambiguous(_, _) | LocalResult::None => -1.0,
}
}
}
pub fn awk_ord(arg: &Value) -> Value {
let s = arg.to_str();
let n = s.chars().next().map(|c| c as u32).unwrap_or(0);
Value::Float(f64::from(n))
}
pub fn awk_chr(arg: &Value) -> Value {
let u = arg.to_float() as u32;
match char::from_u32(u) {
Some(c) => Value::str(c.to_string()),
None => Value::str(""),
}
}
pub fn awk_mkbool(arg: &Value) -> Value {
Value::Float(if arg.is_truthy() { 1.0 } else { 0.0 })
}
pub fn awk_intdiv(a: &Value, b: &Value) -> Value {
let bf = b.to_float();
if bf == 0.0 {
return Value::Undef;
}
let ai = a.to_float() as i64;
let bi = bf as i64;
if bi == 0 {
return Value::Undef;
}
Value::Float((ai / bi) as f64)
}
pub fn awk_intdiv0(a: &Value, b: &Value) -> Value {
let bf = b.to_float();
if bf == 0.0 {
return Value::Float(0.0);
}
let bi = bf as i64;
if bi == 0 {
return Value::Float(0.0);
}
Value::Float((a.to_float() as i64 / bi) as f64)
}