use std::{
cell::RefCell,
collections::{HashMap, hash_map::Entry},
f64::consts::TAU,
path::{Path, PathBuf},
sync::{Arc, LazyLock, OnceLock},
};
use ecow::EcoVec;
use rand::prelude::*;
use crate::{
Array, Boxed, PrimDocFragment, SysBackend, Uiua, Value, WILDCARD_NAN, media,
parse_doc_line_fragments,
};
pub struct ConstantDef {
pub name: &'static str,
pub class: ConstClass,
pub value: LazyLock<ConstantValue>,
pub doc: LazyLock<String>,
pub deprecation: Option<&'static str>,
}
impl ConstantDef {
pub fn doc(&self) -> &str {
&self.doc
}
pub fn doc_frags(&self) -> Vec<PrimDocFragment> {
parse_doc_line_fragments(self.doc())
}
pub fn is_deprecated(&self) -> bool {
self.deprecation.is_some()
}
}
pub enum ConstantValue {
Static(Value),
Big(BigConstant),
Music,
BadAppleAudio,
ThisFile,
ThisFileName,
ThisFileDir,
WorkingDir,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BigConstant {
Uiua386,
Elevation,
BadAppleGif,
Amen,
}
impl ConstantValue {
pub(crate) fn resolve(
&self,
current_file_path: Option<&Path>,
backend: Arc<dyn SysBackend>,
) -> Result<Value, String> {
let current_file_path = current_file_path.map(|p| {
let mut path = PathBuf::new();
for comp in p.components() {
path.push(comp);
}
path
});
Ok(match self {
ConstantValue::Static(val) => val.clone(),
&ConstantValue::Big(big) => {
thread_local! {
static CACHE: RefCell<HashMap<BigConstant, Value>> = Default::default();
}
CACHE.with(|cache| -> Result<_, String> {
let mut cache = cache.borrow_mut();
Ok(match cache.entry(big) {
Entry::Occupied(e) => e.get().clone(),
Entry::Vacant(e) => {
let bytes = backend.big_constant(big)?;
e.insert(match big {
BigConstant::Uiua386 => bytes.into_owned().into(),
BigConstant::Elevation => {
media::image_bytes_to_array(&bytes, true, false)?.into()
}
BigConstant::BadAppleGif => {
let (_, mut val) = media::gif_bytes_to_value_gray(&bytes)?;
let Value::Byte(_) = &mut val else {
return Err(
"Bad Apple gif data is not properly rounded to 0 or 1"
.into(),
);
};
val
}
BigConstant::Amen => {
let (samples, sr) =
media::array_from_wav_bytes(&bytes).unwrap();
let new_row_count = (samples.row_count() as f64
* backend.audio_sample_rate() as f64
/ sr as f64)
.round()
as usize;
samples.keep_scalar_real_impl(new_row_count).into()
}
})
.clone()
}
})
})?
}
ConstantValue::Music => {
static MUSIC: OnceLock<Value> = OnceLock::new();
MUSIC
.get_or_init(|| music_constant(backend.as_ref()))
.clone()
}
ConstantValue::BadAppleAudio => {
static MUSIC: OnceLock<Value> = OnceLock::new();
MUSIC
.get_or_init(|| {
let mut env = Uiua::with_backend(backend);
env.run_str(include_str!("assets/bad_apple.ua")).unwrap();
env.pop("samples").unwrap()
})
.clone()
}
ConstantValue::ThisFile => {
current_file_path.map_or_else(|| "".into(), |p| p.display().to_string().into())
}
ConstantValue::ThisFileName => current_file_path
.and_then(|p| p.file_name().map(|f| f.to_string_lossy().into_owned()))
.unwrap_or_default()
.into(),
ConstantValue::ThisFileDir => {
let mut path = current_file_path
.and_then(|p| p.parent().map(|f| f.display().to_string()))
.unwrap_or_default();
if path.is_empty() {
path = ".".into();
}
path.into()
}
ConstantValue::WorkingDir => std::env::current_dir()
.unwrap_or_default()
.display()
.to_string()
.into(),
})
}
}
impl<T> From<T> for ConstantValue
where
T: Into<Value>,
{
fn from(val: T) -> Self {
ConstantValue::Static(val.into())
}
}
macro_rules! constant {
($(
$(#[doc = $doc:literal])+
(
$(#[$attr:meta])*
$name:literal,
$class:ident,
$value:expr
$(, deprecated($deprecation:literal))?
)
),* $(,)?) => {
const COUNT: usize = {
let mut count = 0;
$(
$(#[$attr])*
{
_ = $name;
count += 1;
}
)*
count
};
#[allow(path_statements)]
pub static CONSTANTS: [ConstantDef; COUNT] =
[$(
$(#[$attr])*
ConstantDef {
name: $name,
value: LazyLock::new(|| {$value.into()}),
class: ConstClass::$class,
doc: LazyLock::new(|| {
let mut s = String::new();
$(
s.push_str($doc.trim());
s.push('\n');
)*
s.pop();
s
}),
deprecation: { None::<&'static str> $(; Some($deprecation))? }
},
)*];
};
}
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ConstClass {
Math,
External,
Time,
Media,
System,
Color,
Spatial,
Flags,
Fun,
}
constant!(
("e", Math, std::f64::consts::E),
("φ", Math, 1.618_033_988_749_895_f64),
("γ", Math, 0.577_215_664_901_532_9_f64),
("r", Math, crate::Complex::ONE),
("i", Math, crate::Complex::I),
("NaN", Math, f64::NAN),
("W", Math, WILDCARD_NAN),
("MaxInt", Math, 2f64.powi(53)),
("ε", Math, f64::EPSILON),
("A₁", Spatial, [1, -1]),
("A₂", Spatial, [[0, 1], [1, 0], [0, -1], [-1, 0]]),
("A₃", Spatial, [[0, 1, 0], [1, 0, 0], [0, -1, 0], [-1, 0, 0], [0, 0, 1], [0, 0, -1]]),
("C₂", Spatial, [[1, 1], [1, -1], [-1, -1], [-1, 1]]),
("C₃", Spatial, [
[1, 1, 1], [1, -1, 1], [-1, -1, 1], [-1, 1, 1],
[1, 1, -1], [1, -1, -1], [-1, -1, -1], [-1, 1, -1]
]),
("E₃", Spatial, [
[1, 1, 0], [1, -1, 0], [-1, -1, 0], [-1, 1, 0],
[0, 1, 1], [1, 0, 1], [0, -1, 1], [-1, 0, 1],
[0, 1, -1], [1, 0, -1], [0, -1, -1], [-1, 0, -1]
]),
("Os", System, std::env::consts::OS, deprecated("Use the os function instead")),
("Family", System, std::env::consts::FAMILY, deprecated("Use the osfamily function instead")),
("Arch", System, std::env::consts::ARCH, deprecated("Use the arch function instead")),
("ExeExt", System, std::env::consts::EXE_EXTENSION, deprecated("Use the exeext function instead")),
("DllExt", System, std::env::consts::DLL_EXTENSION, deprecated("Use the dllext function instead")),
("Sep", System, std::path::MAIN_SEPARATOR, deprecated("Use the pathsep function instead")),
("ThisFile", System, ConstantValue::ThisFile),
("ThisFileName", System, ConstantValue::ThisFileName),
("ThisFileDir", System, ConstantValue::ThisFileDir),
("WorkingDir", System, ConstantValue::WorkingDir),
("NumProcs", System, num_cpus::get() as f64, deprecated("Use the numprocs function instead")),
("True", External, Array::json_bool(true)),
("False", External, Array::json_bool(false)),
("NULL", External, Value::null()),
("HexDigits", Math, "0123456789abcdef"),
(
"Days",
Time,
[
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
]
.as_slice()
),
(
"Months",
Time,
[
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
]
.as_slice()
),
(
"MonthDays",
Time,
[31u8, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
),
(
"LeapMonthDays",
Time,
[31u8, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
),
("White", Color, [1.0, 1.0, 1.0]),
("Black", Color, [0.0, 0.0, 0.0]),
("Red", Color, [1.0, 0.0, 0.0]),
("Orange", Color, [1.0, 0.5, 0.0]),
("Yellow", Color, [1.0, 1.0, 0.0]),
("Green", Color, [0.0, 1.0, 0.0]),
("Cyan", Color, [0.0, 1.0, 1.0]),
("Blue", Color, [0.0, 0.0, 1.0]),
("Purple", Color, [0.5, 0.0, 1.0]),
("Magenta", Color, [1.0, 0.0, 1.0]),
(
"Planets",
Fun,
["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"].as_slice()
),
(
"PlanetSymbols",
Fun,
['☿', '♀', '🜨', '♂', '♃', '♄', '⛢', '♆']
),
(
"Zodiac",
Fun,
[
"Aries",
"Taurus",
"Gemini",
"Cancer",
"Leo",
"Virgo",
"Libra",
"Scorpio",
"Sagittarius",
"Capricorn",
"Aquarius",
"Pisces"
]
.as_slice()
),
(
"ZodiacSymbols",
Fun,
['♈', '♉', '♊', '♋', '♌', '♍', '♎', '♏', '♐', '♑', '♒', '♓']
),
("Suits", Fun, ['♣', '♦', '♥', '♠']),
(
"Cards",
Fun,
["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"].as_slice()
),
(
"Chess",
Fun,
Array::new(
[2, 6],
['♟', '♜', '♞', '♝', '♛', '♚', '♙', '♖', '♘', '♗', '♕', '♔']
)
),
("Moon", Fun, "🌑🌒🌓🌔🌕🌖🌗🌘"),
("Skin", Fun, "🏻🏼🏽🏾🏿"),
("People", Fun, "👨👩👦👧"),
("Hair", Fun, "🦰🦱🦲🦳"),
(
"Elements",
Fun,
[
"Hydrogen", "Helium", "Lithium", "Beryllium", "Boron", "Carbon", "Nitrogen", "Oxygen", "Fluorine", "Neon", "Sodium", "Magnesium", "Aluminium",
"Silicon", "Phosphorus", "Sulfur", "Chlorine", "Argon", "Potassium", "Calcium", "Scandium", "Titanium", "Vanadium", "Chromium", "Manganese",
"Iron", "Cobalt", "Nickel", "Copper", "Zinc", "Gallium", "Germanium", "Arsenic", "Selenium", "Bromine", "Krypton", "Rubidium", "Strontium",
"Yttrium", "Zirconium", "Niobium", "Molybdenum", "Technetium", "Ruthenium", "Rhodium", "Palladium", "Silver", "Cadmium", "Indium", "Tin",
"Antimony", "Tellurium", "Iodine", "Xenon", "Cesium", "Barium", "Lanthanum", "Cerium", "Praseodymium", "Neodymium", "Promethium", "Samarium",
"Europium", "Gadolinium", "Terbium", "Dysprosium", "Holmium", "Erbium", "Thulium", "Ytterbium", "Lutetium", "Hafnium", "Tantalum", "Tungsten",
"Rhenium", "Osmium", "Iridium", "Platinum", "Gold", "Mercury", "Thallium", "Lead", "Bismuth", "Polonium", "Astatine", "Radon", "Francium",
"Radium", "Actinium", "Thorium", "Protactinium", "Uranium", "Neptunium", "Plutonium", "Americium", "Curium", "Berkelium", "Californium",
"Einsteinium", "Fermium", "Mendelevium", "Nobelium", "Lawrencium", "Rutherfordium", "Dubnium", "Seaborgium", "Bohrium", "Hassium", "Meitnerium",
"Darmstadtium", "Roentgenium", "Copernicium", "Nihonium", "Flerovium", "Moscovium", "Livermorium", "Tennessine", "Oganesson"
].as_slice()
),
(
"ElementSymbols",
Fun,
[
"H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne", "Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar", "K", "Ca", "Sc", "Ti", "V",
"Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge", "As", "Se", "Br", "Kr", "Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc", "Ru", "Rh",
"Pd", "Ag", "Cd", "In", "Sn", "Sb", "Te", "I", "Xe", "Cs", "Ba", "La", "Ce", "Pr", "Nd", "Pm", "Sm", "Eu", "Gd", "Tb", "Dy", "Ho",
"Er", "Tm", "Yb", "Lu", "Hf", "Ta", "W", "Re", "Os", "Ir", "Pt", "Au", "Hg", "Tl", "Pb", "Bi", "Po", "At", "Rn", "Fr", "Ra", "Ac",
"Th", "Pa", "U", "Np", "Pu", "Am", "Cm", "Bk", "Cf", "Es", "Fm", "Md", "No", "Lr", "Rf", "Db", "Sg", "Bh", "Hs", "Mt", "Ds", "Rg",
"Cn", "Nh", "Fl", "Mc", "Lv", "Ts", "Og"
].as_slice()
),
(#[cfg(feature = "image")] "Logo", Media, media::image_bytes_to_array(include_bytes!("assets/uiua-logo-512.png"), false, true).unwrap()),
(#[cfg(feature = "image")] "Lena", Media, media::image_bytes_to_array(include_bytes!("assets/lena.jpg"), false, false).unwrap()),
(#[cfg(feature = "image")] "LenaDepth", Media, media::image_bytes_to_array(include_bytes!("assets/lena-depth.png"), true, false).unwrap()),
(#[cfg(feature = "image")] "Cats", Media, media::image_bytes_to_array(include_bytes!("assets/cats.webp"), false, false).unwrap()),
(#[cfg(feature = "image")] "CatsDepth", Media, media::image_bytes_to_array(include_bytes!("assets/cats-depth.png"), true, false).unwrap()),
(#[cfg(feature = "image")] "Elevation", Media, ConstantValue::Big(BigConstant::Elevation)),
("Music", Media, ConstantValue::Music),
("Amen", Media, ConstantValue::Big(BigConstant::Amen)),
("Bad", Media, ConstantValue::BadAppleAudio),
("Apple", Media, ConstantValue::Big(BigConstant::BadAppleGif)),
("Lorem", Media, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."),
("Rainbow", Flags, [[0.894, 0.012, 0.012], [1.0, 0.647, 0.173], [1.0, 1.0, 0.255], [0.0, 0.502, 0.094], [0.0, 0.0, 0.976], [0.525, 0.0, 0.49]]),
("Lesbian", Flags, [[0.831, 0.173, 0.0], [0.992, 0.596, 0.333], [1.0, 1.0, 1.0], [0.82, 0.38, 0.635], [0.635, 0.004, 0.38]]),
("Gay", Flags, [[0.031, 0.55, 0.44], [0.149, 0.808, 0.667], [0.596, 0.91, 0.757], [1.0, 1.0, 1.0], [0.482, 0.678, 0.886], [0.314, 0.286, 0.8], [0.239, 0.102, 0.471]]),
("Bi", Flags, [[0.839, 0.008, 0.439], [0.839, 0.008, 0.439], [0.608, 0.31, 0.588], [0.0, 0.22, 0.659], [0.0, 0.22, 0.659]]),
("Trans", Flags, [[0.357, 0.808, 0.98], [0.961, 0.663, 0.722], [1.0, 1.0, 1.0], [0.961, 0.663, 0.722], [0.357, 0.808, 0.98]]),
("Pan", Flags, [[1.0, 0.129, 0.549], [1.0, 0.847, 0.0], [0.129, 0.694, 1.0]]),
("Ace", Flags, [[0.0, 0.0, 0.0], [0.639, 0.639, 0.639], [1.0, 1.0, 1.0], [0.502, 0.0, 0.502]]),
("Aro", Flags, [[0.239, 0.647, 0.259], [0.655, 0.827, 0.475], [1.0, 1.0, 1.0], [0.663, 0.663, 0.663], [0.0, 0.0, 0.0]]),
("AroAce", Flags, [[0.937, 0.565, 0.027], [0.965, 0.827, 0.09], [1.0, 1.0, 1.0], [0.271, 0.737, 0.933], [0.118, 0.247, 0.329]]),
("Enby", Flags, [[0.988, 0.957, 0.204], [1.0, 1.0, 1.0], [0.612, 0.349, 0.82], [0.173, 0.173, 0.173]]),
("Fluid", Flags, [[1.0, 0.463, 0.643], [1.0, 1.0, 1.0], [0.753, 0.067, 0.843], [0.0, 0.0, 0.0], [0.184, 0.235, 0.745]]),
("Queer", Flags, [[0.71, 0.494, 0.863], [1.0, 1.0, 1.0], [0.29, 0.506, 0.137]]),
("Agender", Flags, [[0.0; 3], [0.74, 0.77, 0.78], [1.0; 3], [0.72, 0.96, 0.52], [1.0; 3], [0.74, 0.77, 0.78], [0.0; 3],]),
("PrideFlags", Flags, {
CONSTANTS
.iter()
.skip_while(|def| def.name != "Rainbow")
.take_while(|def| def.name != "PrideFlags")
.map(|def| match &*def.value {
ConstantValue::Static(val) => val.clone(),
_ => unreachable!()
})
.map(Boxed).collect::<Array<Boxed>>()
}),
("PrideFlagNames", Flags, {
CONSTANTS
.iter()
.skip_while(|def| def.name != "Rainbow")
.take_while(|def| def.name != "PrideFlags")
.map(|def| def.name.to_string())
.collect::<Value>()
}),
);
fn music_constant(backend: &dyn SysBackend) -> Value {
const TEMPO: f64 = 128.0;
const BEAT: f64 = 60.0 / TEMPO;
const C4: f64 = 261.63;
const B3: f64 = 246.94;
const G3: f64 = 196.00;
const E3: f64 = 164.81;
const C2: f64 = 65.41;
const D2: f64 = 73.42;
const E2: f64 = 82.41;
const G2: f64 = 98.00;
#[rustfmt::skip]
let down = [
C4, C4, C4, C4, B3, B3, B3, B3, G3, G3, G3, G3, E3, E3, E3, E3,
C4, C4, B3, B3, G3, G3, E3, E3, C4, C4, B3, B3, G3, G3, E3, E3,
C4, C4, C4, C4, B3, B3, B3, B3, G3, G3, G3, G3, E3, E3, E3, E3,
C4, B3, G3, E3, C4, B3, G3, E3, C4, B3, G3, E3, C4, B3, G3, E3,
];
#[rustfmt::skip]
let up = [
E3, G3, B3, C4, E3, G3, B3, C4, E3, G3, B3, C4, E3, G3, B3, C4,
E3, E3, E3, E3, G3, G3, G3, G3, B3, B3, B3, B3, C4, C4, C4, C4,
E3, E3, G3, G3, B3, B3, C4, C4, E3, E3, G3, G3, B3, B3, C4, C4,
E3, E3, E3, E3, G3, G3, G3, G3, B3, B3, B3, B3, C4, C4, C4, C4,
];
let mut melody = Vec::with_capacity(down.len() * 2 + up.len() * 2);
melody.extend(down);
melody.extend(up);
let harmony = [C2, D2, E2, G2];
let mut hat_mask = Vec::new();
let mut hat_bits: u64 = 0xbeef_babe;
for _ in 0..32 {
hat_mask.push((hat_bits & 1) as f64);
hat_bits >>= 1;
}
let mut rng = SmallRng::seed_from_u64(0);
let sr = backend.audio_sample_rate();
(0..(BEAT * 2.0 * 16.0 * sr as f64) as usize)
.map(|s| {
let secs = s as f64 / sr as f64;
let beat = secs / BEAT;
let m = melody[(4.0 * beat) as usize % melody.len()];
let h = harmony[(beat / 4.0) as usize % harmony.len()];
let m = (1.0 - (m * secs % 1.0) * 2.0) / 3.0; let h = if (h * secs % 1.0) < 0.5 { 1.0 } else { -1.0 } / 3.0; let kick = ((secs % BEAT).powf(0.4) * 40.0 * TAU).sin();
let hat = 0.3
* rng.random_range(-1.0..=1.0)
* hat_mask[(4.0 * beat) as usize % 32]
* (0.0..=0.1).contains(&(secs % (BEAT / 4.0) / (BEAT / 4.0))) as u8 as f64;
let snare = 0.5
* rng.random_range(-1.0..=1.0)
* ((0.5..=0.6).contains(&(secs % (2.0 * BEAT) / (2.0 * BEAT))) as u8 as f64);
0.5 * (m + h + kick + hat + snare)
})
.collect::<EcoVec<_>>()
.into()
}