Spanned object pairs and refactoring 🧵
This commit is contained in:
parent
bb1350cff5
commit
e9a9581252
@ -364,7 +364,7 @@ impl Deref for NamedTuple {
|
|||||||
#[derive(Default, Clone, PartialEq)]
|
#[derive(Default, Clone, PartialEq)]
|
||||||
pub struct Object {
|
pub struct Object {
|
||||||
/// The key-value pairs of the object.
|
/// The key-value pairs of the object.
|
||||||
pub pairs: Vec<Pair>,
|
pub pairs: Vec<Spanned<Pair>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A key-value pair in an object.
|
/// A key-value pair in an object.
|
||||||
@ -391,7 +391,7 @@ impl Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add a pair to object.
|
/// Add a pair to object.
|
||||||
pub fn add(&mut self, pair: Pair) {
|
pub fn add(&mut self, pair: Spanned<Pair>) {
|
||||||
self.pairs.push(pair);
|
self.pairs.push(pair);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,7 +401,7 @@ impl Object {
|
|||||||
/// Inserts an error if the value does not match. If the key is not
|
/// Inserts an error if the value does not match. If the key is not
|
||||||
/// contained, no error is inserted.
|
/// contained, no error is inserted.
|
||||||
pub fn get<V: Value>(&mut self, errors: &mut Errors, key: &str) -> Option<V> {
|
pub fn get<V: Value>(&mut self, errors: &mut Errors, key: &str) -> Option<V> {
|
||||||
let index = self.pairs.iter().position(|pair| pair.key.v.as_str() == key)?;
|
let index = self.pairs.iter().position(|pair| pair.v.key.v.as_str() == key)?;
|
||||||
self.get_index::<V>(errors, index)
|
self.get_index::<V>(errors, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,7 +414,7 @@ impl Object {
|
|||||||
errors: &mut Errors,
|
errors: &mut Errors,
|
||||||
) -> Option<(K, V)> {
|
) -> Option<(K, V)> {
|
||||||
for (index, pair) in self.pairs.iter().enumerate() {
|
for (index, pair) in self.pairs.iter().enumerate() {
|
||||||
let key = Spanned { v: pair.key.v.as_str(), span: pair.key.span };
|
let key = Spanned { v: pair.v.key.v.as_str(), span: pair.v.key.span };
|
||||||
if let Some(key) = K::parse(key) {
|
if let Some(key) = K::parse(key) {
|
||||||
return self.get_index::<V>(errors, index).map(|value| (key, value));
|
return self.get_index::<V>(errors, index).map(|value| (key, value));
|
||||||
}
|
}
|
||||||
@ -432,7 +432,7 @@ impl Object {
|
|||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
std::iter::from_fn(move || {
|
std::iter::from_fn(move || {
|
||||||
if index < self.pairs.len() {
|
if index < self.pairs.len() {
|
||||||
let key = &self.pairs[index].key;
|
let key = &self.pairs[index].v.key;
|
||||||
let key = Spanned { v: key.v.as_str(), span: key.span };
|
let key = Spanned { v: key.v.as_str(), span: key.span };
|
||||||
|
|
||||||
Some(if let Some(key) = K::parse(key) {
|
Some(if let Some(key) = K::parse(key) {
|
||||||
@ -465,7 +465,7 @@ impl Object {
|
|||||||
/// Extract the argument at the given index and insert an error if the value
|
/// Extract the argument at the given index and insert an error if the value
|
||||||
/// does not match.
|
/// does not match.
|
||||||
fn get_index<V: Value>(&mut self, errors: &mut Errors, index: usize) -> Option<V> {
|
fn get_index<V: Value>(&mut self, errors: &mut Errors, index: usize) -> Option<V> {
|
||||||
let expr = self.pairs.remove(index).value;
|
let expr = self.pairs.remove(index).v.value;
|
||||||
let span = expr.span;
|
let span = expr.span;
|
||||||
match V::parse(expr) {
|
match V::parse(expr) {
|
||||||
Ok(output) => Some(output),
|
Ok(output) => Some(output),
|
||||||
@ -474,14 +474,14 @@ impl Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over the pairs of this object.
|
/// Iterate over the pairs of this object.
|
||||||
pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, Pair> {
|
pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, Spanned<Pair>> {
|
||||||
self.pairs.iter()
|
self.pairs.iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoIterator for Object {
|
impl IntoIterator for Object {
|
||||||
type Item = Pair;
|
type Item = Spanned<Pair>;
|
||||||
type IntoIter = std::vec::IntoIter<Pair>;
|
type IntoIter = std::vec::IntoIter<Spanned<Pair>>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.pairs.into_iter()
|
self.pairs.into_iter()
|
||||||
@ -489,16 +489,16 @@ impl IntoIterator for Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IntoIterator for &'a Object {
|
impl<'a> IntoIterator for &'a Object {
|
||||||
type Item = &'a Pair;
|
type Item = &'a Spanned<Pair>;
|
||||||
type IntoIter = std::slice::Iter<'a, Pair>;
|
type IntoIter = std::slice::Iter<'a, Spanned<Pair>>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.iter()
|
self.iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromIterator<Pair> for Object {
|
impl FromIterator<Spanned<Pair>> for Object {
|
||||||
fn from_iter<I: IntoIterator<Item=Pair>>(iter: I) -> Self {
|
fn from_iter<I: IntoIterator<Item=Spanned<Pair>>>(iter: I) -> Self {
|
||||||
Object { pairs: iter.into_iter().collect() }
|
Object { pairs: iter.into_iter().collect() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -506,7 +506,7 @@ impl FromIterator<Pair> for Object {
|
|||||||
impl Debug for Object {
|
impl Debug for Object {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.debug_map()
|
f.debug_map()
|
||||||
.entries(self.pairs.iter().map(|p| (&p.key.v, &p.value.v)))
|
.entries(self.pairs.iter().map(|p| (&p.v.key.v, &p.v.value.v)))
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,11 +56,11 @@ impl FuncArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromIterator<FuncArg> for FuncArgs {
|
impl FromIterator<Spanned<FuncArg>> for FuncArgs {
|
||||||
fn from_iter<I: IntoIterator<Item=FuncArg>>(iter: I) -> Self {
|
fn from_iter<I: IntoIterator<Item=Spanned<FuncArg>>>(iter: I) -> Self {
|
||||||
let mut args = FuncArgs::new();
|
let mut args = FuncArgs::new();
|
||||||
for item in iter.into_iter() {
|
for item in iter.into_iter() {
|
||||||
args.add(item);
|
args.add(item.v);
|
||||||
}
|
}
|
||||||
args
|
args
|
||||||
}
|
}
|
||||||
@ -72,7 +72,7 @@ pub enum FuncArg {
|
|||||||
/// A positional argument.
|
/// A positional argument.
|
||||||
Pos(Spanned<Expr>),
|
Pos(Spanned<Expr>),
|
||||||
/// A keyword argument.
|
/// A keyword argument.
|
||||||
Key(Pair),
|
Key(Spanned<Pair>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FuncArg {
|
impl FuncArg {
|
||||||
@ -83,7 +83,7 @@ impl FuncArg {
|
|||||||
pub fn span(&self) -> Span {
|
pub fn span(&self) -> Span {
|
||||||
match self {
|
match self {
|
||||||
FuncArg::Pos(item) => item.span,
|
FuncArg::Pos(item) => item.span,
|
||||||
FuncArg::Key(Pair { key, value }) => Span::merge(key.span, value.span),
|
FuncArg::Key(item) => item.span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,10 +190,10 @@ impl<'s> FuncParser<'s> {
|
|||||||
if let Some(ident) = p.parse_ident() {
|
if let Some(ident) = p.parse_ident() {
|
||||||
// This could still be a named tuple
|
// This could still be a named tuple
|
||||||
if let Some(Token::LeftParen) = p.peekv() {
|
if let Some(Token::LeftParen) = p.peekv() {
|
||||||
return Ok(FuncArg::Pos(
|
let n_tuple = p.parse_named_tuple(ident)
|
||||||
p.parse_named_tuple(ident)
|
.map(|t| Expr::NamedTuple(t));
|
||||||
.map(|t| Expr::NamedTuple(t))
|
let span = n_tuple.span;
|
||||||
));
|
return Ok(Spanned::new(FuncArg::Pos(n_tuple), span));
|
||||||
}
|
}
|
||||||
|
|
||||||
p.skip_whitespace();
|
p.skip_whitespace();
|
||||||
@ -209,16 +209,22 @@ impl<'s> FuncParser<'s> {
|
|||||||
let value = p.parse_expr().ok_or(("value", None))?;
|
let value = p.parse_expr().ok_or(("value", None))?;
|
||||||
|
|
||||||
// Add a keyword argument.
|
// Add a keyword argument.
|
||||||
Ok(FuncArg::Key(Pair { key: ident, value }))
|
let span = Span::merge(ident.span, value.span);
|
||||||
|
Ok(Spanned::new(
|
||||||
|
FuncArg::Key(Spanned::new(Pair { key: ident, value }, span)),
|
||||||
|
span,
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
// Add a positional argument because there was no equals
|
// Add a positional argument because there was no equals
|
||||||
// sign after the identifier that could have been a key.
|
// sign after the identifier that could have been a key.
|
||||||
Ok(FuncArg::Pos(ident.map(|id| Expr::Ident(id))))
|
let ident = ident.map(|id| Expr::Ident(id));
|
||||||
|
let span = ident.span;
|
||||||
|
Ok(Spanned::new(FuncArg::Pos(ident), span))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Add a positional argument because we haven't got an
|
// Add a positional argument because we haven't got an
|
||||||
// identifier that could be an argument key.
|
// identifier that could be an argument key.
|
||||||
p.parse_expr().map(|expr| FuncArg::Pos(expr))
|
p.parse_expr().map(|expr| Spanned { span: expr.span, v: FuncArg::Pos(expr) })
|
||||||
.ok_or(("argument", None))
|
.ok_or(("argument", None))
|
||||||
}
|
}
|
||||||
}).v
|
}).v
|
||||||
@ -231,78 +237,58 @@ impl<'s> FuncParser<'s> {
|
|||||||
/// we look for multiplication and division and here finally, for addition
|
/// we look for multiplication and division and here finally, for addition
|
||||||
/// and subtraction.
|
/// and subtraction.
|
||||||
fn parse_expr(&mut self) -> Option<Spanned<Expr>> {
|
fn parse_expr(&mut self) -> Option<Spanned<Expr>> {
|
||||||
let term = self.parse_term()?;
|
let o1 = self.parse_term()?;
|
||||||
self.skip_whitespace();
|
self.parse_binop(o1, "summand", Self::parse_expr, |token| match token {
|
||||||
|
Token::Plus => Some(Expr::Add),
|
||||||
if let Some(next) = self.peek() {
|
Token::Hyphen => Some(Expr::Sub),
|
||||||
match next.v {
|
_ => None,
|
||||||
Token::Plus | Token::Hyphen => {
|
})
|
||||||
self.eat();
|
|
||||||
self.skip_whitespace();
|
|
||||||
let o2 = self.parse_expr();
|
|
||||||
if o2.is_none() {
|
|
||||||
self.feedback.errors.push(err!(
|
|
||||||
Span::merge(next.span, term.span);
|
|
||||||
"Missing right summand"
|
|
||||||
));
|
|
||||||
return Some(term)
|
|
||||||
}
|
|
||||||
|
|
||||||
let o2 = o2.expect("Checked for None before");
|
|
||||||
let span = Span::merge(term.span, o2.span);
|
|
||||||
match next.v {
|
|
||||||
Token::Plus => Some(Spanned::new(
|
|
||||||
Expr::Add(Box::new(term), Box::new(o2)), span
|
|
||||||
)),
|
|
||||||
Token::Hyphen => Some(Spanned::new(
|
|
||||||
Expr::Sub(Box::new(term), Box::new(o2)), span
|
|
||||||
)),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => Some(term)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Some(term)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_term(&mut self) -> Option<Spanned<Expr>> {
|
fn parse_term(&mut self) -> Option<Spanned<Expr>> {
|
||||||
// TODO: Deduplicate code here
|
let o1 = self.parse_factor()?;
|
||||||
let factor = self.parse_factor()?;
|
self.parse_binop(o1, "factor", Self::parse_term, |token| match token {
|
||||||
|
Token::Star => Some(Expr::Mul),
|
||||||
|
Token::Slash => Some(Expr::Div),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_binop<F, G>(
|
||||||
|
&mut self,
|
||||||
|
o1: Spanned<Expr>,
|
||||||
|
operand_name: &str,
|
||||||
|
mut parse_operand: F,
|
||||||
|
parse_op: G,
|
||||||
|
) -> Option<Spanned<Expr>>
|
||||||
|
where
|
||||||
|
F: FnMut(&mut Self) -> Option<Spanned<Expr>>,
|
||||||
|
G: FnOnce(Token) -> Option<fn(Box<Spanned<Expr>>, Box<Spanned<Expr>>) -> Expr>,
|
||||||
|
{
|
||||||
self.skip_whitespace();
|
self.skip_whitespace();
|
||||||
|
|
||||||
if let Some(next) = self.peek() {
|
if let Some(next) = self.peek() {
|
||||||
match next.v {
|
let binop = match parse_op(next.v) {
|
||||||
Token::Star | Token::Slash => {
|
Some(op) => op,
|
||||||
self.eat();
|
None => return Some(o1),
|
||||||
self.skip_whitespace();
|
};
|
||||||
let o2 = self.parse_term();
|
|
||||||
if o2.is_none() {
|
|
||||||
self.feedback.errors.push(err!(
|
|
||||||
Span::merge(next.span, factor.span);
|
|
||||||
"Missing right factor"
|
|
||||||
));
|
|
||||||
return Some(factor)
|
|
||||||
}
|
|
||||||
|
|
||||||
let o2 = o2.expect("Checked for None before");
|
self.eat();
|
||||||
let span = Span::merge(factor.span, o2.span);
|
self.skip_whitespace();
|
||||||
match next.v {
|
|
||||||
Token::Star => Some(Spanned::new(
|
if let Some(o2) = parse_operand(self) {
|
||||||
Expr::Mul(Box::new(factor), Box::new(o2)), span
|
let span = Span::merge(o1.span, o2.span);
|
||||||
)),
|
let expr = binop(Box::new(o1), Box::new(o2));
|
||||||
Token::Slash => Some(Spanned::new(
|
return Some(Spanned::new(expr, span));
|
||||||
Expr::Div(Box::new(factor), Box::new(o2)), span
|
} else {
|
||||||
)),
|
self.feedback.errors.push(err!(
|
||||||
_ => unreachable!(),
|
Span::merge(next.span, o1.span);
|
||||||
}
|
"missing right {}", operand_name,
|
||||||
},
|
));
|
||||||
_ => Some(factor)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Some(factor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some(o1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse expressions that are of the form value or -value
|
/// Parse expressions that are of the form value or -value
|
||||||
@ -310,16 +296,15 @@ impl<'s> FuncParser<'s> {
|
|||||||
let first = self.peek()?;
|
let first = self.peek()?;
|
||||||
if first.v == Token::Hyphen {
|
if first.v == Token::Hyphen {
|
||||||
self.eat();
|
self.eat();
|
||||||
let o2 = self.parse_value();
|
|
||||||
self.skip_whitespace();
|
self.skip_whitespace();
|
||||||
if o2.is_none() {
|
|
||||||
self.feedback.errors.push(err!(first.span; "Dangling minus"));
|
if let Some(factor) = self.parse_value() {
|
||||||
return None
|
let span = Span::merge(first.span, factor.span);
|
||||||
|
Some(Spanned::new(Expr::Neg(Box::new(factor)), span))
|
||||||
|
} else {
|
||||||
|
self.feedback.errors.push(err!(first.span; "dangling minus"));
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
let o2 = o2.expect("Checked for None before");
|
|
||||||
let span = Span::merge(first.span, o2.span);
|
|
||||||
Some(Spanned::new(Expr::Neg(Box::new(o2)), span))
|
|
||||||
} else {
|
} else {
|
||||||
self.parse_value()
|
self.parse_value()
|
||||||
}
|
}
|
||||||
@ -423,7 +408,8 @@ impl<'s> FuncParser<'s> {
|
|||||||
|
|
||||||
let value = p.parse_expr().ok_or(("value", None))?;
|
let value = p.parse_expr().ok_or(("value", None))?;
|
||||||
|
|
||||||
Ok(Pair { key, value })
|
let span = Span::merge(key.span, value.span);
|
||||||
|
Ok(Spanned::new(Pair { key, value }, span))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -438,8 +424,8 @@ impl<'s> FuncParser<'s> {
|
|||||||
mut parse_item: F
|
mut parse_item: F
|
||||||
) -> (Spanned<C>, bool)
|
) -> (Spanned<C>, bool)
|
||||||
where
|
where
|
||||||
C: FromIterator<I>,
|
C: FromIterator<Spanned<I>>,
|
||||||
F: FnMut(&mut Self) -> Result<I, (&'static str, Option<Position>)>,
|
F: FnMut(&mut Self) -> Result<Spanned<I>, (&'static str, Option<Position>)>,
|
||||||
{
|
{
|
||||||
let start = self.pos();
|
let start = self.pos();
|
||||||
let mut can_be_coerced = true;
|
let mut can_be_coerced = true;
|
||||||
@ -469,7 +455,6 @@ impl<'s> FuncParser<'s> {
|
|||||||
Ok(item) => {
|
Ok(item) => {
|
||||||
// Expect a comma behind the item (only separated by
|
// Expect a comma behind the item (only separated by
|
||||||
// whitespace).
|
// whitespace).
|
||||||
let behind_item = self.pos();
|
|
||||||
self.skip_whitespace();
|
self.skip_whitespace();
|
||||||
match self.peekv() {
|
match self.peekv() {
|
||||||
Some(Token::Comma) => {
|
Some(Token::Comma) => {
|
||||||
@ -478,7 +463,7 @@ impl<'s> FuncParser<'s> {
|
|||||||
}
|
}
|
||||||
t @ Some(_) if t != end => {
|
t @ Some(_) if t != end => {
|
||||||
can_be_coerced = false;
|
can_be_coerced = false;
|
||||||
self.expected_at("comma", behind_item);
|
self.expected_at("comma", item.span.end);
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@ -513,8 +498,8 @@ impl<'s> FuncParser<'s> {
|
|||||||
parse_item: F
|
parse_item: F
|
||||||
) -> Spanned<C>
|
) -> Spanned<C>
|
||||||
where
|
where
|
||||||
C: FromIterator<I>,
|
C: FromIterator<Spanned<I>>,
|
||||||
F: FnMut(&mut Self) -> Result<I, (&'static str, Option<Position>)>,
|
F: FnMut(&mut Self) -> Result<Spanned<I>, (&'static str, Option<Position>)>,
|
||||||
{
|
{
|
||||||
self.parse_collection_bracket_aware(end, parse_item).0
|
self.parse_collection_bracket_aware(end, parse_item).0
|
||||||
}
|
}
|
||||||
@ -735,10 +720,10 @@ mod tests {
|
|||||||
macro_rules! object {
|
macro_rules! object {
|
||||||
($($key:expr => $value:expr),* $(,)?) => {
|
($($key:expr => $value:expr),* $(,)?) => {
|
||||||
Expr::Object(Object {
|
Expr::Object(Object {
|
||||||
pairs: vec![$(Pair {
|
pairs: vec![$(zspan(Pair {
|
||||||
key: zspan(Ident($key.to_string())),
|
key: zspan(Ident($key.to_string())),
|
||||||
value: zspan($value),
|
value: zspan($value),
|
||||||
}),*]
|
})),*]
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -995,9 +980,20 @@ mod tests {
|
|||||||
Sub(Div(Num(5.0), Num(2.0)), Num(1.0))
|
Sub(Div(Num(5.0), Num(2.0)), Num(1.0))
|
||||||
)
|
)
|
||||||
), {})]);
|
), {})]);
|
||||||
p!("[val: (6.3E+2+4*-3.2pt)/2]" => [func!("val": (
|
p!("[val: (6.3E+2+4* - 3.2pt)/2]" => [func!("val": (
|
||||||
Div(Add(Num(6.3e2),Mul(Num(4.0), Neg(Pt(3.2)))), Num(2.0))
|
Div(Add(Num(6.3e2),Mul(Num(4.0), Neg(Pt(3.2)))), Num(2.0))
|
||||||
), {})]);
|
), {})]);
|
||||||
|
p!("[val: 4pt--]" =>
|
||||||
|
[func!("val": (Pt(4.0)), {})],
|
||||||
|
[
|
||||||
|
(0:10, 0:11, "dangling minus"),
|
||||||
|
(0:6, 0:10, "missing right summand")
|
||||||
|
],
|
||||||
|
);
|
||||||
|
p!("[val: 3mm+4pt*]" =>
|
||||||
|
[func!("val": (Add(Sz(Size::mm(3.0)), Pt(4.0))), {})],
|
||||||
|
[(0:10, 0:14, "missing right factor")],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -152,7 +152,7 @@ impl SpanlessEq for Object {
|
|||||||
fn spanless_eq(&self, other: &Object) -> bool {
|
fn spanless_eq(&self, other: &Object) -> bool {
|
||||||
self.pairs.len() == other.pairs.len()
|
self.pairs.len() == other.pairs.len()
|
||||||
&& self.pairs.iter().zip(&other.pairs)
|
&& self.pairs.iter().zip(&other.pairs)
|
||||||
.all(|(x, y)| x.key.v == y.key.v && x.value.v.spanless_eq(&y.value.v))
|
.all(|(x, y)| x.v.key.v == y.v.key.v && x.v.value.v.spanless_eq(&y.v.value.v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user