mirror of
https://github.com/urbit/ares.git
synced 2024-12-23 13:25:03 +03:00
commit
dd8f481824
@ -349,6 +349,19 @@ pub const URBIT_HOT_STATE: &[HotEntry] = &[
|
||||
jet_pfix,
|
||||
),
|
||||
//
|
||||
(
|
||||
&[
|
||||
K_139,
|
||||
Left(b"one"),
|
||||
Left(b"two"),
|
||||
Left(b"tri"),
|
||||
Left(b"qua"),
|
||||
Left(b"plug"),
|
||||
],
|
||||
1,
|
||||
jet_plug,
|
||||
),
|
||||
//
|
||||
(
|
||||
&[
|
||||
K_139,
|
||||
@ -375,6 +388,34 @@ pub const URBIT_HOT_STATE: &[HotEntry] = &[
|
||||
jet_sfix,
|
||||
),
|
||||
//
|
||||
(
|
||||
&[
|
||||
K_139,
|
||||
Left(b"one"),
|
||||
Left(b"two"),
|
||||
Left(b"tri"),
|
||||
Left(b"qua"),
|
||||
Left(b"cold"),
|
||||
Left(b"fun"),
|
||||
],
|
||||
1,
|
||||
jet_cold,
|
||||
),
|
||||
//
|
||||
(
|
||||
&[
|
||||
K_139,
|
||||
Left(b"one"),
|
||||
Left(b"two"),
|
||||
Left(b"tri"),
|
||||
Left(b"qua"),
|
||||
Left(b"cook"),
|
||||
Left(b"fun"),
|
||||
],
|
||||
1,
|
||||
jet_cook,
|
||||
),
|
||||
//
|
||||
(
|
||||
&[
|
||||
K_139,
|
||||
@ -431,6 +472,20 @@ pub const URBIT_HOT_STATE: &[HotEntry] = &[
|
||||
jet_mask,
|
||||
),
|
||||
//
|
||||
(
|
||||
&[
|
||||
K_139,
|
||||
Left(b"one"),
|
||||
Left(b"two"),
|
||||
Left(b"tri"),
|
||||
Left(b"qua"),
|
||||
Left(b"shim"),
|
||||
Left(b"fun"),
|
||||
],
|
||||
1,
|
||||
jet_shim,
|
||||
),
|
||||
//
|
||||
(
|
||||
&[
|
||||
K_139,
|
||||
@ -445,6 +500,34 @@ pub const URBIT_HOT_STATE: &[HotEntry] = &[
|
||||
jet_stag,
|
||||
),
|
||||
//
|
||||
(
|
||||
&[
|
||||
K_139,
|
||||
Left(b"one"),
|
||||
Left(b"two"),
|
||||
Left(b"tri"),
|
||||
Left(b"qua"),
|
||||
Left(b"stew"),
|
||||
Left(b"fun"),
|
||||
],
|
||||
1,
|
||||
jet_stew,
|
||||
),
|
||||
//
|
||||
(
|
||||
&[
|
||||
K_139,
|
||||
Left(b"one"),
|
||||
Left(b"two"),
|
||||
Left(b"tri"),
|
||||
Left(b"qua"),
|
||||
Left(b"stir"),
|
||||
Left(b"fun"),
|
||||
],
|
||||
1,
|
||||
jet_stir,
|
||||
),
|
||||
//
|
||||
(
|
||||
&[
|
||||
K_139,
|
||||
|
@ -15,7 +15,7 @@
|
||||
use crate::interpreter::{Context, Error};
|
||||
use crate::jets::util::*;
|
||||
use crate::jets::{JetErr, Result};
|
||||
use crate::noun::{Atom, DirectAtom, IndirectAtom, Noun, D, DIRECT_MAX, NO, T, YES};
|
||||
use crate::noun::{Atom, DirectAtom, IndirectAtom, Noun, D, DIRECT_MAX, T};
|
||||
use either::{Left, Right};
|
||||
use ibig::ops::DivRem;
|
||||
use ibig::UBig;
|
||||
@ -120,21 +120,7 @@ pub fn jet_gte(context: &mut Context, subject: Noun) -> Result {
|
||||
let a = slot(arg, 2)?.as_atom()?;
|
||||
let b = slot(arg, 3)?.as_atom()?;
|
||||
|
||||
Ok(if let (Ok(a), Ok(b)) = (a.as_direct(), b.as_direct()) {
|
||||
if a.data() >= b.data() {
|
||||
YES
|
||||
} else {
|
||||
NO
|
||||
}
|
||||
} else if a.bit_size() > b.bit_size() {
|
||||
YES
|
||||
} else if a.bit_size() < b.bit_size() {
|
||||
NO
|
||||
} else if a.as_ubig(stack) >= b.as_ubig(stack) {
|
||||
YES
|
||||
} else {
|
||||
NO
|
||||
})
|
||||
Ok(util::gte(stack, a, b))
|
||||
}
|
||||
|
||||
pub fn jet_gth(context: &mut Context, subject: Noun) -> Result {
|
||||
@ -143,21 +129,7 @@ pub fn jet_gth(context: &mut Context, subject: Noun) -> Result {
|
||||
let a = slot(arg, 2)?.as_atom()?;
|
||||
let b = slot(arg, 3)?.as_atom()?;
|
||||
|
||||
Ok(if let (Ok(a), Ok(b)) = (a.as_direct(), b.as_direct()) {
|
||||
if a.data() > b.data() {
|
||||
YES
|
||||
} else {
|
||||
NO
|
||||
}
|
||||
} else if a.bit_size() > b.bit_size() {
|
||||
YES
|
||||
} else if a.bit_size() < b.bit_size() {
|
||||
NO
|
||||
} else if a.as_ubig(stack) > b.as_ubig(stack) {
|
||||
YES
|
||||
} else {
|
||||
NO
|
||||
})
|
||||
Ok(util::gth(stack, a, b))
|
||||
}
|
||||
|
||||
pub fn jet_lte(context: &mut Context, subject: Noun) -> Result {
|
||||
@ -166,21 +138,7 @@ pub fn jet_lte(context: &mut Context, subject: Noun) -> Result {
|
||||
let a = slot(arg, 2)?.as_atom()?;
|
||||
let b = slot(arg, 3)?.as_atom()?;
|
||||
|
||||
Ok(if let (Ok(a), Ok(b)) = (a.as_direct(), b.as_direct()) {
|
||||
if a.data() <= b.data() {
|
||||
YES
|
||||
} else {
|
||||
NO
|
||||
}
|
||||
} else if a.bit_size() < b.bit_size() {
|
||||
YES
|
||||
} else if a.bit_size() > b.bit_size() {
|
||||
NO
|
||||
} else if a.as_ubig(stack) <= b.as_ubig(stack) {
|
||||
YES
|
||||
} else {
|
||||
NO
|
||||
})
|
||||
Ok(util::lte(stack, a, b))
|
||||
}
|
||||
|
||||
pub fn jet_lth(context: &mut Context, subject: Noun) -> Result {
|
||||
@ -307,19 +265,88 @@ pub mod util {
|
||||
}
|
||||
}
|
||||
|
||||
/// Greater than or equal to (boolean)
|
||||
pub fn gte_b(stack: &mut NockStack, a: Atom, b: Atom) -> bool {
|
||||
if let (Ok(a), Ok(b)) = (a.as_direct(), b.as_direct()) {
|
||||
a.data() >= b.data()
|
||||
} else if a.bit_size() > b.bit_size() {
|
||||
true
|
||||
} else if a.bit_size() < b.bit_size() {
|
||||
false
|
||||
} else {
|
||||
a.as_ubig(stack) >= b.as_ubig(stack)
|
||||
}
|
||||
}
|
||||
|
||||
/// Greater than or equal to
|
||||
pub fn gte(stack: &mut NockStack, a: Atom, b: Atom) -> Noun {
|
||||
if gte_b(stack, a, b) {
|
||||
YES
|
||||
} else {
|
||||
NO
|
||||
}
|
||||
}
|
||||
|
||||
/// Greater than (boolean)
|
||||
pub fn gth_b(stack: &mut NockStack, a: Atom, b: Atom) -> bool {
|
||||
if let (Ok(a), Ok(b)) = (a.as_direct(), b.as_direct()) {
|
||||
a.data() > b.data()
|
||||
} else if a.bit_size() > b.bit_size() {
|
||||
true
|
||||
} else if a.bit_size() < b.bit_size() {
|
||||
false
|
||||
} else {
|
||||
a.as_ubig(stack) > b.as_ubig(stack)
|
||||
}
|
||||
}
|
||||
|
||||
/// Greater than
|
||||
pub fn gth(stack: &mut NockStack, a: Atom, b: Atom) -> Noun {
|
||||
if gth_b(stack, a, b) {
|
||||
YES
|
||||
} else {
|
||||
NO
|
||||
}
|
||||
}
|
||||
|
||||
/// Less than or equal to (boolean)
|
||||
pub fn lte_b(stack: &mut NockStack, a: Atom, b: Atom) -> bool {
|
||||
if let (Ok(a), Ok(b)) = (a.as_direct(), b.as_direct()) {
|
||||
a.data() <= b.data()
|
||||
} else if a.bit_size() < b.bit_size() {
|
||||
true
|
||||
} else if a.bit_size() > b.bit_size() {
|
||||
false
|
||||
} else {
|
||||
a.as_ubig(stack) <= b.as_ubig(stack)
|
||||
}
|
||||
}
|
||||
|
||||
/// Less than or equal to
|
||||
pub fn lte(stack: &mut NockStack, a: Atom, b: Atom) -> Noun {
|
||||
if lte_b(stack, a, b) {
|
||||
YES
|
||||
} else {
|
||||
NO
|
||||
}
|
||||
}
|
||||
|
||||
/// Less than (boolean)
|
||||
pub fn lth_b(stack: &mut NockStack, a: Atom, b: Atom) -> bool {
|
||||
if let (Ok(a), Ok(b)) = (a.as_direct(), b.as_direct()) {
|
||||
a.data() < b.data()
|
||||
} else if a.bit_size() > b.bit_size() {
|
||||
false
|
||||
} else if a.bit_size() < b.bit_size() {
|
||||
true
|
||||
} else {
|
||||
a.as_ubig(stack) < b.as_ubig(stack)
|
||||
}
|
||||
}
|
||||
|
||||
/// Less than
|
||||
pub fn lth(stack: &mut NockStack, a: Atom, b: Atom) -> Noun {
|
||||
if let (Ok(a), Ok(b)) = (a.as_direct(), b.as_direct()) {
|
||||
if a.data() < b.data() {
|
||||
YES
|
||||
} else {
|
||||
NO
|
||||
}
|
||||
} else if a.bit_size() < b.bit_size() {
|
||||
YES
|
||||
} else if a.bit_size() > b.bit_size() {
|
||||
NO
|
||||
} else if a.as_ubig(stack) < b.as_ubig(stack) {
|
||||
if lth_b(stack, a, b) {
|
||||
YES
|
||||
} else {
|
||||
NO
|
||||
|
@ -1,9 +1,11 @@
|
||||
/** Parsing jets
|
||||
*/
|
||||
use crate::interpreter::Context;
|
||||
use crate::interpreter::{Context, Error};
|
||||
use crate::jets::math::util::{gte_b, lte_b, lth_b};
|
||||
use crate::jets::util::{kick, slam, slot};
|
||||
use crate::jets::Result;
|
||||
use crate::jets::{JetErr, Result};
|
||||
use crate::noun::{Noun, D, T};
|
||||
use either::{Left, Right};
|
||||
|
||||
crate::gdb!();
|
||||
|
||||
@ -177,6 +179,38 @@ pub fn jet_pfix(context: &mut Context, subject: Noun) -> Result {
|
||||
Ok(T(&mut context.stack, &[arg, q_yit]))
|
||||
}
|
||||
|
||||
pub fn jet_plug(context: &mut Context, subject: Noun) -> Result {
|
||||
let vex = slot(subject, 12)?.as_cell()?;
|
||||
let sab = slot(subject, 13)?;
|
||||
let p_vex = vex.head();
|
||||
let q_vex = vex.tail();
|
||||
|
||||
if unsafe { q_vex.raw_equals(D(0)) } {
|
||||
Ok(vex.as_noun())
|
||||
} else {
|
||||
let uq_vex = q_vex.as_cell()?.tail().as_cell()?;
|
||||
let puq_vex = uq_vex.head();
|
||||
let quq_vex = uq_vex.tail();
|
||||
|
||||
let yit = slam(context, sab, quq_vex)?.as_cell()?;
|
||||
let p_yit = yit.head();
|
||||
let q_yit = yit.tail();
|
||||
|
||||
let yur = util::last(p_vex, p_yit)?;
|
||||
|
||||
if unsafe { q_yit.raw_equals(D(0)) } {
|
||||
Ok(T(&mut context.stack, &[yur, D(0)]))
|
||||
} else {
|
||||
let uq_yit = q_yit.as_cell()?.tail().as_cell()?;
|
||||
let puq_yit = uq_yit.head();
|
||||
let quq_yit = uq_yit.tail();
|
||||
|
||||
let inner = T(&mut context.stack, &[puq_vex, puq_yit]);
|
||||
Ok(T(&mut context.stack, &[yur, D(0), inner, quq_yit]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn jet_pose(context: &mut Context, subject: Noun) -> Result {
|
||||
let vex = slot(subject, 12)?.as_cell()?;
|
||||
let sab = slot(subject, 13)?;
|
||||
@ -229,6 +263,47 @@ pub fn jet_sfix(context: &mut Context, subject: Noun) -> Result {
|
||||
// Rule Builders
|
||||
//
|
||||
|
||||
pub fn jet_cold(context: &mut Context, subject: Noun) -> Result {
|
||||
let tub = slot(subject, 6)?;
|
||||
let van = slot(subject, 7)?;
|
||||
let cus = slot(van, 12)?;
|
||||
let sef = slot(van, 13)?;
|
||||
|
||||
let vex = slam(context, sef, tub)?.as_cell()?;
|
||||
let p_vex = vex.head();
|
||||
let q_vex = vex.tail();
|
||||
|
||||
if unsafe { q_vex.raw_equals(D(0)) } {
|
||||
Ok(vex.as_noun())
|
||||
} else {
|
||||
let quq_vex = q_vex.as_cell()?.tail().as_cell()?.tail();
|
||||
|
||||
Ok(T(&mut context.stack, &[p_vex, D(0), cus, quq_vex]))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn jet_cook(context: &mut Context, subject: Noun) -> Result {
|
||||
let tub = slot(subject, 6)?;
|
||||
let van = slot(subject, 7)?;
|
||||
let poq = slot(van, 12)?;
|
||||
let sef = slot(van, 13)?;
|
||||
|
||||
let vex = slam(context, sef, tub)?.as_cell()?;
|
||||
let p_vex = vex.head();
|
||||
let q_vex = vex.tail();
|
||||
|
||||
if unsafe { q_vex.raw_equals(D(0)) } {
|
||||
Ok(vex.as_noun())
|
||||
} else {
|
||||
let uq_vex = q_vex.as_cell()?.tail().as_cell()?;
|
||||
let puq_vex = uq_vex.head();
|
||||
let quq_vex = uq_vex.tail();
|
||||
|
||||
let wag = slam(context, poq, puq_vex)?;
|
||||
Ok(T(&mut context.stack, &[p_vex, D(0), wag, quq_vex]))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn jet_easy(context: &mut Context, subject: Noun) -> Result {
|
||||
let tub = slot(subject, 6)?;
|
||||
let van = slot(subject, 7)?;
|
||||
@ -307,6 +382,35 @@ pub fn jet_mask(context: &mut Context, subject: Noun) -> Result {
|
||||
util::fail(context, p_tub)
|
||||
}
|
||||
|
||||
pub fn jet_shim(context: &mut Context, subject: Noun) -> Result {
|
||||
let tub = slot(subject, 6)?.as_cell()?;
|
||||
let van = slot(subject, 7)?;
|
||||
let zep = slot(van, 6)?.as_cell()?;
|
||||
|
||||
let p_tub = tub.head();
|
||||
let q_tub = tub.tail();
|
||||
|
||||
if unsafe { q_tub.raw_equals(D(0)) } {
|
||||
util::fail(context, p_tub)
|
||||
} else {
|
||||
let p_zep = zep.head();
|
||||
let q_zep = zep.tail();
|
||||
let iq_tub = q_tub.as_cell()?.head();
|
||||
|
||||
if let (Some(p_zep_d), Some(q_zep_d), Some(iq_tub_d)) =
|
||||
(p_zep.direct(), q_zep.direct(), iq_tub.direct())
|
||||
{
|
||||
if (iq_tub_d.data() >= p_zep_d.data()) && (iq_tub_d.data() <= q_zep_d.data()) {
|
||||
util::next(context, tub.as_noun())
|
||||
} else {
|
||||
util::fail(context, p_tub)
|
||||
}
|
||||
} else {
|
||||
Err(JetErr::Fail(Error::NonDeterministic(D(0))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn jet_stag(context: &mut Context, subject: Noun) -> Result {
|
||||
let tub = slot(subject, 6)?;
|
||||
let van = slot(subject, 7)?;
|
||||
@ -329,6 +433,139 @@ pub fn jet_stag(context: &mut Context, subject: Noun) -> Result {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn jet_stew(context: &mut Context, subject: Noun) -> Result {
|
||||
let tub = slot(subject, 6)?.as_cell()?;
|
||||
let con = slot(subject, 7)?;
|
||||
let mut hel = slot(con, 2)?;
|
||||
|
||||
let p_tub = tub.head();
|
||||
let q_tub = tub.tail();
|
||||
if unsafe { q_tub.raw_equals(D(0)) } {
|
||||
return util::fail(context, p_tub);
|
||||
}
|
||||
|
||||
let iq_tub = q_tub.as_cell()?.head().as_atom()?;
|
||||
if !iq_tub.is_direct() {
|
||||
// Character cannot be encoded using 8 bytes = computibilty error
|
||||
return Err(JetErr::Fail(Error::NonDeterministic(D(0))));
|
||||
}
|
||||
|
||||
loop {
|
||||
if unsafe { hel.raw_equals(D(0)) } {
|
||||
return util::fail(context, p_tub);
|
||||
} else {
|
||||
let n_hel = slot(hel, 2)?.as_cell()?;
|
||||
let l_hel = slot(hel, 6)?;
|
||||
let r_hel = slot(hel, 7)?;
|
||||
let pn_hel = n_hel.head();
|
||||
let qn_hel = n_hel.tail();
|
||||
|
||||
let bit = match pn_hel.as_either_atom_cell() {
|
||||
Left(atom) => match atom.as_either() {
|
||||
Left(direct) => iq_tub.as_direct()?.data() == direct.data(),
|
||||
Right(_) => {
|
||||
// Character cannot be encoded using 8 bytes = computibilty error
|
||||
return Err(JetErr::Fail(Error::NonDeterministic(D(0))));
|
||||
}
|
||||
},
|
||||
Right(cell) => {
|
||||
let hpn_hel = cell.head().as_atom()?;
|
||||
let tpn_hel = cell.tail().as_atom()?;
|
||||
|
||||
match (hpn_hel.as_either(), tpn_hel.as_either()) {
|
||||
(Left(_), Left(_)) => {
|
||||
gte_b(&mut context.stack, iq_tub, hpn_hel)
|
||||
&& lte_b(&mut context.stack, iq_tub, tpn_hel)
|
||||
}
|
||||
_ => {
|
||||
// XX: Fixes jet mismatch in Vere
|
||||
// Character cannot be encoded using 8 bytes = computibilty error
|
||||
return Err(JetErr::Fail(Error::NonDeterministic(D(0))));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if bit {
|
||||
return slam(context, qn_hel, tub.as_noun());
|
||||
} else {
|
||||
let wor = match pn_hel.as_either_atom_cell() {
|
||||
Left(atom) => atom,
|
||||
Right(cell) => cell.head().as_atom()?,
|
||||
};
|
||||
|
||||
if lth_b(&mut context.stack, iq_tub, wor) {
|
||||
hel = l_hel;
|
||||
} else {
|
||||
hel = r_hel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// +$ edge [p=hair q=(unit [p=* q=nail])]
|
||||
#[derive(Copy, Clone)]
|
||||
struct StirPair {
|
||||
pub har: Noun, // p.edge
|
||||
pub res: Noun, // p.u.q.edge
|
||||
}
|
||||
|
||||
pub fn jet_stir(context: &mut Context, subject: Noun) -> Result {
|
||||
unsafe {
|
||||
context.with_stack_frame(0, |context| {
|
||||
let mut tub = slot(subject, 6)?;
|
||||
let van = slot(subject, 7)?;
|
||||
let rud = slot(van, 12)?;
|
||||
let raq = slot(van, 26)?;
|
||||
let fel = slot(van, 27)?;
|
||||
|
||||
// initial accumulator (deconstructed)
|
||||
let mut p_wag: Noun;
|
||||
let mut puq_wag: Noun;
|
||||
let quq_wag: Noun;
|
||||
|
||||
// push incremental, succesful [fel] parse results onto stack
|
||||
{
|
||||
let vex = slam(context, fel, tub)?.as_cell()?;
|
||||
let mut p_vex = vex.head();
|
||||
let mut q_vex = vex.tail();
|
||||
while !q_vex.raw_equals(D(0)) {
|
||||
let puq_vex = slot(q_vex, 6)?;
|
||||
let quq_vex = slot(q_vex, 7)?;
|
||||
|
||||
*(context.stack.push::<StirPair>()) = StirPair {
|
||||
har: p_vex,
|
||||
res: puq_vex,
|
||||
};
|
||||
|
||||
tub = quq_vex;
|
||||
|
||||
let vex = slam(context, fel, tub)?.as_cell()?;
|
||||
p_vex = vex.head();
|
||||
q_vex = vex.tail();
|
||||
}
|
||||
|
||||
p_wag = p_vex;
|
||||
puq_wag = rud;
|
||||
quq_wag = tub;
|
||||
}
|
||||
|
||||
// unwind the stack, folding parse results into [wag] by way of [raq]
|
||||
while !context.stack.stack_is_empty() {
|
||||
let par_u = *(context.stack.top::<StirPair>());
|
||||
p_wag = util::last(par_u.har, p_wag)?;
|
||||
let sam = T(&mut context.stack, &[par_u.res, puq_wag]);
|
||||
puq_wag = slam(context, raq, sam)?;
|
||||
context.stack.pop::<StirPair>();
|
||||
}
|
||||
|
||||
let res = T(&mut context.stack, &[p_wag, D(0), puq_wag, quq_wag]);
|
||||
Ok(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub mod util {
|
||||
use crate::interpreter::{inc, Context};
|
||||
use crate::jets::Result;
|
||||
@ -339,14 +576,14 @@ pub mod util {
|
||||
let zyl = zyc.as_cell()?;
|
||||
let nal = naz.as_cell()?;
|
||||
|
||||
let zyll = zyl.head().as_direct()?.data();
|
||||
let zylc = zyl.tail().as_direct()?.data();
|
||||
let nall = nal.head().as_direct()?.data();
|
||||
let nalc = nal.tail().as_direct()?.data();
|
||||
let p_zyc = zyl.head().as_direct()?.data();
|
||||
let q_zyc = zyl.tail().as_direct()?.data();
|
||||
let p_naz = nal.head().as_direct()?.data();
|
||||
let q_naz = nal.tail().as_direct()?.data();
|
||||
|
||||
match zyll.cmp(&nall) {
|
||||
match p_zyc.cmp(&p_naz) {
|
||||
Ordering::Equal => {
|
||||
if zylc > nalc {
|
||||
if q_zyc > q_naz {
|
||||
Ok(zyc)
|
||||
} else {
|
||||
Ok(naz)
|
||||
|
@ -419,7 +419,7 @@ fn peek(context: &mut Context, ovo: Noun) -> Noun {
|
||||
}
|
||||
|
||||
fn goof(context: &mut Context, traces: Noun) -> Noun {
|
||||
let trace = zing(&mut context.nock_context.stack, traces).unwrap();
|
||||
let trace = zing(&mut context.nock_context.stack, traces).expect("serf: goof: zing failed");
|
||||
let tone = Cell::new(&mut context.nock_context.stack, D(2), trace);
|
||||
let tang = mook(&mut context.nock_context, tone, false)
|
||||
.expect("serf: goof: +mook crashed on bail")
|
||||
|
Loading…
Reference in New Issue
Block a user