cleanup of run_query
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1,6 +1,6 @@
|
|||||||
[root]
|
[root]
|
||||||
name = "rusty-wam"
|
name = "rusty-wam"
|
||||||
version = "0.6.1"
|
version = "0.6.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lalrpop 0.12.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lalrpop 0.12.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lalrpop-util 0.12.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lalrpop-util 0.12.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rusty-wam"
|
name = "rusty-wam"
|
||||||
version = "0.6.1"
|
version = "0.6.2"
|
||||||
authors = ["Mark Thom"]
|
authors = ["Mark Thom"]
|
||||||
|
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ mod tests {
|
|||||||
match parse_TopLevel(buffer.trim()) {
|
match parse_TopLevel(buffer.trim()) {
|
||||||
Ok(tl) =>
|
Ok(tl) =>
|
||||||
match eval(wam, &tl) {
|
match eval(wam, &tl) {
|
||||||
EvalResult::InitialQuerySuccess(_, _) |
|
EvalSession::InitialQuerySuccess(_, _) |
|
||||||
EvalResult::EntrySuccess |
|
EvalSession::EntrySuccess |
|
||||||
EvalResult::SubsequentQuerySuccess =>
|
EvalSession::SubsequentQuerySuccess =>
|
||||||
true,
|
true,
|
||||||
_ => false
|
_ => false
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -213,6 +213,16 @@ pub enum ControlInstruction {
|
|||||||
Proceed
|
Proceed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ControlInstruction {
|
||||||
|
pub fn is_jump_instr(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
&ControlInstruction::Call(_, _, _) => true,
|
||||||
|
&ControlInstruction::Execute(_, _) => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub enum IndexingInstruction {
|
pub enum IndexingInstruction {
|
||||||
SwitchOnTerm(usize, usize, usize, usize),
|
SwitchOnTerm(usize, usize, usize, usize),
|
||||||
SwitchOnConstant(usize, HashMap<Constant, usize>),
|
SwitchOnConstant(usize, HashMap<Constant, usize>),
|
||||||
@@ -362,7 +372,7 @@ impl HeapCellValue {
|
|||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum CodePtr {
|
pub enum CodePtr {
|
||||||
DirEntry(usize),
|
DirEntry(usize),
|
||||||
TopLevel(usize, usize, usize) // chunk_num, e, offset.
|
TopLevel(usize, usize) // chunk_num, offset.
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd<CodePtr> for CodePtr {
|
impl PartialOrd<CodePtr> for CodePtr {
|
||||||
@@ -370,9 +380,9 @@ impl PartialOrd<CodePtr> for CodePtr {
|
|||||||
match (self, other) {
|
match (self, other) {
|
||||||
(&CodePtr::DirEntry(p1), &CodePtr::DirEntry(ref p2)) =>
|
(&CodePtr::DirEntry(p1), &CodePtr::DirEntry(ref p2)) =>
|
||||||
p1.partial_cmp(p2),
|
p1.partial_cmp(p2),
|
||||||
(&CodePtr::DirEntry(_), &CodePtr::TopLevel(_, _, _)) =>
|
(&CodePtr::DirEntry(_), &CodePtr::TopLevel(_, _)) =>
|
||||||
Some(Ordering::Less),
|
Some(Ordering::Less),
|
||||||
(&CodePtr::TopLevel(_, _, p1), &CodePtr::TopLevel(_, _, ref p2)) =>
|
(&CodePtr::TopLevel(_, p1), &CodePtr::TopLevel(_, ref p2)) =>
|
||||||
p1.partial_cmp(p2),
|
p1.partial_cmp(p2),
|
||||||
_ => Some(Ordering::Greater)
|
_ => Some(Ordering::Greater)
|
||||||
}
|
}
|
||||||
@@ -381,7 +391,7 @@ impl PartialOrd<CodePtr> for CodePtr {
|
|||||||
|
|
||||||
impl Default for CodePtr {
|
impl Default for CodePtr {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
CodePtr::TopLevel(0, 0, 0)
|
CodePtr::TopLevel(0, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,7 +401,7 @@ impl Add<usize> for CodePtr {
|
|||||||
fn add(self, rhs: usize) -> Self::Output {
|
fn add(self, rhs: usize) -> Self::Output {
|
||||||
match self {
|
match self {
|
||||||
CodePtr::DirEntry(p) => CodePtr::DirEntry(p + rhs),
|
CodePtr::DirEntry(p) => CodePtr::DirEntry(p + rhs),
|
||||||
CodePtr::TopLevel(cn, e, p) => CodePtr::TopLevel(cn, e, p + rhs)
|
CodePtr::TopLevel(cn, p) => CodePtr::TopLevel(cn, p + rhs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -400,7 +410,7 @@ impl AddAssign<usize> for CodePtr {
|
|||||||
fn add_assign(&mut self, rhs: usize) {
|
fn add_assign(&mut self, rhs: usize) {
|
||||||
match self {
|
match self {
|
||||||
&mut CodePtr::DirEntry(ref mut p) |
|
&mut CodePtr::DirEntry(ref mut p) |
|
||||||
&mut CodePtr::TopLevel(_, _, ref mut p) => *p += rhs
|
&mut CodePtr::TopLevel(_, ref mut p) => *p += rhs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ pub struct CodeGenerator<'a, TermMarker> {
|
|||||||
var_count: HashMap<&'a Var, usize>
|
var_count: HashMap<&'a Var, usize>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum EvalResult<'a> {
|
pub enum EvalSession<'a> {
|
||||||
EntryFailure,
|
EntryFailure,
|
||||||
EntrySuccess,
|
EntrySuccess,
|
||||||
InitialQuerySuccess(AllocVarDict<'a>, HeapVarDict<'a>),
|
InitialQuerySuccess(AllocVarDict<'a>, HeapVarDict<'a>),
|
||||||
@@ -22,10 +22,10 @@ pub enum EvalResult<'a> {
|
|||||||
SubsequentQuerySuccess,
|
SubsequentQuerySuccess,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> EvalResult<'a> {
|
impl<'a> EvalSession<'a> {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn failed_query(&self) -> bool {
|
pub fn failed_query(&self) -> bool {
|
||||||
if let &EvalResult::QueryFailure = self {
|
if let &EvalSession::QueryFailure = self {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ pub fn read() -> String {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval<'a, 'b: 'a>(wam: &'a mut Machine, tl: &'b TopLevel) -> EvalResult<'b>
|
pub fn eval<'a, 'b: 'a>(wam: &'a mut Machine, tl: &'b TopLevel) -> EvalSession<'b>
|
||||||
{
|
{
|
||||||
match tl {
|
match tl {
|
||||||
&TopLevel::Predicate(ref clauses) => {
|
&TopLevel::Predicate(ref clauses) => {
|
||||||
@@ -267,13 +267,13 @@ pub fn eval<'a, 'b: 'a>(wam: &'a mut Machine, tl: &'b TopLevel) -> EvalResult<'b
|
|||||||
let compiled_pred = cg.compile_predicate(clauses);
|
let compiled_pred = cg.compile_predicate(clauses);
|
||||||
wam.add_predicate(clauses, compiled_pred);
|
wam.add_predicate(clauses, compiled_pred);
|
||||||
|
|
||||||
EvalResult::EntrySuccess
|
EvalSession::EntrySuccess
|
||||||
} else {
|
} else {
|
||||||
let msg = r"Error: predicate is inconsistent.
|
let msg = r"Error: predicate is inconsistent.
|
||||||
Each predicate must have the same name and arity.";
|
Each predicate must have the same name and arity.";
|
||||||
|
|
||||||
println!("{}", msg);
|
println!("{}", msg);
|
||||||
EvalResult::EntryFailure
|
EvalSession::EntryFailure
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
&TopLevel::Fact(ref fact) => {
|
&TopLevel::Fact(ref fact) => {
|
||||||
@@ -282,7 +282,7 @@ Each predicate must have the same name and arity.";
|
|||||||
let compiled_fact = cg.compile_fact(fact);
|
let compiled_fact = cg.compile_fact(fact);
|
||||||
wam.add_fact(fact, compiled_fact);
|
wam.add_fact(fact, compiled_fact);
|
||||||
|
|
||||||
EvalResult::EntrySuccess
|
EvalSession::EntrySuccess
|
||||||
},
|
},
|
||||||
&TopLevel::Rule(ref rule) => {
|
&TopLevel::Rule(ref rule) => {
|
||||||
let mut cg = CodeGenerator::<DebrayAllocator>::new();
|
let mut cg = CodeGenerator::<DebrayAllocator>::new();
|
||||||
@@ -290,7 +290,7 @@ Each predicate must have the same name and arity.";
|
|||||||
let compiled_rule = cg.compile_rule(rule);
|
let compiled_rule = cg.compile_rule(rule);
|
||||||
wam.add_rule(rule, compiled_rule);
|
wam.add_rule(rule, compiled_rule);
|
||||||
|
|
||||||
EvalResult::EntrySuccess
|
EvalSession::EntrySuccess
|
||||||
},
|
},
|
||||||
&TopLevel::Query(ref query) => {
|
&TopLevel::Query(ref query) => {
|
||||||
let mut cg = CodeGenerator::<DebrayAllocator>::new();
|
let mut cg = CodeGenerator::<DebrayAllocator>::new();
|
||||||
@@ -301,9 +301,9 @@ Each predicate must have the same name and arity.";
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print(wam: &mut Machine, result: EvalResult) {
|
pub fn print(wam: &mut Machine, result: EvalSession) {
|
||||||
match result {
|
match result {
|
||||||
EvalResult::InitialQuerySuccess(alloc_locs, mut heap_locs) => {
|
EvalSession::InitialQuerySuccess(alloc_locs, mut heap_locs) => {
|
||||||
println!("yes");
|
println!("yes");
|
||||||
|
|
||||||
if heap_locs.is_empty() {
|
if heap_locs.is_empty() {
|
||||||
@@ -311,7 +311,7 @@ pub fn print(wam: &mut Machine, result: EvalResult) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
'outer: loop {
|
'outer: loop {
|
||||||
let mut result = EvalResult::QueryFailure;
|
let mut result = EvalSession::QueryFailure;
|
||||||
let bindings = wam.heap_view(&heap_locs);
|
let bindings = wam.heap_view(&heap_locs);
|
||||||
|
|
||||||
let stdin = stdin();
|
let stdin = stdin();
|
||||||
@@ -336,7 +336,7 @@ pub fn print(wam: &mut Machine, result: EvalResult) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let &EvalResult::QueryFailure = &result {
|
if let &EvalSession::QueryFailure = &result {
|
||||||
write!(stdout, "no\n\r").unwrap();
|
write!(stdout, "no\n\r").unwrap();
|
||||||
stdout.flush().unwrap();
|
stdout.flush().unwrap();
|
||||||
break;
|
break;
|
||||||
@@ -346,7 +346,7 @@ pub fn print(wam: &mut Machine, result: EvalResult) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
EvalResult::QueryFailure => println!("no"),
|
EvalSession::QueryFailure => println!("no"),
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ impl Index<CodePtr> for Machine {
|
|||||||
|
|
||||||
fn index(&self, ptr: CodePtr) -> &Self::Output {
|
fn index(&self, ptr: CodePtr) -> &Self::Output {
|
||||||
match ptr {
|
match ptr {
|
||||||
CodePtr::TopLevel(_, _, p) => {
|
CodePtr::TopLevel(_, p) => {
|
||||||
match &self.cached_query {
|
match &self.cached_query {
|
||||||
&Some(ref cq) => &cq[p],
|
&Some(ref cq) => &cq[p],
|
||||||
&None => panic!("Out-of-bounds top level index.")
|
&None => panic!("Out-of-bounds top level index.")
|
||||||
@@ -142,13 +142,13 @@ impl Machine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute_instr(&mut self, ptr: CodePtr)
|
fn execute_instr(&mut self)
|
||||||
{
|
{
|
||||||
// can't use self[ptr] or self.index(ptr) to set the value of
|
// can't use self[ptr] or self.index(ptr) to set the value of
|
||||||
// instr! instr is then typed as Line, not &Line. WHY????
|
// instr! instr is then typed as Line, not &Line. WHY????
|
||||||
// This is a compiler bug. Has to be.
|
// This is a compiler bug. Has to be.
|
||||||
let instr = match ptr {
|
let instr = match self.ms.p {
|
||||||
CodePtr::TopLevel(_, _, p) => {
|
CodePtr::TopLevel(_, p) => {
|
||||||
match &self.cached_query {
|
match &self.cached_query {
|
||||||
&Some(ref cq) => &cq[p],
|
&Some(ref cq) => &cq[p],
|
||||||
&None => return
|
&None => return
|
||||||
@@ -203,13 +203,13 @@ impl Machine {
|
|||||||
let b = self.ms.b - 1;
|
let b = self.ms.b - 1;
|
||||||
self.ms.or_stack[b].bp
|
self.ms.or_stack[b].bp
|
||||||
} else {
|
} else {
|
||||||
self.ms.p = CodePtr::TopLevel(0, 0, 0);
|
self.ms.p = CodePtr::TopLevel(0, 0);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.ms.p = p;
|
self.ms.p = p;
|
||||||
|
|
||||||
if let CodePtr::TopLevel(_, _, p) = p {
|
if let CodePtr::TopLevel(_, p) = p {
|
||||||
self.ms.fail = p == 0;
|
self.ms.fail = p == 0;
|
||||||
self.ms.b0 = b0;
|
self.ms.b0 = b0;
|
||||||
|
|
||||||
@@ -219,19 +219,18 @@ impl Machine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn query_stepper<'a>(&mut self, mut ptr: CodePtr)
|
fn query_stepper<'a>(&mut self)
|
||||||
{
|
{
|
||||||
loop
|
loop
|
||||||
{
|
{
|
||||||
self.execute_instr(ptr);
|
self.execute_instr();
|
||||||
|
|
||||||
if self.failed() {
|
if self.failed() {
|
||||||
self.backtrack();
|
self.backtrack();
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.ms.p {
|
match self.ms.p {
|
||||||
CodePtr::DirEntry(p) if p < self.code.len() =>
|
CodePtr::DirEntry(p) if p < self.code.len() => {},
|
||||||
ptr = self.ms.p,
|
|
||||||
_ => break
|
_ => break
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -239,22 +238,22 @@ impl Machine {
|
|||||||
|
|
||||||
fn record_var_places<'a>(&self,
|
fn record_var_places<'a>(&self,
|
||||||
chunk_num: usize,
|
chunk_num: usize,
|
||||||
e: usize,
|
|
||||||
alloc_locs: &AllocVarDict<'a>,
|
alloc_locs: &AllocVarDict<'a>,
|
||||||
heap_locs: &mut HeapVarDict<'a>)
|
heap_locs: &mut HeapVarDict<'a>)
|
||||||
{
|
{
|
||||||
for (var, var_data) in alloc_locs {
|
for (var, var_data) in alloc_locs {
|
||||||
match var_data {
|
match var_data {
|
||||||
&VarData::Perm(_) => {
|
&VarData::Perm(_) => {
|
||||||
|
let e = self.ms.e;
|
||||||
let r = var_data.as_reg_type().reg_num();
|
let r = var_data.as_reg_type().reg_num();
|
||||||
let addr = self.ms.and_stack[e][r].clone();
|
let addr = self.ms.and_stack[e][r].clone();
|
||||||
|
|
||||||
heap_locs.insert(var, addr);
|
heap_locs.insert(var, addr);
|
||||||
},
|
},
|
||||||
&VarData::Temp(cn, _, _) if cn == chunk_num => {
|
&VarData::Temp(cn, _, _) if cn == chunk_num => {
|
||||||
let r = var_data.as_reg_type();
|
let r = var_data.as_reg_type();
|
||||||
let addr = self.ms[r].clone();
|
let addr = self.ms[r].clone();
|
||||||
|
|
||||||
heap_locs.insert(var, addr);
|
heap_locs.insert(var, addr);
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -264,38 +263,30 @@ impl Machine {
|
|||||||
|
|
||||||
fn run_query<'a>(&mut self, alloc_locs: &AllocVarDict<'a>, heap_locs: &mut HeapVarDict<'a>)
|
fn run_query<'a>(&mut self, alloc_locs: &AllocVarDict<'a>, heap_locs: &mut HeapVarDict<'a>)
|
||||||
{
|
{
|
||||||
let end_ptr = CodePtr::TopLevel(0, 0, self.cached_query_size());
|
let end_ptr = CodePtr::TopLevel(0, self.cached_query_size());
|
||||||
let mut tl_ptr = self.ms.p;
|
|
||||||
|
|
||||||
while tl_ptr < end_ptr {
|
while self.ms.p < end_ptr {
|
||||||
self.query_stepper(tl_ptr);
|
if let CodePtr::TopLevel(mut cn, p) = self.ms.p {
|
||||||
|
if let &Line::Control(ref ctrl_instr) = &self[CodePtr::TopLevel(cn, p)] {
|
||||||
tl_ptr = self.ms.p;
|
if ctrl_instr.is_jump_instr() {
|
||||||
|
self.record_var_places(cn, alloc_locs, heap_locs);
|
||||||
if let &mut CodePtr::TopLevel(ref mut cn, ref mut e, p) = &mut tl_ptr {
|
cn += 1;
|
||||||
if p == 0 {
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match &self[CodePtr::TopLevel(*cn, *e, p)] {
|
self.ms.p = CodePtr::TopLevel(cn, p);
|
||||||
&Line::Control(ControlInstruction::Call(_, _, _))
|
|
||||||
| &Line::Control(ControlInstruction::Execute(_, _)) => {
|
|
||||||
self.record_var_places(*cn, *e, alloc_locs, heap_locs);
|
|
||||||
*cn += 1;
|
|
||||||
},
|
|
||||||
&Line::Control(ControlInstruction::Allocate(_)) =>
|
|
||||||
*e = self.ms.e,
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.ms.p = CodePtr::TopLevel(*cn, *e, p);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.query_stepper();
|
||||||
|
|
||||||
|
match self.ms.p {
|
||||||
|
CodePtr::TopLevel(_, p) if p > 0 => {},
|
||||||
|
_ => break
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn submit_query<'a>(&mut self, code: Code, alloc_locs: AllocVarDict<'a>) -> EvalResult<'a>
|
pub fn submit_query<'a>(&mut self, code: Code, alloc_locs: AllocVarDict<'a>) -> EvalSession<'a>
|
||||||
{
|
{
|
||||||
let mut heap_locs = HashMap::new();
|
let mut heap_locs = HashMap::new();
|
||||||
self.cached_query = Some(code);
|
self.cached_query = Some(code);
|
||||||
@@ -303,38 +294,38 @@ impl Machine {
|
|||||||
self.run_query(&alloc_locs, &mut heap_locs);
|
self.run_query(&alloc_locs, &mut heap_locs);
|
||||||
|
|
||||||
if self.failed() {
|
if self.failed() {
|
||||||
EvalResult::QueryFailure
|
EvalSession::QueryFailure
|
||||||
} else {
|
} else {
|
||||||
EvalResult::InitialQuerySuccess(alloc_locs, heap_locs)
|
EvalSession::InitialQuerySuccess(alloc_locs, heap_locs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn continue_query<'a>(&mut self,
|
pub fn continue_query<'a>(&mut self,
|
||||||
alloc_locs: &AllocVarDict<'a>,
|
alloc_locs: &AllocVarDict<'a>,
|
||||||
heap_locs: &mut HeapVarDict<'a>)
|
heap_locs: &mut HeapVarDict<'a>)
|
||||||
-> EvalResult
|
-> EvalSession
|
||||||
{
|
{
|
||||||
if !self.or_stack_is_empty() {
|
if !self.or_stack_is_empty() {
|
||||||
if self.ms.b == 0 {
|
if self.ms.b == 0 {
|
||||||
return EvalResult::QueryFailure;
|
return EvalSession::QueryFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
let b = self.ms.b - 1;
|
let b = self.ms.b - 1;
|
||||||
self.ms.p = self.ms.or_stack[b].bp;
|
self.ms.p = self.ms.or_stack[b].bp;
|
||||||
|
|
||||||
if let CodePtr::TopLevel(_, _, 0) = self.ms.p {
|
if let CodePtr::TopLevel(_, 0) = self.ms.p {
|
||||||
return EvalResult::QueryFailure
|
return EvalSession::QueryFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
self.run_query(alloc_locs, heap_locs);
|
self.run_query(alloc_locs, heap_locs);
|
||||||
|
|
||||||
if self.failed() {
|
if self.failed() {
|
||||||
EvalResult::QueryFailure
|
EvalSession::QueryFailure
|
||||||
} else {
|
} else {
|
||||||
EvalResult::SubsequentQuerySuccess
|
EvalSession::SubsequentQuerySuccess
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
EvalResult::QueryFailure
|
EvalSession::QueryFailure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user