doughlang: let else, fn rety
ast: - `let Pat (= Expr (else Expr)?)?`, - `fn (args) -> Ty`, coagulating `do`, `if/while` formatting fixes parser: - int cleanup, - fn rety parsing, - experimental `for` desugar, - experimental semicolon elision (TODO: it sucks), - let-else parsing - `do` coagulation impl (still not 100% there) TODO: - Fix `do` coagulation - codify `do` elision rules - `for` needs lib support - this is fine because we have no codegen yet - Ty matching in macro_matcher - Or rip out macro_matcher? Who knows what the future holds.
This commit is contained in:
63
src/ast.rs
63
src/ast.rs
@@ -3,13 +3,25 @@
|
||||
pub mod macro_matcher;
|
||||
|
||||
/// A value with an annotation.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct Anno<T: Annotation, A: Annotation = Span>(pub T, pub A);
|
||||
|
||||
impl<T: Annotation, A: Annotation> std::fmt::Debug for Anno<T, A> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
<A as std::fmt::Debug>::fmt(&self.1, f)?;
|
||||
f.write_str(": ")?;
|
||||
<T as std::fmt::Debug>::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// An annotation: extra data added on to important AST nodes.
|
||||
pub trait Annotation: Clone + std::fmt::Display + std::fmt::Debug + PartialEq + Eq {}
|
||||
impl<T: Clone + std::fmt::Debug + std::fmt::Display + PartialEq + Eq> Annotation for T {}
|
||||
|
||||
//
|
||||
// TODO: Identifier interning
|
||||
//
|
||||
|
||||
/// A literal value (boolean, character, integer, string)
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Literal {
|
||||
@@ -18,7 +30,7 @@ pub enum Literal {
|
||||
/// A character literal: 'a', '\u{1f988}'
|
||||
Char(char),
|
||||
/// An integer literal: 0, 123, 0x10
|
||||
Int(i128),
|
||||
Int(u128),
|
||||
/// A string literal:
|
||||
Str(String),
|
||||
}
|
||||
@@ -66,13 +78,18 @@ pub enum Ty {
|
||||
/// `[..Args, Rety]`
|
||||
Fn(Vec<Ty>),
|
||||
}
|
||||
impl Default for Ty {
|
||||
fn default() -> Self {
|
||||
Self::Tuple(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
/// A `let` binding
|
||||
/// ```ignore
|
||||
/// let Pat (= Expr)?
|
||||
/// ``````
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Let<A: Annotation = Span>(pub Pat, pub Option<Anno<Expr<A>, A>>);
|
||||
pub struct Let<A: Annotation = Span>(pub Pat, pub Vec<Anno<Expr<A>, A>>);
|
||||
|
||||
/// A `const` binding (which defines its name before executing)
|
||||
/// ```ignore
|
||||
@@ -86,7 +103,7 @@ pub struct Const<A: Annotation = Span>(pub Pat, pub Anno<Expr<A>, A>);
|
||||
/// fn Ident? (Pat) Expr
|
||||
/// ```
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Fn<A: Annotation = Span>(pub Option<String>, pub Pat, pub Anno<Expr<A>, A>);
|
||||
pub struct Fn<A: Annotation = Span>(pub Option<String>, pub Pat, pub Ty, pub Anno<Expr<A>, A>);
|
||||
|
||||
/// A match expression
|
||||
/// ```ignore
|
||||
@@ -107,7 +124,7 @@ pub struct MatchArm<A: Annotation = Span>(pub Pat, pub Anno<Expr<A>, A>);
|
||||
/// Expr { (Ident (: Expr)?),* }
|
||||
/// ```
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Make<A: Annotation = Span>(pub Box<Anno<Expr<A>, A>>, pub Vec<MakeArm<A>>);
|
||||
pub struct Make<A: Annotation = Span>(pub Anno<Expr<A>, A>, pub Vec<MakeArm<A>>);
|
||||
|
||||
/// A single "arm" of a make expression
|
||||
/// ```ignore
|
||||
@@ -154,11 +171,29 @@ pub enum Expr<A: Annotation = Span> {
|
||||
Op(Op, Vec<Anno<Self, A>>),
|
||||
}
|
||||
|
||||
impl<A: Annotation> Default for Expr<A> {
|
||||
fn default() -> Self {
|
||||
Self::Op(Op::Tuple, vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Annotation> Expr<A> {
|
||||
pub fn anno(self, annotation: A) -> Anno<Expr<A>, A> {
|
||||
Anno(self, annotation)
|
||||
}
|
||||
|
||||
pub fn and_do(self, annotation: A, other: Anno<Expr<A>, A>) -> Self {
|
||||
let Self::Op(Op::Do, mut exprs) = self else {
|
||||
return Self::Op(Op::Do, vec![self.anno(annotation), other]);
|
||||
};
|
||||
let Anno(Self::Op(Op::Do, mut other), _) = other else {
|
||||
exprs.push(other);
|
||||
return Self::Op(Op::Do, exprs);
|
||||
};
|
||||
exprs.append(&mut other);
|
||||
Self::Op(Op::Do, exprs)
|
||||
}
|
||||
|
||||
pub fn is_place(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
@@ -271,17 +306,20 @@ impl<A: Annotation> Display for Const<A> {
|
||||
impl<A: Annotation> Display for Fn<A> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self(Some(name), pat, expr) => write!(f, "fn {name} {pat} {expr}"),
|
||||
Self(None, pat, expr) => write!(f, "|{pat}| {expr}"),
|
||||
Self(Some(name), pat, rety, expr) => write!(f, "fn {name} {pat} -> {rety} {expr}"),
|
||||
Self(None, pat, rety, expr) => write!(f, "|{pat}| -> {rety} {expr}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Annotation> Display for Let<A> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self(pat, Some(expr)) => write!(f, "let {pat} = {expr}"),
|
||||
Self(pat, None) => write!(f, "let ({pat})"),
|
||||
let Self(pat, exprs) = self;
|
||||
match exprs.as_slice() {
|
||||
[] => write!(f, "let {pat}"),
|
||||
[value] => write!(f, "let {pat} = {value}"),
|
||||
[value, fail] => write!(f, "let {pat} = {value} else {fail}"),
|
||||
other => f.delimit(fmt!("let! {pat} ("), ")").list(other, ", "),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -314,7 +352,7 @@ impl Display for Struct {
|
||||
match pat {
|
||||
Pat::Struct(name, bind) => match bind.as_ref() {
|
||||
Pat::Tuple(parts) => f
|
||||
.delimit_indented(fmt!("{name} {{"), "}")
|
||||
.delimit_indented(fmt!("struct {name} {{"), "}")
|
||||
.list_wrap("\n", parts, ",\n", ",\n"),
|
||||
other => write!(f, "{name} {{ {other} }}"),
|
||||
},
|
||||
@@ -338,6 +376,9 @@ impl<A: Annotation> Display for Expr<A> {
|
||||
Self::Fn(v) => v.fmt(f),
|
||||
|
||||
Self::Op(op @ (Op::If | Op::While), exprs) => match exprs.as_slice() {
|
||||
[cond, pass, Anno(Expr::Op(Op::Tuple, e), _)] if e.is_empty() => {
|
||||
write!(f, "{op}{cond} {pass}")
|
||||
}
|
||||
[cond, pass, fail] => write!(f, "{op}{cond} {pass} else {fail}"),
|
||||
other => f.delimit(fmt!("({op}, "), ")").list(other, ", "),
|
||||
},
|
||||
|
||||
@@ -93,14 +93,17 @@ impl<A: Annotation> Match<A> for Const<A> {
|
||||
|
||||
impl<A: Annotation> Match<A> for Fn<A> {
|
||||
fn recurse(sub: &mut Subst<A>, pat: &Self, expr: &Self) -> bool {
|
||||
let (Self(pat_id, pat_arg, pat_body), Self(expr_id, expr_arg, expr_body)) = (pat, expr);
|
||||
let (
|
||||
Self(pat_id, pat_arg, _pat_rety, pat_body),
|
||||
Self(expr_id, expr_arg, _expr_rety, expr_body),
|
||||
) = (pat, expr);
|
||||
pat_id == expr_id
|
||||
&& Match::recurse(sub, pat_arg, expr_arg)
|
||||
&& Match::recurse(sub, pat_body, expr_body)
|
||||
}
|
||||
|
||||
fn apply(&mut self, sub: &Subst<A>) {
|
||||
let Self(_, pat, body) = self;
|
||||
let Self(_, pat, _rety, body) = self;
|
||||
pat.apply(sub);
|
||||
body.apply(sub);
|
||||
}
|
||||
|
||||
209
src/parser.rs
209
src/parser.rs
@@ -200,15 +200,14 @@ impl<'t> Parse<'t> for Literal {
|
||||
.expect("should have one char in char literal"),
|
||||
),
|
||||
TKind::Integer => {
|
||||
let Token { lexeme, kind: _, span } = p.take().expect("should have Token");
|
||||
// TODO: more complex int parsing
|
||||
let int = lexeme
|
||||
.int()
|
||||
.ok_or(ParseError::Expected(TKind::Integer, span))?;
|
||||
Literal::Int(int as _)
|
||||
let Token { lexeme, span, .. } = p.take().expect("should have Token");
|
||||
let Lexeme::Integer(int, _) = lexeme else {
|
||||
Err(ParseError::Expected(TKind::Integer, span))?
|
||||
};
|
||||
Literal::Int(int)
|
||||
}
|
||||
TKind::String => Literal::Str({
|
||||
let Token { lexeme, kind: _, span } = p.take().expect("should have Token");
|
||||
let Token { lexeme, span, .. } = p.take().expect("should have Token");
|
||||
lexeme
|
||||
.string()
|
||||
.ok_or(ParseError::Expected(TKind::String, span))?
|
||||
@@ -368,7 +367,10 @@ impl<'t> Parse<'t> for Ty {
|
||||
_ => Err(ParseError::NotType(tok.kind, tok.span))?,
|
||||
};
|
||||
|
||||
Ok(head)
|
||||
Ok(match p.next_if(TKind::Arrow) {
|
||||
Ok(_) => Ty::Fn(vec![head, p.parse(())?]),
|
||||
_ => head,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,21 +437,23 @@ impl Prec {
|
||||
/// PseudoOperator: fake operators used to give certain tokens special behavior.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Ps {
|
||||
Id, // Identifier
|
||||
Mid, // MetaIdentifier
|
||||
Lit, // Literal
|
||||
Let, // let Pat = Expr
|
||||
Const, // const Pat = Expr
|
||||
Struct, // struct { Pat } | struct ( Pat )
|
||||
Fn, // fn ( Pat,* ) Expr
|
||||
Lambda0, // || Expr
|
||||
Lambda, // | Pat,* | Expr
|
||||
DoubleRef, // && Expr
|
||||
Make, // Expr{ Expr,* }
|
||||
Match, // match Expr { MatchArm,* }
|
||||
Mod, // mod Ty Expr
|
||||
End, // Produces an empty value.
|
||||
Op(Op), // A normal [ast::Op]
|
||||
Id, // Identifier
|
||||
Mid, // MetaIdentifier
|
||||
Lit, // Literal
|
||||
Let, // let Pat = Expr
|
||||
Const, // const Pat = Expr
|
||||
Struct, // struct { Pat } | struct ( Pat )
|
||||
For, // for Pat in Expr Expr else Expr
|
||||
Fn, // fn ( Pat,* ) Expr
|
||||
Lambda0, // || Expr
|
||||
Lambda, // | Pat,* | Expr
|
||||
DoubleRef, // && Expr
|
||||
Make, // Expr{ Expr,* }
|
||||
Match, // match Expr { MatchArm,* }
|
||||
Mod, // mod Ty Expr
|
||||
ImplicitDo, // An implicit semicolon
|
||||
End, // Produces an empty value.
|
||||
Op(Op), // A normal [ast::Op]
|
||||
}
|
||||
|
||||
fn from_prefix(token: &Token) -> PResult<(Ps, Prec)> {
|
||||
@@ -464,6 +468,7 @@ fn from_prefix(token: &Token) -> PResult<(Ps, Prec)> {
|
||||
}
|
||||
|
||||
TKind::Public => (Ps::Op(Op::Pub), Prec::Body),
|
||||
TKind::For => (Ps::For, Prec::Body),
|
||||
TKind::Fn => (Ps::Fn, Prec::Body),
|
||||
TKind::Match => (Ps::Match, Prec::Body),
|
||||
TKind::Macro => (Ps::Op(Op::Macro), Prec::Assign),
|
||||
@@ -533,6 +538,22 @@ fn from_infix(token: &Token) -> PResult<(Ps, Prec)> {
|
||||
TKind::Star => (Ps::Op(Op::Mul), Prec::Term),
|
||||
TKind::Slash => (Ps::Op(Op::Div), Prec::Term),
|
||||
TKind::Rem => (Ps::Op(Op::Rem), Prec::Term),
|
||||
|
||||
TKind::True
|
||||
| TKind::False
|
||||
| TKind::Character
|
||||
| TKind::Integer
|
||||
| TKind::String
|
||||
| TKind::Identifier
|
||||
| TKind::Public
|
||||
| TKind::Module
|
||||
| TKind::Fn
|
||||
| TKind::Do
|
||||
| TKind::While
|
||||
| TKind::If
|
||||
| TKind::For
|
||||
| TKind::Break
|
||||
| TKind::Return => (Ps::ImplicitDo, Prec::Do),
|
||||
kind => Err(ParseError::NotInfix(kind, token.span))?,
|
||||
})
|
||||
}
|
||||
@@ -565,6 +586,7 @@ impl<'t> Parse<'t> for Fn {
|
||||
Ok(Token { lexeme, .. }) => Ok(Self(
|
||||
lexeme.string(),
|
||||
p.parse(PPrec::Typed)?,
|
||||
p.opt_if((), TKind::Arrow)?.unwrap_or_default(),
|
||||
p.parse(Prec::Body.next())?,
|
||||
)),
|
||||
_ => Ok(Self(
|
||||
@@ -575,6 +597,7 @@ impl<'t> Parse<'t> for Fn {
|
||||
TKind::Comma,
|
||||
TKind::RParen,
|
||||
)?),
|
||||
p.opt_if((), TKind::Arrow)?.unwrap_or_default(),
|
||||
p.parse(Prec::Body.next())?,
|
||||
)),
|
||||
}
|
||||
@@ -585,10 +608,17 @@ impl<'t> Parse<'t> for Let {
|
||||
type Prec = ();
|
||||
|
||||
fn parse(p: &mut Parser<'t>, _level: Self::Prec) -> PResult<Self> {
|
||||
Ok(Self(
|
||||
p.consume().parse(PPrec::Alt)?,
|
||||
p.opt_if(Prec::Tuple.value(), TKind::Eq)?,
|
||||
))
|
||||
let pat = p.consume().parse(PPrec::Tuple)?;
|
||||
if p.next_if(TKind::Eq).is_err() {
|
||||
return Ok(Self(pat, vec![]));
|
||||
}
|
||||
|
||||
let body = p.parse(Prec::Tuple.value())?;
|
||||
if p.next_if(TKind::Else).is_err() {
|
||||
return Ok(Self(pat, vec![body]));
|
||||
}
|
||||
|
||||
Ok(Self(pat, vec![body, p.parse(Prec::Body.next())?]))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,10 +664,106 @@ impl<'t> Parse<'t> for MakeArm {
|
||||
impl<'t> Parse<'t> for Mod {
|
||||
type Prec = ();
|
||||
fn parse(p: &mut Parser<'t>, _level: Self::Prec) -> PResult<Self> {
|
||||
Ok(Mod(p.consume().parse(())?, p.parse(Prec::Body.value())?))
|
||||
let ty = p.consume().parse(())?;
|
||||
let body = p.parse(Prec::Body.value())?;
|
||||
Ok(Mod(ty, body))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_for<'t>(p: &mut Parser<'t>, _level: ()) -> PResult<Expr> {
|
||||
// for Pat
|
||||
let pat = p.consume().parse(PPrec::Tuple)?;
|
||||
// in Expr
|
||||
let iter: Anno<Expr> = p.consume_if(TKind::In)?.parse(Prec::Logical.next())?;
|
||||
let cspan = iter.1;
|
||||
// Expr
|
||||
let pass: Anno<Expr> = p.parse(Prec::Body.next())?;
|
||||
let pspan = pass.1;
|
||||
// else Expr?
|
||||
let fail = match p.next_if(TKind::Else) {
|
||||
Ok(_) => p.parse(Prec::Body.next())?,
|
||||
_ => Expr::Op(Op::Tuple, vec![]).anno(pspan),
|
||||
};
|
||||
let fspan = fail.1;
|
||||
/*
|
||||
for `pat in `iter `pass else `fail
|
||||
==>
|
||||
match (`iter).into_iter() {
|
||||
#iter => loop match #iter.next() {
|
||||
None => break `fail,
|
||||
Some(`pat) => `pass,
|
||||
},
|
||||
}
|
||||
*/
|
||||
// let mut tmp_p = Parser::new(Lexer::new(
|
||||
// "match `iter.into_iter() {
|
||||
// `iterator => loop match `iterator.next() {
|
||||
// None => break `fail,
|
||||
// Some(`pat) => `pass,
|
||||
// },
|
||||
// }",
|
||||
// ));
|
||||
|
||||
// let mut template: Expr = tmp_p.parse(Prec::MIN)?;
|
||||
|
||||
// let mut subst = Subst::<Span> { exp: Default::default(), pat: Default::default() };
|
||||
// subst.exp.extend([
|
||||
// ("iterator".into(), Expr::Id("#iter".into())),
|
||||
// ("iter".into(), iter.0),
|
||||
// ("fail".into(), fail.0),
|
||||
// ("pass".into(), pass.0),
|
||||
// ]);
|
||||
// subst.pat.extend([
|
||||
// ("iterator".into(), Pat::Name("#iter".into())),
|
||||
// ("pat".into(), pat),
|
||||
// ]);
|
||||
|
||||
// template.apply(&subst);
|
||||
// Ok(template)
|
||||
|
||||
Ok(Expr::Match(Box::new(Match(
|
||||
Expr::Op(
|
||||
Op::Dot,
|
||||
vec![
|
||||
iter,
|
||||
Expr::Op(Op::Call, vec![Expr::Id("into_iter".into()).anno(cspan)]).anno(cspan),
|
||||
],
|
||||
)
|
||||
.anno(cspan),
|
||||
vec![MatchArm(
|
||||
Pat::Name("#iter".into()),
|
||||
Expr::Op(
|
||||
Op::Loop,
|
||||
vec![
|
||||
Expr::Match(Box::new(Match(
|
||||
Expr::Op(
|
||||
Op::Dot,
|
||||
vec![
|
||||
Expr::Id("#iter".into()).anno(cspan),
|
||||
Expr::Op(Op::Call, vec![Expr::Id("next".into()).anno(cspan)])
|
||||
.anno(cspan),
|
||||
],
|
||||
)
|
||||
.anno(cspan),
|
||||
vec![
|
||||
MatchArm(
|
||||
Pat::Name("None".into()),
|
||||
Expr::Op(Op::Break, vec![fail]).anno(fspan),
|
||||
),
|
||||
MatchArm(
|
||||
Pat::TupStruct("Some".into(), Box::new(Pat::Tuple(vec![pat]))),
|
||||
pass,
|
||||
),
|
||||
],
|
||||
)))
|
||||
.anno(pspan),
|
||||
],
|
||||
)
|
||||
.anno(pspan),
|
||||
)],
|
||||
))))
|
||||
}
|
||||
|
||||
impl<'t> Parse<'t> for Expr {
|
||||
type Prec = usize;
|
||||
|
||||
@@ -662,6 +788,7 @@ impl<'t> Parse<'t> for Expr {
|
||||
Ps::Mid => Expr::MetId(p.consume().next()?.lexeme.to_string()),
|
||||
Ps::Lit => Expr::Lit(p.parse(())?),
|
||||
Ps::Let => Expr::Let(p.parse(())?),
|
||||
Ps::For => parse_for(p, ())?,
|
||||
Ps::Const => Expr::Const(p.parse(())?),
|
||||
Ps::Struct => Expr::Struct(p.parse(())?),
|
||||
Ps::Match => Expr::Match(p.parse(())?),
|
||||
@@ -694,11 +821,15 @@ impl<'t> Parse<'t> for Expr {
|
||||
p.consume()
|
||||
.opt(PPrec::Tuple, TKind::Bar)?
|
||||
.unwrap_or(Pat::Tuple(vec![])),
|
||||
p.opt_if((), TKind::Arrow)?.unwrap_or_default(),
|
||||
p.parse(Prec::Body.next())?,
|
||||
))),
|
||||
Ps::Lambda0 => Expr::Fn(Box::new(Fn(
|
||||
None,
|
||||
Pat::Tuple(vec![]),
|
||||
p.consume().opt_if((), TKind::Arrow)?.unwrap_or_default(),
|
||||
p.parse(Prec::Body.next())?,
|
||||
))),
|
||||
Ps::Lambda0 => Expr::Fn(Box::new(Fn(None, Pat::Tuple(vec![]), {
|
||||
p.consume().parse(Prec::Body.next())?
|
||||
}))),
|
||||
Ps::DoubleRef => p.consume().parse(prec.next()).map(|Anno(expr, span)| {
|
||||
Expr::Op(
|
||||
Op::Refer,
|
||||
@@ -724,12 +855,14 @@ impl<'t> Parse<'t> for Expr {
|
||||
Ps::Make => match &head {
|
||||
Expr::Op(Op::Path, _) | Expr::Id(_) | Expr::MetId(_) => {
|
||||
Expr::Make(Box::new(Make(
|
||||
head.anno(span).into(),
|
||||
head.anno(span),
|
||||
p.consume().list(vec![], (), TKind::Comma, TKind::RCurly)?,
|
||||
)))
|
||||
}
|
||||
_ => break,
|
||||
},
|
||||
Ps::Op(Op::Do) => head.and_do(span, p.consume().parse(prec.next())?),
|
||||
Ps::ImplicitDo => head.and_do(span, p.parse(prec.next())?),
|
||||
Ps::Op(Op::Index) => Expr::Op(
|
||||
Op::Index,
|
||||
p.consume()
|
||||
@@ -740,13 +873,11 @@ impl<'t> Parse<'t> for Expr {
|
||||
p.consume()
|
||||
.list(vec![head.anno(span)], 0, TKind::Comma, TKind::RParen)?,
|
||||
),
|
||||
Ps::Op(op @ (Op::Do | Op::Tuple | Op::Dot | Op::Path | Op::LogAnd | Op::LogOr)) => {
|
||||
Expr::Op(
|
||||
op,
|
||||
p.consume()
|
||||
.list_bare(vec![head.anno(span)], prec.next(), kind)?,
|
||||
)
|
||||
}
|
||||
Ps::Op(op @ (Op::Tuple | Op::Dot | Op::Path | Op::LogAnd | Op::LogOr)) => Expr::Op(
|
||||
op,
|
||||
p.consume()
|
||||
.list_bare(vec![head.anno(span)], prec.next(), kind)?,
|
||||
),
|
||||
Ps::Op(op @ Op::Try) => {
|
||||
p.consume();
|
||||
Expr::Op(op, vec![head.anno(span)])
|
||||
|
||||
Reference in New Issue
Block a user