Errors now use Location instead of line numbers
This commit is contained in:
@@ -2,12 +2,42 @@ use std::fmt::{Display, Formatter};
|
|||||||
|
|
||||||
use vcore::opcodes::InvalidMnemonic;
|
use vcore::opcodes::InvalidMnemonic;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub struct Location {
|
||||||
|
pub line_num: usize,
|
||||||
|
pub file: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum ParseError {
|
pub enum ParseError {
|
||||||
LineParseFailure,
|
LineParseFailure,
|
||||||
InvalidInstruction(String),
|
InvalidInstruction(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Location {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}:{}", self.file, self.line_num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Location {
|
||||||
|
fn default() -> Self {
|
||||||
|
Location {
|
||||||
|
line_num: 0,
|
||||||
|
file: "<none>".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<usize> for Location {
|
||||||
|
fn from(line: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
line_num: line,
|
||||||
|
file: "<none>".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> From<InvalidMnemonic<'a>> for ParseError {
|
impl<'a> From<InvalidMnemonic<'a>> for ParseError {
|
||||||
fn from(err: InvalidMnemonic<'a>) -> Self {
|
fn from(err: InvalidMnemonic<'a>) -> Self {
|
||||||
Self::InvalidInstruction(err.0.into())
|
Self::InvalidInstruction(err.0.into())
|
||||||
@@ -26,43 +56,43 @@ impl Display for ParseError {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum AssembleError {
|
pub enum AssembleError {
|
||||||
ParseError(usize, ParseError),
|
ParseError(Location, ParseError),
|
||||||
EquResolveError(usize, String, EvalError),
|
EquResolveError(Location, String, EvalError),
|
||||||
EquDuplicateError(usize, String),
|
EquDuplicateError(Location, String),
|
||||||
OrgResolveError(usize, EvalError),
|
OrgResolveError(Location, EvalError),
|
||||||
ArgError(usize, EvalError),
|
ArgError(Location, EvalError),
|
||||||
NoCode,
|
NoCode,
|
||||||
IncludeError(usize, String),
|
IncludeError(Location, String),
|
||||||
FileError(String),
|
FileError(String),
|
||||||
MacroError(usize),
|
MacroError(Location),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for AssembleError {
|
impl Display for AssembleError {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
AssembleError::EquResolveError(line, name, err) => {
|
AssembleError::EquResolveError(line, name, err) => {
|
||||||
write!(f, "Cannot resolve .equ {} on line {}: {}", name, line, err)
|
write!(f, "Cannot resolve .equ {} at {}: {}", name, line, err)
|
||||||
}
|
}
|
||||||
AssembleError::EquDuplicateError(line, name) => {
|
AssembleError::EquDuplicateError(line, name) => {
|
||||||
write!(f, "Duplicate .equ {} on line {}", name, line)
|
write!(f, "Duplicate .equ {} at {}", name, line)
|
||||||
}
|
}
|
||||||
AssembleError::OrgResolveError(line, err) => {
|
AssembleError::OrgResolveError(line, err) => {
|
||||||
write!(f, "Cannot resolve .org on line {}: {}", line, err)
|
write!(f, "Cannot resolve .org at {}: {}", line, err)
|
||||||
}
|
}
|
||||||
AssembleError::ArgError(line, err) => {
|
AssembleError::ArgError(line, err) => {
|
||||||
write!(f, "Cannot calculate argument on line {}: {}", line, err)
|
write!(f, "Cannot calculate argument at {}: {}", line, err)
|
||||||
}
|
}
|
||||||
AssembleError::NoCode => {
|
AssembleError::NoCode => {
|
||||||
write!(f, "No output would be generated by this code")
|
write!(f, "No output would be generated by this code")
|
||||||
}
|
}
|
||||||
AssembleError::ParseError(line, err) => {
|
AssembleError::ParseError(line, err) => {
|
||||||
write!(f, "Parse error on line {}: {}", line, err)
|
write!(f, "Parse error at {}: {}", line, err)
|
||||||
}
|
}
|
||||||
AssembleError::IncludeError(line, file) => {
|
AssembleError::IncludeError(line, file) => {
|
||||||
write!(f, "Cannot read \"{}\" on line {}", file, line)
|
write!(f, "Cannot read \"{}\" at {}", file, line)
|
||||||
}
|
}
|
||||||
AssembleError::MacroError(line) => {
|
AssembleError::MacroError(line) => {
|
||||||
write!(f, "Malformed macro control structure on line {}", line)
|
write!(f, "Malformed macro control structure at {}", line)
|
||||||
}
|
}
|
||||||
AssembleError::FileError(file) => {
|
AssembleError::FileError(file) => {
|
||||||
write!(f, "File read error in {}", file)
|
write!(f, "File read error in {}", file)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::ast::{Label, Scope, VASMLine};
|
use crate::ast::{Label, Scope, VASMLine};
|
||||||
use crate::parse_error::AssembleError;
|
use crate::parse_error::{AssembleError, Location};
|
||||||
use crate::vasm_evaluator::eval;
|
use crate::vasm_evaluator::eval;
|
||||||
use crate::vasm_preprocessor::{Line, LineSource};
|
use crate::vasm_preprocessor::{Line, LineSource};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
@@ -13,11 +13,15 @@ fn solve_equs(lines: &[Line]) -> Result<Scope, AssembleError> {
|
|||||||
let line_nums: BTreeMap<usize, i32> = BTreeMap::new();
|
let line_nums: BTreeMap<usize, i32> = BTreeMap::new();
|
||||||
for (line_num, line) in lines.iter().enumerate() {
|
for (line_num, line) in lines.iter().enumerate() {
|
||||||
if let VASMLine::Equ(Label(name), expr) = &line.line {
|
if let VASMLine::Equ(Label(name), expr) = &line.line {
|
||||||
let value = eval(expr, line_num, &line_nums, &scope)
|
let value = eval(expr, line_num, &line_nums, &scope).map_err(|e| {
|
||||||
.map_err(|e| AssembleError::EquResolveError(line_num, name.to_string(), e))?;
|
AssembleError::EquResolveError(line.location.clone(), name.to_string(), e)
|
||||||
|
})?;
|
||||||
|
|
||||||
if let Some(_old_value) = scope.insert(name.clone(), value) {
|
if let Some(_old_value) = scope.insert(name.clone(), value) {
|
||||||
return Err(AssembleError::EquDuplicateError(line_num, name.to_string()));
|
return Err(AssembleError::EquDuplicateError(
|
||||||
|
line.location.clone(),
|
||||||
|
name.to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,7 +105,7 @@ fn place_labels(
|
|||||||
for (line_num, line) in lines.iter().enumerate() {
|
for (line_num, line) in lines.iter().enumerate() {
|
||||||
if let VASMLine::Org(_, expr) = &line.line {
|
if let VASMLine::Org(_, expr) = &line.line {
|
||||||
address = eval(expr, line_num, &addresses, &scope)
|
address = eval(expr, line_num, &addresses, &scope)
|
||||||
.map_err(|err| AssembleError::OrgResolveError(line_num, err))?;
|
.map_err(|err| AssembleError::OrgResolveError(line.location.clone(), err))?;
|
||||||
addresses.insert(line_num, address);
|
addresses.insert(line_num, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,9 +171,9 @@ pub fn assemble_snippet<T: IntoIterator<Item = String>>(
|
|||||||
lines: T,
|
lines: T,
|
||||||
) -> Result<Vec<u8>, AssembleError> {
|
) -> Result<Vec<u8>, AssembleError> {
|
||||||
let line_results: Vec<Result<Line, AssembleError>> =
|
let line_results: Vec<Result<Line, AssembleError>> =
|
||||||
LineSource::new("_snippet", lines, |_file| {
|
LineSource::new("<none>", lines, |_file| {
|
||||||
Err(AssembleError::IncludeError(
|
Err(AssembleError::IncludeError(
|
||||||
0,
|
Location::default(),
|
||||||
"Including is not supported in assembling snippets".to_string(),
|
"Including is not supported in assembling snippets".to_string(),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
@@ -239,7 +243,7 @@ fn generate_code<T: IntoIterator<Item = Line>>(lines: T) -> Result<Vec<u8>, Asse
|
|||||||
}
|
}
|
||||||
VASMLine::Instruction(_, opcode, Some(arg)) => {
|
VASMLine::Instruction(_, opcode, Some(arg)) => {
|
||||||
let arg = eval(arg, line_num, &line_addresses, &scope)
|
let arg = eval(arg, line_num, &line_addresses, &scope)
|
||||||
.map_err(|err| AssembleError::ArgError(line_num, err))?;
|
.map_err(|err| AssembleError::ArgError(line.location.clone(), err))?;
|
||||||
let len = line_lengths[&line_num] - 1;
|
let len = line_lengths[&line_num] - 1;
|
||||||
let instr = (u8::from(*opcode) << 2) + len as u8;
|
let instr = (u8::from(*opcode) << 2) + len as u8;
|
||||||
code[current_addr - start] = instr;
|
code[current_addr - start] = instr;
|
||||||
@@ -255,7 +259,7 @@ fn generate_code<T: IntoIterator<Item = Line>>(lines: T) -> Result<Vec<u8>, Asse
|
|||||||
}
|
}
|
||||||
VASMLine::Db(_, arg) => {
|
VASMLine::Db(_, arg) => {
|
||||||
let arg = eval(arg, line_num, &line_addresses, &scope)
|
let arg = eval(arg, line_num, &line_addresses, &scope)
|
||||||
.map_err(|err| AssembleError::ArgError(line_num, err))?;
|
.map_err(|err| AssembleError::ArgError(line.location.clone(), err))?;
|
||||||
poke_word(&mut code, current_addr - start, arg);
|
poke_word(&mut code, current_addr - start, arg);
|
||||||
current_addr += 3;
|
current_addr += 3;
|
||||||
}
|
}
|
||||||
@@ -280,7 +284,6 @@ fn generate_code<T: IntoIterator<Item = Line>>(lines: T) -> Result<Vec<u8>, Asse
|
|||||||
mod test {
|
mod test {
|
||||||
use super::AssembleError::*;
|
use super::AssembleError::*;
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::ast::VASMLine;
|
|
||||||
use crate::parse_error;
|
use crate::parse_error;
|
||||||
use crate::parse_error::EvalError::*;
|
use crate::parse_error::EvalError::*;
|
||||||
use crate::vasm_parser::parse_vasm_line;
|
use crate::vasm_parser::parse_vasm_line;
|
||||||
@@ -288,7 +291,11 @@ mod test {
|
|||||||
fn parse<'a, T: IntoIterator<Item = &'a str>>(lines: T) -> Vec<Line> {
|
fn parse<'a, T: IntoIterator<Item = &'a str>>(lines: T) -> Vec<Line> {
|
||||||
lines
|
lines
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|line| parse_vasm_line(line).unwrap().into())
|
.enumerate()
|
||||||
|
.map(|(line_num, line)| Line {
|
||||||
|
line: parse_vasm_line(line).unwrap(),
|
||||||
|
location: line_num.into(),
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +343,7 @@ mod test {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
solve_equs(&parse(["blah: .equ 5", "foo: .equ banana"])),
|
solve_equs(&parse(["blah: .equ 5", "foo: .equ banana"])),
|
||||||
Err(EquResolveError(
|
Err(EquResolveError(
|
||||||
1,
|
1.into(),
|
||||||
"foo".into(),
|
"foo".into(),
|
||||||
MissingLabel("banana".into())
|
MissingLabel("banana".into())
|
||||||
))
|
))
|
||||||
@@ -344,14 +351,14 @@ mod test {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
solve_equs(&parse(["blah: .equ foo+3", "foo: .equ 7"])),
|
solve_equs(&parse(["blah: .equ foo+3", "foo: .equ 7"])),
|
||||||
Err(EquResolveError(
|
Err(EquResolveError(
|
||||||
0,
|
0.into(),
|
||||||
"blah".into(),
|
"blah".into(),
|
||||||
MissingLabel("foo".into())
|
MissingLabel("foo".into())
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
solve_equs(&parse(["blah: .equ 3", "blah: .equ 7"])),
|
solve_equs(&parse(["blah: .equ 3", "blah: .equ 7"])),
|
||||||
Err(EquDuplicateError(1, "blah".into()))
|
Err(EquDuplicateError(1.into(), "blah".into()))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,11 +450,11 @@ mod test {
|
|||||||
fn test_unresolvable_orgs() {
|
fn test_unresolvable_orgs() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
place_labels_pass([".org 0xffffff - blah"]),
|
place_labels_pass([".org 0xffffff - blah"]),
|
||||||
Err(OrgResolveError(0, MissingLabel("blah".into())))
|
Err(OrgResolveError(0.into(), MissingLabel("blah".into())))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
place_labels_pass(["blah: .org blah"]),
|
place_labels_pass(["blah: .org blah"]),
|
||||||
Err(OrgResolveError(0, MissingLabel("blah".into())))
|
Err(OrgResolveError(0.into(), MissingLabel("blah".into())))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,7 +501,7 @@ mod test {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
assemble_snippet(["apple"].map(String::from)),
|
assemble_snippet(["apple"].map(String::from)),
|
||||||
Err(ParseError(
|
Err(ParseError(
|
||||||
1,
|
1.into(),
|
||||||
parse_error::ParseError::InvalidInstruction("apple".into())
|
parse_error::ParseError::InvalidInstruction("apple".into())
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::ast::{Macro, VASMLine};
|
use crate::ast::{Macro, VASMLine};
|
||||||
use crate::parse_error::AssembleError;
|
use crate::parse_error::{AssembleError, Location};
|
||||||
use crate::vasm_parser::parse_vasm_line;
|
use crate::vasm_parser::parse_vasm_line;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::iter::Enumerate;
|
use std::iter::Enumerate;
|
||||||
@@ -7,8 +7,7 @@ use std::iter::Enumerate;
|
|||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct Line {
|
pub struct Line {
|
||||||
pub line: VASMLine,
|
pub line: VASMLine,
|
||||||
pub line_num: usize,
|
pub location: Location,
|
||||||
pub file: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
@@ -48,8 +47,10 @@ impl From<VASMLine> for Line {
|
|||||||
fn from(other: VASMLine) -> Self {
|
fn from(other: VASMLine) -> Self {
|
||||||
Line {
|
Line {
|
||||||
line: other,
|
line: other,
|
||||||
|
location: Location {
|
||||||
line_num: 0,
|
line_num: 0,
|
||||||
file: "<none>".to_string(),
|
file: "<none>".to_string(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +77,7 @@ impl<T: IntoIterator<Item = String>, F: Fn(String) -> Result<T, AssembleError>>
|
|||||||
// Try and parse it
|
// Try and parse it
|
||||||
return match parse_vasm_line(line.as_str()) {
|
return match parse_vasm_line(line.as_str()) {
|
||||||
// We failed to parse it
|
// We failed to parse it
|
||||||
Err(err) => Some(Err(AssembleError::ParseError(self.current_line, err))),
|
Err(err) => Some(Err(AssembleError::ParseError(self.current_location(), err))),
|
||||||
|
|
||||||
// It's a macro, so do it and then try again
|
// It's a macro, so do it and then try again
|
||||||
Ok(VASMLine::Macro(mac)) => {
|
Ok(VASMLine::Macro(mac)) => {
|
||||||
@@ -92,8 +93,10 @@ impl<T: IntoIterator<Item = String>, F: Fn(String) -> Result<T, AssembleError>>
|
|||||||
|
|
||||||
Ok(normal_line) => Some(Ok(Line {
|
Ok(normal_line) => Some(Ok(Line {
|
||||||
line: normal_line,
|
line: normal_line,
|
||||||
|
location: Location {
|
||||||
line_num: self.current_line,
|
line_num: self.current_line,
|
||||||
file: self.filename_stack.last().unwrap().clone(),
|
file: self.filename_stack.last().unwrap().clone(),
|
||||||
|
},
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -122,8 +125,10 @@ impl<T: IntoIterator<Item = String>, F: Fn(String) -> Result<T, AssembleError>>
|
|||||||
fn emit(&mut self, line: String) {
|
fn emit(&mut self, line: String) {
|
||||||
self.generated_lines.push_back(Line {
|
self.generated_lines.push_back(Line {
|
||||||
line: parse_vasm_line(line.as_str()).unwrap(),
|
line: parse_vasm_line(line.as_str()).unwrap(),
|
||||||
|
location: Location {
|
||||||
line_num: self.current_line,
|
line_num: self.current_line,
|
||||||
file: self.filename_stack.last().unwrap().clone(),
|
file: self.filename_stack.last().unwrap().clone(),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +137,13 @@ impl<T: IntoIterator<Item = String>, F: Fn(String) -> Result<T, AssembleError>>
|
|||||||
format!("__gensym_{}", self.current_sym)
|
format!("__gensym_{}", self.current_sym)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn current_location(&self) -> Location {
|
||||||
|
Location {
|
||||||
|
line_num: self.current_line,
|
||||||
|
file: self.filename_stack.last().unwrap().clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_macro(&mut self, mac: Macro) -> Option<AssembleError> {
|
fn handle_macro(&mut self, mac: Macro) -> Option<AssembleError> {
|
||||||
match mac {
|
match mac {
|
||||||
Macro::Include(file) => {
|
Macro::Include(file) => {
|
||||||
@@ -166,7 +178,7 @@ impl<T: IntoIterator<Item = String>, F: Fn(String) -> Result<T, AssembleError>>
|
|||||||
self.emit(format!("jmpr @{}", new_end));
|
self.emit(format!("jmpr @{}", new_end));
|
||||||
self.emit(format!("{}:", old_end));
|
self.emit(format!("{}:", old_end));
|
||||||
} else {
|
} else {
|
||||||
return Some(AssembleError::MacroError(self.current_line));
|
return Some(AssembleError::MacroError(self.current_location()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +200,7 @@ impl<T: IntoIterator<Item = String>, F: Fn(String) -> Result<T, AssembleError>>
|
|||||||
};
|
};
|
||||||
self.emit(format!("{} @{}", instr, after));
|
self.emit(format!("{} @{}", instr, after));
|
||||||
} else {
|
} else {
|
||||||
return Some(AssembleError::MacroError(self.current_line));
|
return Some(AssembleError::MacroError(self.current_location()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +210,7 @@ impl<T: IntoIterator<Item = String>, F: Fn(String) -> Result<T, AssembleError>>
|
|||||||
self.emit(format!("jmpr @{}", start));
|
self.emit(format!("jmpr @{}", start));
|
||||||
self.emit(format!("{}:", after));
|
self.emit(format!("{}:", after));
|
||||||
}
|
}
|
||||||
_ => return Some(AssembleError::MacroError(self.current_line)),
|
_ => return Some(AssembleError::MacroError(self.current_location())),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
@@ -229,9 +241,11 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
src.next(),
|
src.next(),
|
||||||
Some(Ok(Line {
|
Some(Ok(Line {
|
||||||
|
line: VASMLine::Instruction(None, Add, None),
|
||||||
|
location: Location {
|
||||||
line_num: 1,
|
line_num: 1,
|
||||||
file: "blah".to_string(),
|
file: "blah".to_string(),
|
||||||
line: VASMLine::Instruction(None, Add, None)
|
}
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
assert_eq!(src.next(), None);
|
assert_eq!(src.next(), None);
|
||||||
@@ -246,14 +260,18 @@ mod tests {
|
|||||||
src.collect::<Vec<Result<Line, AssembleError>>>(),
|
src.collect::<Vec<Result<Line, AssembleError>>>(),
|
||||||
vec![
|
vec![
|
||||||
Ok(Line {
|
Ok(Line {
|
||||||
|
line: VASMLine::Instruction(None, Sub, None),
|
||||||
|
location: Location {
|
||||||
line_num: 1,
|
line_num: 1,
|
||||||
file: "foo".to_string(),
|
file: "foo".to_string(),
|
||||||
line: VASMLine::Instruction(None, Sub, None)
|
}
|
||||||
}),
|
}),
|
||||||
Ok(Line {
|
Ok(Line {
|
||||||
|
line: VASMLine::Instruction(None, Add, None),
|
||||||
|
location: Location {
|
||||||
line_num: 2,
|
line_num: 2,
|
||||||
file: "blah".to_string(),
|
file: "blah".to_string(),
|
||||||
line: VASMLine::Instruction(None, Add, None)
|
}
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user