Start work on Matrix
This commit is contained in:
parent
7ee77f4c4c
commit
3b10aaf06f
75
Cargo.lock
generated
75
Cargo.lock
generated
@ -12,12 +12,6 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
@ -27,12 +21,11 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
[[package]]
|
||||
name = "confy"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2913470204e9e8498a0f31f17f90a0de801ae92c8c5ac18c49af4819e6786697"
|
||||
source = "git+https://github.com/rust-cli/confy/#6ae700bb0e6e2f9f7138d0c1871f604013c8f59f"
|
||||
dependencies = [
|
||||
"directories",
|
||||
"directories-next",
|
||||
"serde",
|
||||
"toml",
|
||||
"serde_yaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -61,33 +54,39 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "2.0.2"
|
||||
name = "directories-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c"
|
||||
checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"dirs-sys",
|
||||
"cfg-if",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.3.6"
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
@ -98,7 +97,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -113,6 +112,12 @@ version = "0.2.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.4"
|
||||
@ -128,7 +133,7 @@ version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -179,7 +184,7 @@ version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
@ -231,6 +236,7 @@ dependencies = [
|
||||
"confy",
|
||||
"crossterm",
|
||||
"serde",
|
||||
"toml",
|
||||
"tui",
|
||||
]
|
||||
|
||||
@ -260,6 +266,18 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.8.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23"
|
||||
dependencies = [
|
||||
"dtoa",
|
||||
"linked-hash-map",
|
||||
"serde",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.1.17"
|
||||
@ -299,9 +317,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.8"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
|
||||
checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@ -364,3 +382,12 @@ name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
10
Cargo.toml
10
Cargo.toml
@ -15,4 +15,12 @@ categories = ["command-line-utilities"]
|
||||
crossterm = "0.18"
|
||||
tui = { version = "0.14", default-features = false, features = ['crossterm'] }
|
||||
serde = {version = "1.0", features = ["derive"]}
|
||||
confy = "0.4.0"
|
||||
# confy = "0.4.0"
|
||||
toml = "0.4.2"
|
||||
|
||||
[dependencies.confy]
|
||||
# TODO: Update this to v0.5.0 when it finally comes out -- for now, use latest git master
|
||||
# version = "0.4.0"
|
||||
git = "https://github.com/rust-cli/confy/"
|
||||
features = ["yaml_conf"]
|
||||
default-features = false
|
||||
|
59
src/calc.rs
59
src/calc.rs
@ -5,7 +5,7 @@ pub mod types;
|
||||
use crate::calc::entries::CalculatorEntry;
|
||||
|
||||
use confy::{load, store};
|
||||
use entries::{Entry, Number, Vector, VectorDirection};
|
||||
use entries::{Entry, Matrix, Number, Vector};
|
||||
use errors::{CalculatorError, CalculatorResult};
|
||||
use operations::{
|
||||
ArithmeticOperation, CalculatorOperation, CalculatorStateChange, MacroState, OpArgs,
|
||||
@ -115,6 +115,13 @@ impl Default for Calculator {
|
||||
value: String::from("RcRbRarbnrb2^4rarc**-v+2ra*/rbnrb2^4rarc**-v-2ra*/"),
|
||||
},
|
||||
),
|
||||
(
|
||||
't',
|
||||
CalculatorMacro {
|
||||
help: String::from("Testing"),
|
||||
value: String::from("1 V1 "),
|
||||
},
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
@ -168,10 +175,10 @@ impl Calculator {
|
||||
}
|
||||
|
||||
pub fn load_config() -> CalculatorResult<Self> {
|
||||
load(APP_NAME).map_err(|e| CalculatorError::LoadError(Some(e)))
|
||||
load(APP_NAME, None).map_err(|e| CalculatorError::LoadError(Some(e)))
|
||||
}
|
||||
pub fn save_config(&self) -> CalculatorResult<()> {
|
||||
store(APP_NAME, self).map_err(|e| CalculatorError::SaveError(Some(e)))
|
||||
store(APP_NAME, None, self).map_err(|e| CalculatorError::SaveError(Some(e)))
|
||||
}
|
||||
|
||||
pub fn take_input(&mut self, c: char) -> CalculatorResult<()> {
|
||||
@ -271,6 +278,7 @@ impl Calculator {
|
||||
)),
|
||||
// Temporary
|
||||
'V' => self.op(CalculatorOperation::BuildVector),
|
||||
'M' => self.op(CalculatorOperation::BuildMatrix),
|
||||
// Special
|
||||
'\\' => self.op(CalculatorOperation::Drop),
|
||||
' ' => self.op(CalculatorOperation::Dup),
|
||||
@ -508,8 +516,9 @@ impl Calculator {
|
||||
pub fn peek_usize(&mut self) -> CalculatorResult<(usize, Entry)> {
|
||||
let entry = self.peek(0)?;
|
||||
let f = match entry {
|
||||
Entry::Number(Number { value }) => value,
|
||||
Entry::Matrix(_) => return Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(_) => return Err(CalculatorError::TypeMismatch),
|
||||
Entry::Number(Number { value }) => value,
|
||||
};
|
||||
// Ensure this can be cast to a usize
|
||||
if !f.is_finite() || f.is_sign_negative() {
|
||||
@ -537,27 +546,30 @@ impl Calculator {
|
||||
|
||||
pub fn build_vector(&mut self) -> CalculatorResult<CalculatorStateChange> {
|
||||
let (count, count_entry) = self.peek_usize()?;
|
||||
let (mut entries, values): (VecDeque<Entry>, Vec<Number>) = (1..=count)
|
||||
.map(|i| {
|
||||
let e = self.peek(i)?;
|
||||
match e {
|
||||
// TODO: For now, vectors only contain numbers
|
||||
Entry::Number(number) => Ok((e, number)),
|
||||
Entry::Vector(_vector) => Err(CalculatorError::TypeMismatch),
|
||||
}
|
||||
})
|
||||
.collect::<CalculatorResult<Vec<(Entry, Number)>>>()?
|
||||
// TODO: Do I have to iter again?
|
||||
.into_iter()
|
||||
.unzip();
|
||||
let entry = Entry::Vector(Vector {
|
||||
values,
|
||||
direction: VectorDirection::Row,
|
||||
});
|
||||
entries.push_front(count_entry);
|
||||
let mut entries: Vec<Entry> = (1..=count)
|
||||
.map(|i| self.peek(i))
|
||||
.collect::<CalculatorResult<Vec<Entry>>>()?;
|
||||
|
||||
let new_entry = Vector::from(&entries)?;
|
||||
|
||||
entries.splice(1..1, vec![count_entry]);
|
||||
Ok(CalculatorStateChange {
|
||||
pop: OpArgs::Variable(Vec::from(entries)),
|
||||
push: OpArgs::Unary(entry),
|
||||
push: OpArgs::Unary(new_entry),
|
||||
})
|
||||
}
|
||||
pub fn build_matrix(&mut self) -> CalculatorResult<CalculatorStateChange> {
|
||||
let (count, count_entry) = self.peek_usize()?;
|
||||
let mut entries: Vec<Entry> = (1..=count)
|
||||
.map(|i| self.peek(i))
|
||||
.collect::<CalculatorResult<Vec<Entry>>>()?;
|
||||
|
||||
let new_entry = Matrix::from(&entries)?;
|
||||
|
||||
entries.splice(1..1, vec![count_entry]);
|
||||
Ok(CalculatorStateChange {
|
||||
pop: OpArgs::Variable(Vec::from(entries)),
|
||||
push: OpArgs::Unary(new_entry),
|
||||
})
|
||||
}
|
||||
|
||||
@ -634,6 +646,7 @@ impl Calculator {
|
||||
self.binary_op(|[a, b]| Ok(OpArgs::Unary(b.pow(&a)?)))
|
||||
}
|
||||
CalculatorOperation::BuildVector => self.build_vector(),
|
||||
CalculatorOperation::BuildMatrix => self.build_matrix(),
|
||||
CalculatorOperation::Dup => self.unary_op(|a| Ok(OpArgs::Binary([a.clone(), a]))),
|
||||
CalculatorOperation::Drop => self.unary_op(|_| Ok(OpArgs::None)),
|
||||
CalculatorOperation::Swap => self.binary_op(|[a, b]| Ok(OpArgs::Binary([b, a]))),
|
||||
|
@ -58,18 +58,19 @@ pub struct Vector {
|
||||
pub direction: VectorDirection,
|
||||
}
|
||||
|
||||
// #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
// pub struct Matrix {
|
||||
// pub values: Vec<Vector>,
|
||||
// pub direction: VectorDirection,
|
||||
// }
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct Matrix {
|
||||
pub vectors: Vec<Vector>,
|
||||
pub dimensions: (usize, usize),
|
||||
pub direction: VectorDirection,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Entry {
|
||||
Number(Number),
|
||||
Vector(Vector),
|
||||
// Matrix(Matrix),
|
||||
Matrix(Matrix),
|
||||
}
|
||||
|
||||
// macro_rules! op_child_call {
|
||||
@ -92,90 +93,105 @@ impl CalculatorEntry for Entry {
|
||||
match self {
|
||||
Self::Number(number) => number.to_editable_string(),
|
||||
Self::Vector(vector) => vector.to_editable_string(),
|
||||
Self::Matrix(matrix) => matrix.to_editable_string(),
|
||||
}
|
||||
}
|
||||
fn format_entry(&self, display_mode: &CalculatorDisplayMode) -> String {
|
||||
match self {
|
||||
Self::Number(number) => number.format_entry(display_mode),
|
||||
Self::Vector(vector) => vector.format_entry(display_mode),
|
||||
Self::Matrix(matrix) => matrix.format_entry(display_mode),
|
||||
}
|
||||
}
|
||||
fn is_valid(&self) -> bool {
|
||||
match self {
|
||||
Self::Number(number) => number.is_valid(),
|
||||
Self::Vector(vector) => vector.is_valid(),
|
||||
Self::Matrix(matrix) => matrix.is_valid(),
|
||||
}
|
||||
}
|
||||
fn negate(&self) -> CalculatorResult<Self> {
|
||||
match self {
|
||||
Self::Number(number) => number.negate(),
|
||||
Self::Vector(vector) => vector.negate(),
|
||||
Self::Matrix(matrix) => matrix.negate(),
|
||||
}
|
||||
}
|
||||
fn abs(&self) -> CalculatorResult<Self> {
|
||||
match self {
|
||||
Self::Number(number) => number.abs(),
|
||||
Self::Vector(vector) => vector.abs(),
|
||||
Self::Matrix(matrix) => matrix.abs(),
|
||||
}
|
||||
}
|
||||
fn inverse(&self) -> CalculatorResult<Self> {
|
||||
match self {
|
||||
Self::Number(number) => number.inverse(),
|
||||
Self::Vector(vector) => vector.inverse(),
|
||||
Self::Matrix(matrix) => matrix.inverse(),
|
||||
}
|
||||
}
|
||||
fn sin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Self> {
|
||||
match self {
|
||||
Self::Number(number) => number.sin(angle_mode),
|
||||
Self::Vector(vector) => vector.sin(angle_mode),
|
||||
Self::Matrix(matrix) => matrix.sin(angle_mode),
|
||||
}
|
||||
}
|
||||
fn cos(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Self> {
|
||||
match self {
|
||||
Self::Number(number) => number.cos(angle_mode),
|
||||
Self::Vector(vector) => vector.cos(angle_mode),
|
||||
Self::Matrix(matrix) => matrix.cos(angle_mode),
|
||||
}
|
||||
}
|
||||
fn tan(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Self> {
|
||||
match self {
|
||||
Self::Number(number) => number.tan(angle_mode),
|
||||
Self::Vector(vector) => vector.tan(angle_mode),
|
||||
Self::Matrix(matrix) => matrix.tan(angle_mode),
|
||||
}
|
||||
}
|
||||
fn asin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Self> {
|
||||
match self {
|
||||
Self::Number(number) => number.asin(angle_mode),
|
||||
Self::Vector(vector) => vector.asin(angle_mode),
|
||||
Self::Matrix(matrix) => matrix.asin(angle_mode),
|
||||
}
|
||||
}
|
||||
fn acos(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Self> {
|
||||
match self {
|
||||
Self::Number(number) => number.acos(angle_mode),
|
||||
Self::Vector(vector) => vector.acos(angle_mode),
|
||||
Self::Matrix(matrix) => matrix.acos(angle_mode),
|
||||
}
|
||||
}
|
||||
fn atan(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Self> {
|
||||
match self {
|
||||
Self::Number(number) => number.atan(angle_mode),
|
||||
Self::Vector(vector) => vector.atan(angle_mode),
|
||||
Self::Matrix(matrix) => matrix.atan(angle_mode),
|
||||
}
|
||||
}
|
||||
fn sqrt(&self) -> CalculatorResult<Self> {
|
||||
match self {
|
||||
Self::Number(number) => number.sqrt(),
|
||||
Self::Vector(vector) => vector.sqrt(),
|
||||
Self::Matrix(matrix) => matrix.sqrt(),
|
||||
}
|
||||
}
|
||||
fn log(&self) -> CalculatorResult<Self> {
|
||||
match self {
|
||||
Self::Number(number) => number.log(),
|
||||
Self::Vector(vector) => vector.log(),
|
||||
Self::Matrix(matrix) => matrix.log(),
|
||||
}
|
||||
}
|
||||
fn ln(&self) -> CalculatorResult<Self> {
|
||||
match self {
|
||||
Self::Number(number) => number.ln(),
|
||||
Self::Vector(vector) => vector.ln(),
|
||||
Self::Matrix(matrix) => matrix.ln(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,46 +199,291 @@ impl CalculatorEntry for Entry {
|
||||
match self {
|
||||
Self::Number(number) => number.add(arg),
|
||||
Self::Vector(vector) => vector.add(arg),
|
||||
Self::Matrix(matrix) => matrix.add(arg),
|
||||
}
|
||||
}
|
||||
fn sub(&self, arg: &Self) -> CalculatorResult<Self> {
|
||||
match self {
|
||||
Self::Number(number) => number.sub(arg),
|
||||
Self::Vector(vector) => vector.sub(arg),
|
||||
Self::Matrix(matrix) => matrix.sub(arg),
|
||||
}
|
||||
}
|
||||
fn mul(&self, arg: &Self) -> CalculatorResult<Self> {
|
||||
match self {
|
||||
Self::Number(number) => number.mul(arg),
|
||||
Self::Vector(vector) => vector.mul(arg),
|
||||
Self::Matrix(matrix) => matrix.mul(arg),
|
||||
}
|
||||
}
|
||||
fn div(&self, arg: &Self) -> CalculatorResult<Self> {
|
||||
match self {
|
||||
Self::Number(number) => number.div(arg),
|
||||
Self::Vector(vector) => vector.div(arg),
|
||||
Self::Matrix(matrix) => matrix.div(arg),
|
||||
}
|
||||
}
|
||||
fn int_divide(&self, arg: &Self) -> CalculatorResult<Self> {
|
||||
match self {
|
||||
Self::Number(number) => number.int_divide(arg),
|
||||
Self::Vector(vector) => vector.int_divide(arg),
|
||||
Self::Matrix(matrix) => matrix.int_divide(arg),
|
||||
}
|
||||
}
|
||||
fn modulo(&self, arg: &Self) -> CalculatorResult<Self> {
|
||||
match self {
|
||||
Self::Number(number) => number.modulo(arg),
|
||||
Self::Vector(vector) => vector.modulo(arg),
|
||||
Self::Matrix(matrix) => matrix.modulo(arg),
|
||||
}
|
||||
}
|
||||
fn pow(&self, arg: &Self) -> CalculatorResult<Self> {
|
||||
match self {
|
||||
Self::Number(number) => number.pow(arg),
|
||||
Self::Vector(vector) => vector.pow(arg),
|
||||
Self::Matrix(matrix) => matrix.pow(arg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CalculatorEntry for Matrix {
|
||||
fn to_editable_string(&self) -> CalculatorResult<String> {
|
||||
// TODO: Eventualy we can parse and edit a matrix as a string
|
||||
Err(CalculatorError::TypeMismatch)
|
||||
}
|
||||
fn is_valid(&self) -> bool {
|
||||
self.vectors.len() == self.dimensions.0
|
||||
&& self
|
||||
.vectors
|
||||
.iter()
|
||||
.all(|v| v.values.len() == self.dimensions.1 && v.is_valid())
|
||||
}
|
||||
fn format_entry(&self, display_mode: &CalculatorDisplayMode) -> String {
|
||||
format!(
|
||||
"[ {} ]",
|
||||
self.vectors
|
||||
.iter()
|
||||
.map(|vector| vector.format_entry(display_mode))
|
||||
.collect::<Vec<String>>()
|
||||
.join(self.direction.get_separator())
|
||||
)
|
||||
}
|
||||
|
||||
// Mathematical operations
|
||||
fn negate(&self) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(Vector::negate)
|
||||
}
|
||||
fn abs(&self) -> CalculatorResult<Entry> {
|
||||
// TODO: Compute determinant
|
||||
// let value: Entry = self
|
||||
// .vectors
|
||||
// .iter()
|
||||
// .try_fold(Entry::Number(Vector::ZERO), |acc, vector| {
|
||||
// acc.add(&vector.abs()?)
|
||||
// })?;
|
||||
// value.sqrt()
|
||||
Err(CalculatorError::NotYetImplemented)
|
||||
}
|
||||
fn inverse(&self) -> CalculatorResult<Entry> {
|
||||
// TODO: Matrix inverse
|
||||
// Ok(Entry::Vector(Self {
|
||||
// vectors: self.vectors.clone(),
|
||||
// direction: self.direction.swap(),
|
||||
// }))
|
||||
self.iterated_unary(Vector::inverse)
|
||||
}
|
||||
fn sin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(|v| v.sin(angle_mode))
|
||||
}
|
||||
fn cos(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(|v| v.cos(angle_mode))
|
||||
}
|
||||
fn tan(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(|v| v.tan(angle_mode))
|
||||
}
|
||||
fn asin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(|v| v.asin(angle_mode))
|
||||
}
|
||||
fn acos(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(|v| v.acos(angle_mode))
|
||||
}
|
||||
fn atan(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(|v| v.atan(angle_mode))
|
||||
}
|
||||
fn sqrt(&self) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(Vector::sqrt)
|
||||
}
|
||||
fn log(&self) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(Vector::log)
|
||||
}
|
||||
fn ln(&self) -> CalculatorResult<Entry> {
|
||||
self.iterated_unary(Vector::ln)
|
||||
}
|
||||
|
||||
// Binary
|
||||
fn add(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(m2) => self.iterated_binary_mat(m2, Vector::add),
|
||||
Entry::Vector(vector) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Vector::add),
|
||||
}
|
||||
}
|
||||
fn sub(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(m2) => self.iterated_binary_mat(m2, Vector::sub),
|
||||
Entry::Vector(vector) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Vector::sub),
|
||||
}
|
||||
}
|
||||
fn mul(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
// TODO: Correct implementation
|
||||
match arg {
|
||||
Entry::Matrix(m2) => self.iterated_binary_mat(m2, Vector::mul),
|
||||
Entry::Vector(vector) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Vector::mul),
|
||||
}
|
||||
}
|
||||
fn div(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(m2) => self.iterated_binary_mat(m2, Vector::div),
|
||||
Entry::Vector(vector) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Vector::div),
|
||||
}
|
||||
}
|
||||
fn int_divide(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(m2) => self.iterated_binary_mat(m2, Vector::int_divide),
|
||||
Entry::Vector(vector) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Vector::int_divide),
|
||||
}
|
||||
}
|
||||
fn modulo(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(m2) => self.iterated_binary_mat(m2, Vector::modulo),
|
||||
Entry::Vector(vector) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Vector::modulo),
|
||||
}
|
||||
}
|
||||
fn pow(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(m2) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(vector) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Vector::pow),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Matrix {
|
||||
pub fn from(entries: &Vec<Entry>) -> CalculatorResult<Entry> {
|
||||
if entries.len() == 0 {
|
||||
return Err(CalculatorError::NotEnoughStackEntries);
|
||||
}
|
||||
|
||||
let vectors = entries
|
||||
.iter()
|
||||
.map(|e| match e {
|
||||
Entry::Matrix(_) | Entry::Number(_) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(vector) => Ok(vector.clone()),
|
||||
})
|
||||
.collect::<CalculatorResult<Vec<Vector>>>()?;
|
||||
|
||||
// Get the depth of the matrix
|
||||
let depth = vectors
|
||||
.get(0)
|
||||
.ok_or(CalculatorError::NotEnoughStackEntries)?
|
||||
.values
|
||||
.len();
|
||||
|
||||
if vectors.iter().any(|v| v.values.len() != depth) {
|
||||
// One vector has a different length than another
|
||||
return Err(CalculatorError::DimensionMismatch);
|
||||
}
|
||||
|
||||
let dimensions: (usize, usize) = (vectors.len(), depth);
|
||||
Self {
|
||||
vectors,
|
||||
dimensions,
|
||||
direction: VectorDirection::Row,
|
||||
}
|
||||
.validate()
|
||||
}
|
||||
|
||||
// TODO: Should validate be put in the trait?
|
||||
pub fn validate(self) -> CalculatorResult<Entry> {
|
||||
if self.is_valid() {
|
||||
Ok(Entry::Matrix(self))
|
||||
} else {
|
||||
Err(CalculatorError::ArithmeticError)
|
||||
}
|
||||
}
|
||||
|
||||
fn iterated_unary(
|
||||
&self,
|
||||
op: impl Fn(&Vector) -> CalculatorResult<Entry>,
|
||||
) -> CalculatorResult<Entry> {
|
||||
Self {
|
||||
vectors: self
|
||||
.vectors
|
||||
.iter()
|
||||
.map(|v| op(v))
|
||||
.map(|e| match e {
|
||||
Ok(Entry::Vector(vector)) => Ok(vector),
|
||||
_ => Err(CalculatorError::ArithmeticError),
|
||||
})
|
||||
.collect::<CalculatorResult<Vec<Vector>>>()?,
|
||||
direction: self.direction,
|
||||
dimensions: self.dimensions,
|
||||
}
|
||||
.validate()
|
||||
}
|
||||
|
||||
fn iterated_binary_num(
|
||||
&self,
|
||||
number: &Number,
|
||||
op: impl Fn(&Vector, &Entry) -> CalculatorResult<Entry>,
|
||||
) -> CalculatorResult<Entry> {
|
||||
Self {
|
||||
vectors: self
|
||||
.vectors
|
||||
.iter()
|
||||
.map(|v| op(v, &Entry::Number(*number)))
|
||||
.map(|e| match e {
|
||||
Ok(Entry::Vector(vector)) => Ok(vector),
|
||||
_ => Err(CalculatorError::ArithmeticError),
|
||||
})
|
||||
.collect::<CalculatorResult<Vec<Vector>>>()?,
|
||||
direction: self.direction,
|
||||
dimensions: self.dimensions,
|
||||
}
|
||||
.validate()
|
||||
}
|
||||
|
||||
fn iterated_binary_mat(
|
||||
&self,
|
||||
m2: &Self,
|
||||
op: impl Fn(&Vector, &Entry) -> CalculatorResult<Entry>,
|
||||
) -> CalculatorResult<Entry> {
|
||||
if self.dimensions != m2.dimensions && self.direction != m2.direction {
|
||||
return Err(CalculatorError::DimensionMismatch);
|
||||
}
|
||||
Self {
|
||||
vectors: self
|
||||
.vectors
|
||||
.iter()
|
||||
.zip(m2.vectors.iter())
|
||||
.map(|(v1, v2)| op(v1, &Entry::Vector(v2.clone())))
|
||||
.map(|e| match e {
|
||||
Ok(Entry::Vector(vector)) => Ok(vector),
|
||||
_ => Err(CalculatorError::ArithmeticError),
|
||||
})
|
||||
.collect::<CalculatorResult<Vec<Vector>>>()?,
|
||||
direction: self.direction,
|
||||
dimensions: self.dimensions,
|
||||
}
|
||||
.validate()
|
||||
}
|
||||
}
|
||||
|
||||
impl CalculatorEntry for Vector {
|
||||
// Misc
|
||||
fn to_editable_string(&self) -> CalculatorResult<String> {
|
||||
@ -291,18 +552,22 @@ impl CalculatorEntry for Vector {
|
||||
|
||||
fn add(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(_) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(v2) => self.iterated_binary_vec(v2, Number::add),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Number::add),
|
||||
}
|
||||
}
|
||||
fn sub(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(_) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(v2) => self.iterated_binary_vec(v2, Number::sub),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Number::sub),
|
||||
}
|
||||
}
|
||||
fn mul(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
// TODO: Matrix multiplication should be allowed
|
||||
Entry::Matrix(_) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(v2) => {
|
||||
if self.values.len() != v2.values.len() {
|
||||
return Err(CalculatorError::DimensionMismatch);
|
||||
@ -331,24 +596,28 @@ impl CalculatorEntry for Vector {
|
||||
}
|
||||
fn div(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(_) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(v2) => self.iterated_binary_vec(v2, Number::div),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Number::div),
|
||||
}
|
||||
}
|
||||
fn int_divide(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(_) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(v2) => self.iterated_binary_vec(v2, Number::int_divide),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Number::int_divide),
|
||||
}
|
||||
}
|
||||
fn modulo(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(_) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(v2) => self.iterated_binary_vec(v2, Number::modulo),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Number::modulo),
|
||||
}
|
||||
}
|
||||
fn pow(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Matrix(_) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(v2) => self.iterated_binary_vec(v2, Number::pow),
|
||||
Entry::Number(number) => self.iterated_binary_num(number, Number::pow),
|
||||
}
|
||||
@ -356,6 +625,23 @@ impl CalculatorEntry for Vector {
|
||||
}
|
||||
|
||||
impl Vector {
|
||||
pub fn from(entries: &Vec<Entry>) -> CalculatorResult<Entry> {
|
||||
if entries.len() == 0 {
|
||||
return Err(CalculatorError::NotEnoughStackEntries);
|
||||
}
|
||||
Ok(Entry::Vector(Self {
|
||||
values: entries
|
||||
.iter()
|
||||
.map(|e| match e {
|
||||
Entry::Matrix(_) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Vector(_) => Err(CalculatorError::TypeMismatch),
|
||||
Entry::Number(number) => Ok(number.clone()),
|
||||
})
|
||||
.collect::<CalculatorResult<Vec<Number>>>()?,
|
||||
direction: VectorDirection::Row,
|
||||
})) //.validate() <- TODO
|
||||
}
|
||||
|
||||
fn iterated_unary(
|
||||
&self,
|
||||
op: impl Fn(&Number) -> CalculatorResult<Entry>,
|
||||
@ -525,7 +811,8 @@ impl CalculatorEntry for Number {
|
||||
|
||||
fn add(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Vector(vector) => self.iterated_binary(vector, Self::add),
|
||||
Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::add),
|
||||
Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::add),
|
||||
Entry::Number(number) => Self {
|
||||
value: self.value + number.value,
|
||||
}
|
||||
@ -534,7 +821,8 @@ impl CalculatorEntry for Number {
|
||||
}
|
||||
fn sub(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Vector(vector) => self.iterated_binary(vector, Self::sub),
|
||||
Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::sub),
|
||||
Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::sub),
|
||||
Entry::Number(number) => Self {
|
||||
value: self.value - number.value,
|
||||
}
|
||||
@ -543,7 +831,8 @@ impl CalculatorEntry for Number {
|
||||
}
|
||||
fn mul(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Vector(vector) => self.iterated_binary(vector, Self::mul),
|
||||
Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::mul),
|
||||
Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::mul),
|
||||
Entry::Number(number) => Self {
|
||||
value: self.value * number.value,
|
||||
}
|
||||
@ -552,7 +841,8 @@ impl CalculatorEntry for Number {
|
||||
}
|
||||
fn div(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Vector(vector) => self.iterated_binary(vector, Self::div),
|
||||
Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::div),
|
||||
Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::div),
|
||||
Entry::Number(number) => Self {
|
||||
value: self.value / number.value,
|
||||
}
|
||||
@ -561,7 +851,8 @@ impl CalculatorEntry for Number {
|
||||
}
|
||||
fn int_divide(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Vector(vector) => self.iterated_binary(vector, Self::int_divide),
|
||||
Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::int_divide),
|
||||
Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::int_divide),
|
||||
Entry::Number(number) => Self {
|
||||
value: self.value.div_euclid(number.value),
|
||||
}
|
||||
@ -570,7 +861,8 @@ impl CalculatorEntry for Number {
|
||||
}
|
||||
fn modulo(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Vector(vector) => self.iterated_binary(vector, Self::modulo),
|
||||
Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::modulo),
|
||||
Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::modulo),
|
||||
Entry::Number(number) => Self {
|
||||
value: self.value % number.value,
|
||||
}
|
||||
@ -579,7 +871,8 @@ impl CalculatorEntry for Number {
|
||||
}
|
||||
fn pow(&self, arg: &Entry) -> CalculatorResult<Entry> {
|
||||
match arg {
|
||||
Entry::Vector(vector) => self.iterated_binary(vector, Self::pow),
|
||||
Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::pow),
|
||||
Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::pow),
|
||||
Entry::Number(number) => Self {
|
||||
value: self.value.powf(number.value),
|
||||
}
|
||||
@ -599,7 +892,7 @@ impl Number {
|
||||
}
|
||||
}
|
||||
|
||||
fn iterated_binary(
|
||||
fn iterated_binary_vec(
|
||||
self,
|
||||
vector: &Vector,
|
||||
op: impl Fn(&Self, &Entry) -> CalculatorResult<Entry>,
|
||||
@ -618,6 +911,28 @@ impl Number {
|
||||
direction: vector.direction,
|
||||
}))
|
||||
}
|
||||
|
||||
fn iterated_binary_mat(
|
||||
self,
|
||||
matrix: &Matrix,
|
||||
op: impl Fn(&Self, &Entry) -> CalculatorResult<Entry>,
|
||||
) -> CalculatorResult<Entry> {
|
||||
Matrix {
|
||||
vectors: matrix
|
||||
.vectors
|
||||
.iter()
|
||||
.map(|v| op(&self, &Entry::Vector(v.clone()))) // TODO: Do I need to clone?
|
||||
.map(|e| match e {
|
||||
// Only numbers are valid in a vector
|
||||
Ok(Entry::Vector(vector)) => Ok(vector),
|
||||
_ => Err(CalculatorError::ArithmeticError),
|
||||
})
|
||||
.collect::<CalculatorResult<Vec<Vector>>>()?,
|
||||
direction: matrix.direction,
|
||||
dimensions: matrix.dimensions,
|
||||
}
|
||||
.validate()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CalculatorEntry
|
||||
@ -629,15 +944,6 @@ where
|
||||
fn format_entry(&self, display_mode: &CalculatorDisplayMode) -> String;
|
||||
fn to_editable_string(&self) -> CalculatorResult<String>;
|
||||
// Mathematical operations
|
||||
// Binary
|
||||
fn add(&self, arg: &Entry) -> CalculatorResult<Entry>;
|
||||
fn sub(&self, arg: &Entry) -> CalculatorResult<Entry>;
|
||||
fn mul(&self, arg: &Entry) -> CalculatorResult<Entry>;
|
||||
fn div(&self, arg: &Entry) -> CalculatorResult<Entry>;
|
||||
fn int_divide(&self, arg: &Entry) -> CalculatorResult<Entry>;
|
||||
fn modulo(&self, arg: &Entry) -> CalculatorResult<Entry>;
|
||||
fn pow(&self, arg: &Entry) -> CalculatorResult<Entry>;
|
||||
|
||||
// Unary
|
||||
fn negate(&self) -> CalculatorResult<Entry>;
|
||||
fn abs(&self) -> CalculatorResult<Entry>;
|
||||
@ -651,20 +957,38 @@ where
|
||||
fn sqrt(&self) -> CalculatorResult<Entry>;
|
||||
fn log(&self) -> CalculatorResult<Entry>;
|
||||
fn ln(&self) -> CalculatorResult<Entry>;
|
||||
|
||||
// Binary
|
||||
fn add(&self, arg: &Entry) -> CalculatorResult<Entry>;
|
||||
fn sub(&self, arg: &Entry) -> CalculatorResult<Entry>;
|
||||
fn mul(&self, arg: &Entry) -> CalculatorResult<Entry>;
|
||||
fn div(&self, arg: &Entry) -> CalculatorResult<Entry>;
|
||||
fn int_divide(&self, arg: &Entry) -> CalculatorResult<Entry>;
|
||||
fn modulo(&self, arg: &Entry) -> CalculatorResult<Entry>;
|
||||
fn pow(&self, arg: &Entry) -> CalculatorResult<Entry>;
|
||||
}
|
||||
|
||||
impl fmt::Display for Entry {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Number(number) => write!(f, "{}", number),
|
||||
Self::Matrix(matrix) => write!(f, "{}", matrix),
|
||||
Self::Vector(vector) => write!(f, "{}", vector),
|
||||
Self::Number(number) => write!(f, "{}", number),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Number {
|
||||
impl fmt::Display for Matrix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
write!(
|
||||
f,
|
||||
"[ {} ]",
|
||||
self.vectors
|
||||
.iter()
|
||||
.map(|vector| format!("{}", vector))
|
||||
.collect::<Vec<String>>()
|
||||
.join("; ")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -682,6 +1006,12 @@ impl fmt::Display for Vector {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Number {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
// Based on https://stackoverflow.com/a/65266882
|
||||
fn scientific(f: f64, precision: usize) -> String {
|
||||
let mut ret = format!("{:.precision$E}", f, precision = precision);
|
||||
|
@ -66,12 +66,12 @@ impl fmt::Display for CalculatorError {
|
||||
Self::ParseError => write!(f, "Parse error"),
|
||||
Self::PrecisionTooHigh => write!(f, "Precision too high"),
|
||||
Self::SaveError(None) => write!(f, "Could not save"),
|
||||
Self::SaveError(Some(ConfyError::SerializeTomlError(e))) => {
|
||||
Self::SaveError(Some(ConfyError::SerializeYamlError(e))) => {
|
||||
write!(f, "Save serialization error: {}", e)
|
||||
}
|
||||
Self::SaveError(Some(e)) => write!(f, "Could not save: {}", e),
|
||||
Self::LoadError(None) => write!(f, "Could not load"),
|
||||
Self::LoadError(Some(ConfyError::SerializeTomlError(e))) => {
|
||||
Self::LoadError(Some(ConfyError::SerializeYamlError(e))) => {
|
||||
write!(f, "Load serialization error: {}", e)
|
||||
}
|
||||
Self::LoadError(Some(e)) => write!(f, "Could not load: {}", e),
|
||||
|
@ -28,6 +28,7 @@ pub enum ArithmeticOperation {
|
||||
pub enum CalculatorOperation {
|
||||
ArithmeticOperation(ArithmeticOperation),
|
||||
BuildVector,
|
||||
BuildMatrix,
|
||||
Undo,
|
||||
Redo,
|
||||
Drop,
|
||||
|
Loading…
Reference in New Issue
Block a user