mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
parent
c505a0f5dc
commit
4524539c2b
@ -274,6 +274,8 @@ pub enum Param {
|
|||||||
Named(Ident, Value),
|
Named(Ident, Value),
|
||||||
/// An argument sink: `..args`.
|
/// An argument sink: `..args`.
|
||||||
Sink(Option<Ident>),
|
Sink(Option<Ident>),
|
||||||
|
/// A placeholder: `_`.
|
||||||
|
Placeholder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Closure {
|
impl Closure {
|
||||||
@ -334,6 +336,9 @@ impl Closure {
|
|||||||
args.named::<Value>(ident)?.unwrap_or_else(|| default.clone());
|
args.named::<Value>(ident)?.unwrap_or_else(|| default.clone());
|
||||||
vm.define(ident.clone(), value);
|
vm.define(ident.clone(), value);
|
||||||
}
|
}
|
||||||
|
Param::Placeholder => {
|
||||||
|
args.eat::<Value>()?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1160,6 +1160,7 @@ impl Eval for ast::Closure {
|
|||||||
params.push(Param::Named(named.name(), named.expr().eval(vm)?));
|
params.push(Param::Named(named.name(), named.expr().eval(vm)?));
|
||||||
}
|
}
|
||||||
ast::Param::Sink(name) => params.push(Param::Sink(name)),
|
ast::Param::Sink(name) => params.push(Param::Sink(name)),
|
||||||
|
ast::Param::Placeholder => params.push(Param::Placeholder),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1184,6 +1185,7 @@ impl ast::Pattern {
|
|||||||
vm.define(ident.clone(), value);
|
vm.define(ident.clone(), value);
|
||||||
Ok(Value::None)
|
Ok(Value::None)
|
||||||
}
|
}
|
||||||
|
ast::Pattern::Placeholder => Ok(Value::None),
|
||||||
ast::Pattern::Destructuring(destruct) => {
|
ast::Pattern::Destructuring(destruct) => {
|
||||||
match value {
|
match value {
|
||||||
Value::Array(value) => {
|
Value::Array(value) => {
|
||||||
@ -1215,6 +1217,7 @@ impl ast::Pattern {
|
|||||||
"cannot destructure named elements from an array"
|
"cannot destructure named elements from an array"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
ast::DestructuringKind::Placeholder => i += 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if i < value.len() {
|
if i < value.len() {
|
||||||
@ -1243,6 +1246,7 @@ impl ast::Pattern {
|
|||||||
vm.define(ident.clone(), v.clone());
|
vm.define(ident.clone(), v.clone());
|
||||||
used.insert(key.clone().take());
|
used.insert(key.clone().take());
|
||||||
}
|
}
|
||||||
|
ast::DestructuringKind::Placeholder => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1568,6 +1568,8 @@ pub enum Param {
|
|||||||
Named(Named),
|
Named(Named),
|
||||||
/// An argument sink: `..args`.
|
/// An argument sink: `..args`.
|
||||||
Sink(Option<Ident>),
|
Sink(Option<Ident>),
|
||||||
|
/// A placeholder: `_`.
|
||||||
|
Placeholder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AstNode for Param {
|
impl AstNode for Param {
|
||||||
@ -1576,6 +1578,7 @@ impl AstNode for Param {
|
|||||||
SyntaxKind::Ident => node.cast().map(Self::Pos),
|
SyntaxKind::Ident => node.cast().map(Self::Pos),
|
||||||
SyntaxKind::Named => node.cast().map(Self::Named),
|
SyntaxKind::Named => node.cast().map(Self::Named),
|
||||||
SyntaxKind::Spread => Some(Self::Sink(node.cast_first_match())),
|
SyntaxKind::Spread => Some(Self::Sink(node.cast_first_match())),
|
||||||
|
SyntaxKind::Underscore => Some(Self::Placeholder),
|
||||||
_ => Option::None,
|
_ => Option::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1585,6 +1588,7 @@ impl AstNode for Param {
|
|||||||
Self::Pos(v) => v.as_untyped(),
|
Self::Pos(v) => v.as_untyped(),
|
||||||
Self::Named(v) => v.as_untyped(),
|
Self::Named(v) => v.as_untyped(),
|
||||||
Self::Sink(_) => self.as_untyped(),
|
Self::Sink(_) => self.as_untyped(),
|
||||||
|
Self::Placeholder => self.as_untyped(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1603,6 +1607,8 @@ pub enum DestructuringKind {
|
|||||||
Sink(Option<Ident>),
|
Sink(Option<Ident>),
|
||||||
/// Named arguments: `x: 1`.
|
/// Named arguments: `x: 1`.
|
||||||
Named(Ident, Ident),
|
Named(Ident, Ident),
|
||||||
|
/// A placeholder: `_`.
|
||||||
|
Placeholder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Destructuring {
|
impl Destructuring {
|
||||||
@ -1619,6 +1625,7 @@ impl Destructuring {
|
|||||||
let ident = filtered.next().unwrap_or_default();
|
let ident = filtered.next().unwrap_or_default();
|
||||||
Some(DestructuringKind::Named(key, ident))
|
Some(DestructuringKind::Named(key, ident))
|
||||||
}
|
}
|
||||||
|
SyntaxKind::Underscore => Some(DestructuringKind::Placeholder),
|
||||||
_ => Option::None,
|
_ => Option::None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1629,6 +1636,7 @@ impl Destructuring {
|
|||||||
DestructuringKind::Ident(ident) => Some(ident),
|
DestructuringKind::Ident(ident) => Some(ident),
|
||||||
DestructuringKind::Sink(ident) => ident,
|
DestructuringKind::Sink(ident) => ident,
|
||||||
DestructuringKind::Named(_, ident) => Some(ident),
|
DestructuringKind::Named(_, ident) => Some(ident),
|
||||||
|
DestructuringKind::Placeholder => Option::None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1638,6 +1646,8 @@ impl Destructuring {
|
|||||||
pub enum Pattern {
|
pub enum Pattern {
|
||||||
/// A single identifier: `x`.
|
/// A single identifier: `x`.
|
||||||
Ident(Ident),
|
Ident(Ident),
|
||||||
|
/// A placeholder: `_`.
|
||||||
|
Placeholder,
|
||||||
/// A destructuring pattern: `(x, _, ..y)`.
|
/// A destructuring pattern: `(x, _, ..y)`.
|
||||||
Destructuring(Destructuring),
|
Destructuring(Destructuring),
|
||||||
}
|
}
|
||||||
@ -1647,6 +1657,7 @@ impl AstNode for Pattern {
|
|||||||
match node.kind() {
|
match node.kind() {
|
||||||
SyntaxKind::Ident => node.cast().map(Self::Ident),
|
SyntaxKind::Ident => node.cast().map(Self::Ident),
|
||||||
SyntaxKind::Destructuring => node.cast().map(Self::Destructuring),
|
SyntaxKind::Destructuring => node.cast().map(Self::Destructuring),
|
||||||
|
SyntaxKind::Underscore => Some(Self::Placeholder),
|
||||||
_ => Option::None,
|
_ => Option::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1655,6 +1666,7 @@ impl AstNode for Pattern {
|
|||||||
match self {
|
match self {
|
||||||
Self::Ident(v) => v.as_untyped(),
|
Self::Ident(v) => v.as_untyped(),
|
||||||
Self::Destructuring(v) => v.as_untyped(),
|
Self::Destructuring(v) => v.as_untyped(),
|
||||||
|
Self::Placeholder => self.as_untyped(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1665,6 +1677,7 @@ impl Pattern {
|
|||||||
match self {
|
match self {
|
||||||
Pattern::Ident(ident) => vec![ident.clone()],
|
Pattern::Ident(ident) => vec![ident.clone()],
|
||||||
Pattern::Destructuring(destruct) => destruct.idents().collect(),
|
Pattern::Destructuring(destruct) => destruct.idents().collect(),
|
||||||
|
Pattern::Placeholder => vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1722,9 +1735,7 @@ impl LetBinding {
|
|||||||
LetBindingKind::Normal(Pattern::Ident(_)) => {
|
LetBindingKind::Normal(Pattern::Ident(_)) => {
|
||||||
self.0.children().filter_map(SyntaxNode::cast).nth(1)
|
self.0.children().filter_map(SyntaxNode::cast).nth(1)
|
||||||
}
|
}
|
||||||
LetBindingKind::Normal(Pattern::Destructuring(_)) => {
|
LetBindingKind::Normal(_) => self.0.cast_first_match(),
|
||||||
self.0.cast_first_match()
|
|
||||||
}
|
|
||||||
LetBindingKind::Closure(_) => self.0.cast_first_match(),
|
LetBindingKind::Closure(_) => self.0.cast_first_match(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -527,8 +527,12 @@ impl Lexer<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ident == "_" {
|
||||||
|
SyntaxKind::Underscore
|
||||||
|
} else {
|
||||||
SyntaxKind::Ident
|
SyntaxKind::Ident
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn number(&mut self, mut start: usize, c: char) -> SyntaxKind {
|
fn number(&mut self, mut start: usize, c: char) -> SyntaxKind {
|
||||||
// Handle alternative integer bases.
|
// Handle alternative integer bases.
|
||||||
|
@ -636,6 +636,17 @@ fn code_primary(p: &mut Parser, atomic: bool) {
|
|||||||
p.wrap(m, SyntaxKind::Closure);
|
p.wrap(m, SyntaxKind::Closure);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SyntaxKind::Underscore if !atomic => {
|
||||||
|
p.eat();
|
||||||
|
if p.at(SyntaxKind::Arrow) {
|
||||||
|
p.wrap(m, SyntaxKind::Params);
|
||||||
|
p.eat();
|
||||||
|
code_expr(p);
|
||||||
|
p.wrap(m, SyntaxKind::Closure);
|
||||||
|
} else if let Some(underscore) = p.node_mut(m) {
|
||||||
|
underscore.convert_to_error("expected expression, found underscore");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SyntaxKind::LeftBrace => code_block(p),
|
SyntaxKind::LeftBrace => code_block(p),
|
||||||
SyntaxKind::LeftBracket => content_block(p),
|
SyntaxKind::LeftBracket => content_block(p),
|
||||||
@ -718,6 +729,7 @@ fn with_paren(p: &mut Parser) {
|
|||||||
match kind {
|
match kind {
|
||||||
SyntaxKind::Array => validate_array(p, m),
|
SyntaxKind::Array => validate_array(p, m),
|
||||||
SyntaxKind::Dict => validate_dict(p, m),
|
SyntaxKind::Dict => validate_dict(p, m),
|
||||||
|
SyntaxKind::Parenthesized => validate_parenthesized(p, m),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
p.wrap(m, kind);
|
p.wrap(m, kind);
|
||||||
@ -787,13 +799,19 @@ fn item(p: &mut Parser, keyed: bool) -> SyntaxKind {
|
|||||||
return SyntaxKind::Spread;
|
return SyntaxKind::Spread;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !p.eat_if(SyntaxKind::Underscore) {
|
||||||
code_expr(p);
|
code_expr(p);
|
||||||
|
} else {
|
||||||
|
return SyntaxKind::Underscore;
|
||||||
|
}
|
||||||
|
|
||||||
if !p.eat_if(SyntaxKind::Colon) {
|
if !p.eat_if(SyntaxKind::Colon) {
|
||||||
return SyntaxKind::Int;
|
return SyntaxKind::Int;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !p.eat_if(SyntaxKind::Underscore) {
|
||||||
code_expr(p);
|
code_expr(p);
|
||||||
|
}
|
||||||
|
|
||||||
let kind = match p.node(m).map(SyntaxNode::kind) {
|
let kind = match p.node(m).map(SyntaxNode::kind) {
|
||||||
Some(SyntaxKind::Ident) => SyntaxKind::Named,
|
Some(SyntaxKind::Ident) => SyntaxKind::Named,
|
||||||
@ -840,12 +858,12 @@ fn args(p: &mut Parser) {
|
|||||||
|
|
||||||
enum PatternKind {
|
enum PatternKind {
|
||||||
Ident,
|
Ident,
|
||||||
|
Placeholder,
|
||||||
Destructuring,
|
Destructuring,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pattern(p: &mut Parser) -> PatternKind {
|
fn pattern(p: &mut Parser) -> PatternKind {
|
||||||
let m = p.marker();
|
let m = p.marker();
|
||||||
|
|
||||||
if p.at(SyntaxKind::LeftParen) {
|
if p.at(SyntaxKind::LeftParen) {
|
||||||
let kind = collection(p, false);
|
let kind = collection(p, false);
|
||||||
validate_destruct_pattern(p, m);
|
validate_destruct_pattern(p, m);
|
||||||
@ -856,6 +874,8 @@ fn pattern(p: &mut Parser) -> PatternKind {
|
|||||||
p.wrap(m, SyntaxKind::Destructuring);
|
p.wrap(m, SyntaxKind::Destructuring);
|
||||||
PatternKind::Destructuring
|
PatternKind::Destructuring
|
||||||
}
|
}
|
||||||
|
} else if p.eat_if(SyntaxKind::Underscore) {
|
||||||
|
PatternKind::Placeholder
|
||||||
} else {
|
} else {
|
||||||
p.expect(SyntaxKind::Ident);
|
p.expect(SyntaxKind::Ident);
|
||||||
PatternKind::Ident
|
PatternKind::Ident
|
||||||
@ -879,6 +899,7 @@ fn let_binding(p: &mut Parser) {
|
|||||||
p.wrap(m3, SyntaxKind::Params);
|
p.wrap(m3, SyntaxKind::Params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
PatternKind::Placeholder => {}
|
||||||
PatternKind::Destructuring => destructuring = true,
|
PatternKind::Destructuring => destructuring = true,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -961,7 +982,9 @@ fn for_loop(p: &mut Parser) {
|
|||||||
pattern(p);
|
pattern(p);
|
||||||
if p.at(SyntaxKind::Comma) {
|
if p.at(SyntaxKind::Comma) {
|
||||||
p.expected("keyword `in`. did you mean to use a destructuring pattern?");
|
p.expected("keyword `in`. did you mean to use a destructuring pattern?");
|
||||||
p.eat_if(SyntaxKind::Ident);
|
if !p.eat_if(SyntaxKind::Ident) {
|
||||||
|
p.eat_if(SyntaxKind::Underscore);
|
||||||
|
}
|
||||||
p.eat_if(SyntaxKind::In);
|
p.eat_if(SyntaxKind::In);
|
||||||
} else {
|
} else {
|
||||||
p.expect(SyntaxKind::In);
|
p.expect(SyntaxKind::In);
|
||||||
@ -1023,10 +1046,25 @@ fn return_stmt(p: &mut Parser) {
|
|||||||
p.wrap(m, SyntaxKind::FuncReturn);
|
p.wrap(m, SyntaxKind::FuncReturn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_parenthesized(p: &mut Parser, m: Marker) {
|
||||||
|
for child in p.post_process(m) {
|
||||||
|
let kind = child.kind();
|
||||||
|
if kind == SyntaxKind::Underscore {
|
||||||
|
child.convert_to_error(eco_format!(
|
||||||
|
"expected expression, found {}",
|
||||||
|
kind.name()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_array(p: &mut Parser, m: Marker) {
|
fn validate_array(p: &mut Parser, m: Marker) {
|
||||||
for child in p.post_process(m) {
|
for child in p.post_process(m) {
|
||||||
let kind = child.kind();
|
let kind = child.kind();
|
||||||
if kind == SyntaxKind::Named || kind == SyntaxKind::Keyed {
|
if kind == SyntaxKind::Named
|
||||||
|
|| kind == SyntaxKind::Keyed
|
||||||
|
|| kind == SyntaxKind::Underscore
|
||||||
|
{
|
||||||
child.convert_to_error(eco_format!(
|
child.convert_to_error(eco_format!(
|
||||||
"expected expression, found {}",
|
"expected expression, found {}",
|
||||||
kind.name()
|
kind.name()
|
||||||
@ -1114,7 +1152,10 @@ fn validate_params(p: &mut Parser, m: Marker) {
|
|||||||
child.make_erroneous();
|
child.make_erroneous();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SyntaxKind::LeftParen | SyntaxKind::RightParen | SyntaxKind::Comma => {}
|
SyntaxKind::LeftParen
|
||||||
|
| SyntaxKind::RightParen
|
||||||
|
| SyntaxKind::Comma
|
||||||
|
| SyntaxKind::Underscore => {}
|
||||||
kind => {
|
kind => {
|
||||||
child.convert_to_error(eco_format!(
|
child.convert_to_error(eco_format!(
|
||||||
"expected identifier, named pair or argument sink, found {}",
|
"expected identifier, named pair or argument sink, found {}",
|
||||||
@ -1137,6 +1178,8 @@ fn validate_args(p: &mut Parser, m: Marker) {
|
|||||||
));
|
));
|
||||||
child.make_erroneous();
|
child.make_erroneous();
|
||||||
}
|
}
|
||||||
|
} else if child.kind() == SyntaxKind::Underscore {
|
||||||
|
child.convert_to_error("unexpected underscore");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1147,7 +1190,7 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
|
|||||||
for child in p.post_process(m) {
|
for child in p.post_process(m) {
|
||||||
match child.kind() {
|
match child.kind() {
|
||||||
SyntaxKind::Ident => {
|
SyntaxKind::Ident => {
|
||||||
if child.text() != "_" && !used.insert(child.text().clone()) {
|
if !used.insert(child.text().clone()) {
|
||||||
child.convert_to_error(
|
child.convert_to_error(
|
||||||
"at most one binding per identifier is allowed",
|
"at most one binding per identifier is allowed",
|
||||||
);
|
);
|
||||||
@ -1172,7 +1215,7 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if within.text() != "_" && !used.insert(within.text().clone()) {
|
if !used.insert(within.text().clone()) {
|
||||||
within.convert_to_error(
|
within.convert_to_error(
|
||||||
"at most one binding per identifier is allowed",
|
"at most one binding per identifier is allowed",
|
||||||
);
|
);
|
||||||
@ -1189,7 +1232,9 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let Some(within) = child.children_mut().last_mut() else { return };
|
let Some(within) = child.children_mut().last_mut() else { return };
|
||||||
if within.kind() != SyntaxKind::Ident {
|
if within.kind() != SyntaxKind::Ident
|
||||||
|
&& within.kind() != SyntaxKind::Underscore
|
||||||
|
{
|
||||||
within.convert_to_error(eco_format!(
|
within.convert_to_error(eco_format!(
|
||||||
"expected identifier, found {}",
|
"expected identifier, found {}",
|
||||||
within.kind().name(),
|
within.kind().name(),
|
||||||
@ -1197,7 +1242,10 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
|
|||||||
child.make_erroneous();
|
child.make_erroneous();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SyntaxKind::LeftParen | SyntaxKind::RightParen | SyntaxKind::Comma => {}
|
SyntaxKind::LeftParen
|
||||||
|
| SyntaxKind::RightParen
|
||||||
|
| SyntaxKind::Comma
|
||||||
|
| SyntaxKind::Underscore => {}
|
||||||
kind => {
|
kind => {
|
||||||
child.convert_to_error(eco_format!(
|
child.convert_to_error(eco_format!(
|
||||||
"expected identifier or destructuring sink, found {}",
|
"expected identifier or destructuring sink, found {}",
|
||||||
@ -1313,6 +1361,10 @@ impl<'s> Parser<'s> {
|
|||||||
self.nodes.get(m.0)
|
self.nodes.get(m.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn node_mut(&mut self, m: Marker) -> Option<&mut SyntaxNode> {
|
||||||
|
self.nodes.get_mut(m.0)
|
||||||
|
}
|
||||||
|
|
||||||
fn post_process(&mut self, m: Marker) -> impl Iterator<Item = &mut SyntaxNode> {
|
fn post_process(&mut self, m: Marker) -> impl Iterator<Item = &mut SyntaxNode> {
|
||||||
self.nodes[m.0..]
|
self.nodes[m.0..]
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
|
@ -170,3 +170,7 @@
|
|||||||
---
|
---
|
||||||
// Error: 10-14 expected identifier, found `none`
|
// Error: 10-14 expected identifier, found `none`
|
||||||
#let foo(none: b) = key
|
#let foo(none: b) = key
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 11 expected comma
|
||||||
|
#let foo(_: 3) = none
|
||||||
|
@ -221,6 +221,26 @@ Three
|
|||||||
// Error: 9-13 expected identifier, found boolean
|
// Error: 9-13 expected identifier, found boolean
|
||||||
#let (..true) = false
|
#let (..true) = false
|
||||||
|
|
||||||
|
---
|
||||||
|
#let _ = 4
|
||||||
|
|
||||||
|
#for _ in range(2) []
|
||||||
|
|
||||||
|
// Error: 2-3 unexpected underscore
|
||||||
|
#_
|
||||||
|
|
||||||
|
// Error: 8-9 unexpected underscore
|
||||||
|
#lorem(_)
|
||||||
|
|
||||||
|
// Error: 3-4 expected expression, found underscore
|
||||||
|
#(_,)
|
||||||
|
|
||||||
|
// Error: 3-4 expected expression, found underscore
|
||||||
|
#{_}
|
||||||
|
|
||||||
|
// Error: 8-9 expected expression, found underscore
|
||||||
|
#{ 1 + _ }
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 13 expected equals sign
|
// Error: 13 expected equals sign
|
||||||
#let func(x)
|
#let func(x)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user