mirror of
https://github.com/Dummi26/mers.git
synced 2025-03-10 14:13:52 +01:00
order of operations, tests, VSingleType::Function
- moved && and || in order of operations to make a == b && c == d work as expected - added some .mers files to mers/tests - changed VSingleType::Function (setup for future type system changes)
This commit is contained in:
parent
e3ca8d011d
commit
f7feb688e8
2
mers/Cargo.lock
generated
2
mers/Cargo.lock
generated
@ -647,7 +647,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mers"
|
name = "mers"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"colorize",
|
"colorize",
|
||||||
"edit",
|
"edit",
|
||||||
|
@ -561,14 +561,26 @@ impl BuiltinFunction {
|
|||||||
let mut out = VType { types: vec![] };
|
let mut out = VType { types: vec![] };
|
||||||
for func in &funcs.types {
|
for func in &funcs.types {
|
||||||
if let VSingleType::Function(io) = func {
|
if let VSingleType::Function(io) = func {
|
||||||
for (i, o) in io {
|
let mut empty = true;
|
||||||
if i.iter()
|
let fn_out = io.iter().fold(VType::empty(), |t, (fn_in, fn_out)| {
|
||||||
|
if fn_in.len() == (input.len() - 1)
|
||||||
|
&& fn_in
|
||||||
|
.iter()
|
||||||
.zip(input.iter().skip(1))
|
.zip(input.iter().skip(1))
|
||||||
.all(|(i, input)| input.contains(i, info))
|
.all(|(fn_in, arg)| arg.fits_in(fn_in, info).is_empty())
|
||||||
{
|
{
|
||||||
out = out | o;
|
empty = false;
|
||||||
|
t | fn_out.clone()
|
||||||
|
} else {
|
||||||
|
t
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
if empty {
|
||||||
|
unreachable!(
|
||||||
|
"fn args are incorrect for builtin run() or thread()!"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
out = out | fn_out;
|
||||||
} else {
|
} else {
|
||||||
unreachable!("run called, first arg not a function")
|
unreachable!("run called, first arg not a function")
|
||||||
}
|
}
|
||||||
@ -961,7 +973,7 @@ impl BuiltinFunction {
|
|||||||
}
|
}
|
||||||
for (i, var) in f.inputs.iter().enumerate() {
|
for (i, var) in f.inputs.iter().enumerate() {
|
||||||
let val = args[i + 1].run(info).clone_data();
|
let val = args[i + 1].run(info).clone_data();
|
||||||
*var.lock().unwrap() = val;
|
var.lock().unwrap().0 = val;
|
||||||
}
|
}
|
||||||
f.run(info)
|
f.run(info)
|
||||||
} else {
|
} else {
|
||||||
@ -977,9 +989,14 @@ impl BuiltinFunction {
|
|||||||
for (i, var) in f.inputs.iter().enumerate() {
|
for (i, var) in f.inputs.iter().enumerate() {
|
||||||
let val = args[i + 1].run(info).clone_data();
|
let val = args[i + 1].run(info).clone_data();
|
||||||
run_input_types.push(val.out_single());
|
run_input_types.push(val.out_single());
|
||||||
*var.lock().unwrap() = val;
|
var.lock().unwrap().0 = val;
|
||||||
}
|
}
|
||||||
let out_type = f.out(&run_input_types, &info);
|
let out_type = f
|
||||||
|
.out(
|
||||||
|
&run_input_types.iter().map(|v| v.clone().into()).collect(),
|
||||||
|
&info,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
let info = Arc::clone(info);
|
let info = Arc::clone(info);
|
||||||
let f = Arc::clone(f);
|
let f = Arc::clone(f);
|
||||||
VDataEnum::Thread(
|
VDataEnum::Thread(
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
eprintln,
|
assert_eq, eprintln,
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
@ -17,7 +17,7 @@ pub enum RStatementEnum {
|
|||||||
Value(VData),
|
Value(VData),
|
||||||
Tuple(Vec<RStatement>),
|
Tuple(Vec<RStatement>),
|
||||||
List(Vec<RStatement>),
|
List(Vec<RStatement>),
|
||||||
Variable(Arc<Mutex<VData>>, VType, bool),
|
Variable(Arc<Mutex<(VData, VType)>>, bool),
|
||||||
FunctionCall(Arc<RFunction>, Vec<RStatement>),
|
FunctionCall(Arc<RFunction>, Vec<RStatement>),
|
||||||
BuiltinFunctionCall(BuiltinFunction, Vec<RStatement>),
|
BuiltinFunctionCall(BuiltinFunction, Vec<RStatement>),
|
||||||
LibFunctionCall(usize, usize, Vec<RStatement>, VType),
|
LibFunctionCall(usize, usize, Vec<RStatement>, VType),
|
||||||
@ -60,48 +60,51 @@ impl RBlock {
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct RFunction {
|
pub struct RFunction {
|
||||||
pub inputs: Vec<Arc<Mutex<VData>>>,
|
pub inputs: Vec<Arc<Mutex<(VData, VType)>>>,
|
||||||
pub input_types: Vec<VType>,
|
pub input_types: Vec<VType>,
|
||||||
pub input_output_map: Vec<(Vec<VSingleType>, VType)>,
|
|
||||||
pub statement: RStatement,
|
pub statement: RStatement,
|
||||||
|
pub out_map: Vec<(Vec<VType>, VType)>,
|
||||||
|
}
|
||||||
|
impl PartialEq for RFunction {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Eq for RFunction {
|
||||||
|
fn assert_receiver_is_total_eq(&self) {}
|
||||||
}
|
}
|
||||||
impl RFunction {
|
impl RFunction {
|
||||||
pub fn run(&self, info: &GSInfo) -> VData {
|
pub fn run(&self, info: &GSInfo) -> VData {
|
||||||
self.statement.run(info)
|
self.statement.run(info)
|
||||||
}
|
}
|
||||||
pub fn out(&self, input_types: &Vec<VSingleType>, info: &GlobalScriptInfo) -> VType {
|
pub fn out(&self, input_types: &Vec<VType>, info: &GlobalScriptInfo) -> Option<VType> {
|
||||||
self.input_output_map
|
// NOTE: This can ONLY use self.out_map, because it's used by the VSingleType.fits_in method.
|
||||||
|
let mut empty = true;
|
||||||
|
let out = self
|
||||||
|
.out_map
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|v| {
|
.fold(VType::empty(), |t, (fn_in, fn_out)| {
|
||||||
if v.0 == *input_types {
|
if fn_in.len() == (input_types.len())
|
||||||
Some(v.1.clone())
|
&& fn_in
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| self.statement.out(info))
|
|
||||||
}
|
|
||||||
pub fn out_vt(&self, input_types: &Vec<VType>, info: &GlobalScriptInfo) -> VType {
|
|
||||||
let mut out = VType { types: vec![] };
|
|
||||||
for (itype, otype) in self.input_output_map.iter() {
|
|
||||||
if itype
|
|
||||||
.iter()
|
.iter()
|
||||||
.zip(input_types.iter())
|
.zip(input_types.iter())
|
||||||
.all(|(expected, got)| got.contains(expected, info))
|
.all(|(fn_in, arg)| arg.fits_in(fn_in, info).is_empty())
|
||||||
{
|
{
|
||||||
out = out | otype;
|
empty = false;
|
||||||
}
|
t | fn_out.clone()
|
||||||
}
|
|
||||||
if out.types.is_empty() {
|
|
||||||
// this can happen if we used the `any` type in our function signature,
|
|
||||||
// so in that case we just return the most broad type possible.
|
|
||||||
self.statement.out(info)
|
|
||||||
} else {
|
} else {
|
||||||
out
|
t
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if empty {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn out_all(&self, info: &GlobalScriptInfo) -> VType {
|
pub fn out_all(&self, _info: &GlobalScriptInfo) -> VType {
|
||||||
self.statement.out(info)
|
// self.statement.out(info)
|
||||||
|
self.out_map.iter().fold(VType::empty(), |t, (_, v)| t | v)
|
||||||
}
|
}
|
||||||
pub fn in_types(&self) -> &Vec<VType> {
|
pub fn in_types(&self) -> &Vec<VType> {
|
||||||
&self.input_types
|
&self.input_types
|
||||||
@ -179,16 +182,16 @@ impl RStatementEnum {
|
|||||||
}
|
}
|
||||||
VDataEnum::List(out, w).to()
|
VDataEnum::List(out, w).to()
|
||||||
}
|
}
|
||||||
Self::Variable(v, _, is_ref) => {
|
Self::Variable(v, is_ref) => {
|
||||||
if *is_ref {
|
if *is_ref {
|
||||||
VDataEnum::Reference(v.lock().unwrap().clone_mut()).to()
|
VDataEnum::Reference(v.lock().unwrap().0.clone_mut()).to()
|
||||||
} else {
|
} else {
|
||||||
v.lock().unwrap().clone_data()
|
v.lock().unwrap().0.clone_data()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::FunctionCall(func, args) => {
|
Self::FunctionCall(func, args) => {
|
||||||
for (i, input) in func.inputs.iter().enumerate() {
|
for (i, input) in func.inputs.iter().enumerate() {
|
||||||
input.lock().unwrap().assign(args[i].run(info));
|
input.lock().unwrap().0.assign(args[i].run(info));
|
||||||
}
|
}
|
||||||
func.run(info)
|
func.run(info)
|
||||||
}
|
}
|
||||||
@ -333,22 +336,25 @@ impl RStatementEnum {
|
|||||||
types
|
types
|
||||||
})
|
})
|
||||||
.into(),
|
.into(),
|
||||||
Self::Variable(_, t, is_ref) => {
|
Self::Variable(t, is_ref) => {
|
||||||
if *is_ref {
|
if *is_ref {
|
||||||
VType {
|
VType {
|
||||||
types: t
|
types: t
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.1
|
||||||
.types
|
.types
|
||||||
.iter()
|
.iter()
|
||||||
.map(|t| VSingleType::Reference(Box::new(t.clone())))
|
.map(|t| VSingleType::Reference(Box::new(t.clone())))
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.clone()
|
t.lock().unwrap().1.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::FunctionCall(f, args) => {
|
Self::FunctionCall(f, args) => f
|
||||||
f.out_vt(&args.iter().map(|v| v.out(info)).collect(), info)
|
.out(&args.iter().map(|v| v.out(info)).collect(), info)
|
||||||
}
|
.expect("invalid args for function -> can't determine output type"),
|
||||||
Self::LibFunctionCall(.., out) => out.clone(),
|
Self::LibFunctionCall(.., out) => out.clone(),
|
||||||
Self::Block(b) => b.out(info),
|
Self::Block(b) => b.out(info),
|
||||||
Self::If(_, a, b) => {
|
Self::If(_, a, b) => {
|
||||||
|
@ -4,6 +4,7 @@ use std::{
|
|||||||
eprintln,
|
eprintln,
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -198,7 +199,7 @@ impl FormatGs for ToRunnableError {
|
|||||||
// Local, used to keep local variables separated
|
// Local, used to keep local variables separated
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct LInfo {
|
struct LInfo {
|
||||||
vars: HashMap<String, (Arc<Mutex<VData>>, VType)>,
|
vars: HashMap<String, Arc<Mutex<(VData, VType)>>>,
|
||||||
fns: HashMap<String, Vec<Arc<RFunction>>>,
|
fns: HashMap<String, Vec<Arc<RFunction>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,17 +207,6 @@ pub fn to_runnable(
|
|||||||
s: SFunction,
|
s: SFunction,
|
||||||
mut ginfo: GlobalScriptInfo,
|
mut ginfo: GlobalScriptInfo,
|
||||||
) -> Result<RScript, (ToRunnableError, GSInfo)> {
|
) -> Result<RScript, (ToRunnableError, GSInfo)> {
|
||||||
if s.inputs.len() != 1 || s.inputs[0].0 != "args" {
|
|
||||||
return Err((ToRunnableError::MainWrongInput, ginfo.to_arc()));
|
|
||||||
}
|
|
||||||
assert_eq!(
|
|
||||||
s.inputs[0].1,
|
|
||||||
VType {
|
|
||||||
types: vec![VSingleType::List(VType {
|
|
||||||
types: vec![VSingleType::String],
|
|
||||||
})],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
let func = match function(
|
let func = match function(
|
||||||
&s,
|
&s,
|
||||||
&mut ginfo,
|
&mut ginfo,
|
||||||
@ -235,44 +225,6 @@ pub fn to_runnable(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// go over every possible known-type input for the given function, returning all possible RFunctions.
|
|
||||||
fn get_all_functions(
|
|
||||||
s: &SFunction,
|
|
||||||
ginfo: &mut GlobalScriptInfo,
|
|
||||||
linfo: &mut LInfo,
|
|
||||||
input_vars: &Vec<Arc<Mutex<VData>>>,
|
|
||||||
inputs: &mut Vec<VSingleType>,
|
|
||||||
out: &mut Vec<(Vec<VSingleType>, VType)>,
|
|
||||||
) -> Result<(), ToRunnableError> {
|
|
||||||
if s.inputs.len() > inputs.len() {
|
|
||||||
let input_here = &s.inputs[inputs.len()].1;
|
|
||||||
for t in &input_here.types {
|
|
||||||
let mut t = t.clone();
|
|
||||||
stype(&mut t, ginfo)?;
|
|
||||||
inputs.push(t);
|
|
||||||
get_all_functions(s, ginfo, linfo, input_vars, inputs, out)?;
|
|
||||||
inputs.pop();
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
// set the types
|
|
||||||
for (varid, vartype) in s.inputs.iter().zip(inputs.iter()) {
|
|
||||||
linfo.vars.get_mut(&varid.0).unwrap().1 = {
|
|
||||||
let mut vartype = vartype.clone();
|
|
||||||
stype(&mut vartype, ginfo)?;
|
|
||||||
vartype.to()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// the statement is parsed multiple times (this is why we get duplicates in stderr):
|
|
||||||
// - n times for the function args to generate the input-output map
|
|
||||||
// - 1 more time here, where the function args aren't single types
|
|
||||||
out.push((
|
|
||||||
inputs.clone(),
|
|
||||||
statement(&s.statement, ginfo, &mut linfo.clone())?.out(ginfo),
|
|
||||||
));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn function(
|
fn function(
|
||||||
s: &SFunction,
|
s: &SFunction,
|
||||||
ginfo: &mut GlobalScriptInfo,
|
ginfo: &mut GlobalScriptInfo,
|
||||||
@ -283,32 +235,74 @@ fn function(
|
|||||||
for (iname, itype) in &s.inputs {
|
for (iname, itype) in &s.inputs {
|
||||||
let mut itype = itype.to_owned();
|
let mut itype = itype.to_owned();
|
||||||
stypes(&mut itype, ginfo)?;
|
stypes(&mut itype, ginfo)?;
|
||||||
let var = Arc::new(Mutex::new(VData::new_placeholder()));
|
let var = Arc::new(Mutex::new((VData::new_placeholder(), itype.clone())));
|
||||||
linfo
|
linfo.vars.insert(iname.clone(), Arc::clone(&var));
|
||||||
.vars
|
|
||||||
.insert(iname.clone(), (Arc::clone(&var), itype.clone()));
|
|
||||||
input_vars.push(var);
|
input_vars.push(var);
|
||||||
input_types.push(itype);
|
input_types.push(itype);
|
||||||
}
|
}
|
||||||
let mut all_outs = vec![];
|
|
||||||
get_all_functions(
|
|
||||||
s,
|
|
||||||
ginfo,
|
|
||||||
&mut linfo,
|
|
||||||
&input_vars,
|
|
||||||
&mut Vec::with_capacity(s.inputs.len()),
|
|
||||||
&mut all_outs,
|
|
||||||
)?;
|
|
||||||
// set the types to all possible types (get_all_functions sets the types to one single type to get the return type of the block for that case)
|
// set the types to all possible types (get_all_functions sets the types to one single type to get the return type of the block for that case)
|
||||||
for (varid, vartype) in s.inputs.iter().zip(input_types.iter()) {
|
for (varid, vartype) in s.inputs.iter().zip(input_types.iter()) {
|
||||||
linfo.vars.get_mut(&varid.0).unwrap().1 = vartype.clone();
|
linfo.vars.get(&varid.0).unwrap().lock().unwrap().1 = vartype.clone();
|
||||||
}
|
}
|
||||||
Ok(RFunction {
|
let mut o = RFunction {
|
||||||
|
out_map: vec![],
|
||||||
inputs: input_vars,
|
inputs: input_vars,
|
||||||
input_types,
|
input_types,
|
||||||
input_output_map: all_outs,
|
|
||||||
statement: statement(&s.statement, ginfo, &mut linfo.clone())?,
|
statement: statement(&s.statement, ginfo, &mut linfo.clone())?,
|
||||||
})
|
};
|
||||||
|
o.out_map = {
|
||||||
|
let mut map = vec![];
|
||||||
|
let mut indices: Vec<_> = o.input_types.iter().map(|_| 0).collect();
|
||||||
|
// like counting: advance first index, when we reach the end, reset to zero and advance the next index, ...
|
||||||
|
loop {
|
||||||
|
let mut current_types = Vec::with_capacity(o.input_types.len());
|
||||||
|
let mut adv = true;
|
||||||
|
let mut was_last = o.input_types.is_empty();
|
||||||
|
for i in 0..o.input_types.len() {
|
||||||
|
current_types.push(match o.input_types[i].types.get(indices[i]) {
|
||||||
|
Some(v) => v.clone().to(),
|
||||||
|
None => VType::empty(),
|
||||||
|
});
|
||||||
|
if adv {
|
||||||
|
if indices[i] + 1 < o.input_types[i].types.len() {
|
||||||
|
indices[i] += 1;
|
||||||
|
adv = false;
|
||||||
|
} else {
|
||||||
|
indices[i] = 0;
|
||||||
|
// we just reset the last index back to 0 - if we don't break
|
||||||
|
// from the loop, we will just start all over again.
|
||||||
|
if i + 1 == o.input_types.len() {
|
||||||
|
was_last = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// let out = o.out(¤t_types, ginfo).expect("invalid args????");
|
||||||
|
let out = {
|
||||||
|
let mut actual = Vec::with_capacity(o.inputs.len());
|
||||||
|
// simulate these variable types
|
||||||
|
for (fn_input, c_type) in o.inputs.iter().zip(current_types.iter()) {
|
||||||
|
actual.push(std::mem::replace(
|
||||||
|
&mut fn_input.lock().unwrap().1,
|
||||||
|
c_type.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// not get the return type if these were the actual types
|
||||||
|
let out = o.statement.out(ginfo);
|
||||||
|
// reset
|
||||||
|
for (fn_input, actual) in o.inputs.iter().zip(actual) {
|
||||||
|
std::mem::replace(&mut fn_input.lock().unwrap().1, actual);
|
||||||
|
}
|
||||||
|
// return
|
||||||
|
out
|
||||||
|
};
|
||||||
|
map.push((current_types, out));
|
||||||
|
if was_last {
|
||||||
|
break map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(o)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn block(
|
fn block(
|
||||||
@ -364,7 +358,7 @@ pub fn stype(t: &mut VSingleType, ginfo: &mut GlobalScriptInfo) -> Result<(), To
|
|||||||
VSingleType::Function(io_map) => {
|
VSingleType::Function(io_map) => {
|
||||||
for io_variant in io_map {
|
for io_variant in io_map {
|
||||||
for i in &mut io_variant.0 {
|
for i in &mut io_variant.0 {
|
||||||
stype(i, ginfo)?;
|
stypes(i, ginfo)?;
|
||||||
}
|
}
|
||||||
stypes(&mut io_variant.1, ginfo)?;
|
stypes(&mut io_variant.1, ginfo)?;
|
||||||
}
|
}
|
||||||
@ -449,24 +443,14 @@ fn statement_adv(
|
|||||||
let var = VData::new_placeholder();
|
let var = VData::new_placeholder();
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
let var = VData::new_placeholder_with_name(v.to_owned());
|
let var = VData::new_placeholder_with_name(v.to_owned());
|
||||||
let var_arc = Arc::new(Mutex::new(var));
|
let var_arc = Arc::new(Mutex::new((var, t.clone())));
|
||||||
linfo
|
linfo.vars.insert(v.to_owned(), Arc::clone(&var_arc));
|
||||||
.vars
|
RStatementEnum::Variable(var_arc, true)
|
||||||
.insert(v.to_owned(), (Arc::clone(&var_arc), t.clone()));
|
|
||||||
RStatementEnum::Variable(var_arc, t.clone(), true)
|
|
||||||
} else {
|
} else {
|
||||||
return Err(ToRunnableError::UseOfUndefinedVariable(v.clone()));
|
return Err(ToRunnableError::UseOfUndefinedVariable(v.clone()));
|
||||||
}
|
}
|
||||||
} else if let Some(var) = existing_var {
|
} else if let Some(var) = existing_var {
|
||||||
RStatementEnum::Variable(
|
RStatementEnum::Variable(Arc::clone(&var), *is_ref)
|
||||||
Arc::clone(&var.0),
|
|
||||||
{
|
|
||||||
let mut v = var.1.clone();
|
|
||||||
stypes(&mut v, ginfo)?;
|
|
||||||
v
|
|
||||||
},
|
|
||||||
*is_ref,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
return Err(ToRunnableError::UseOfUndefinedVariable(v.clone()));
|
return Err(ToRunnableError::UseOfUndefinedVariable(v.clone()));
|
||||||
}
|
}
|
||||||
@ -477,45 +461,23 @@ fn statement_adv(
|
|||||||
for arg in args.iter() {
|
for arg in args.iter() {
|
||||||
rargs.push(statement(arg, ginfo, linfo)?);
|
rargs.push(statement(arg, ginfo, linfo)?);
|
||||||
}
|
}
|
||||||
fn check_fn_args(
|
|
||||||
args: &Vec<VType>,
|
|
||||||
inputs: &Vec<(Vec<VType>, VType)>,
|
|
||||||
ginfo: &GlobalScriptInfo,
|
|
||||||
) -> Option<VType> {
|
|
||||||
let mut fit_any = false;
|
|
||||||
let mut out = VType::empty();
|
|
||||||
for (inputs, output) in inputs {
|
|
||||||
if args.len() == inputs.len()
|
|
||||||
&& args
|
|
||||||
.iter()
|
|
||||||
.zip(inputs.iter())
|
|
||||||
.all(|(arg, input)| arg.fits_in(input, ginfo).is_empty())
|
|
||||||
{
|
|
||||||
fit_any = true;
|
|
||||||
out = out | output;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if fit_any {
|
|
||||||
Some(out)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let arg_types: Vec<_> = rargs.iter().map(|v| v.out(ginfo)).collect();
|
let arg_types: Vec<_> = rargs.iter().map(|v| v.out(ginfo)).collect();
|
||||||
|
fn check_fn_args(
|
||||||
|
arg_types: &Vec<VType>,
|
||||||
|
func: &RFunction,
|
||||||
|
ginfo: &GlobalScriptInfo,
|
||||||
|
) -> bool {
|
||||||
|
func.inputs.len() == arg_types.len()
|
||||||
|
&& func
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.zip(arg_types.iter())
|
||||||
|
.all(|(fn_in, arg)| arg.fits_in(&fn_in.lock().unwrap().1, ginfo).is_empty())
|
||||||
|
}
|
||||||
if let Some(funcs) = linfo.fns.get(v) {
|
if let Some(funcs) = linfo.fns.get(v) {
|
||||||
'find_func: {
|
'find_func: {
|
||||||
for func in funcs.iter().rev() {
|
for func in funcs.iter().rev() {
|
||||||
if let Some(_out) = check_fn_args(
|
if check_fn_args(&arg_types, &func, ginfo) {
|
||||||
&arg_types,
|
|
||||||
&func
|
|
||||||
.input_output_map
|
|
||||||
.iter()
|
|
||||||
.map(|v| {
|
|
||||||
(v.0.iter().map(|v| v.clone().to()).collect(), v.1.to_owned())
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
ginfo,
|
|
||||||
) {
|
|
||||||
break 'find_func RStatementEnum::FunctionCall(
|
break 'find_func RStatementEnum::FunctionCall(
|
||||||
Arc::clone(&func),
|
Arc::clone(&func),
|
||||||
rargs,
|
rargs,
|
||||||
@ -545,14 +507,27 @@ fn statement_adv(
|
|||||||
if let Some((libid, fnid)) = ginfo.lib_fns.get(v) {
|
if let Some((libid, fnid)) = ginfo.lib_fns.get(v) {
|
||||||
let lib = &ginfo.libs[*libid];
|
let lib = &ginfo.libs[*libid];
|
||||||
let libfn = &lib.registered_fns[*fnid];
|
let libfn = &lib.registered_fns[*fnid];
|
||||||
if let Some(fn_out) = check_fn_args(&arg_types, &libfn.1, ginfo) {
|
let mut empty = true;
|
||||||
RStatementEnum::LibFunctionCall(*libid, *fnid, rargs, fn_out.clone())
|
let fn_out = libfn.1.iter().fold(VType::empty(), |t, (fn_in, fn_out)| {
|
||||||
|
if fn_in.len() == arg_types.len()
|
||||||
|
&& fn_in
|
||||||
|
.iter()
|
||||||
|
.zip(arg_types.iter())
|
||||||
|
.all(|(fn_in, arg)| arg.fits_in(fn_in, ginfo).is_empty())
|
||||||
|
{
|
||||||
|
empty = false;
|
||||||
|
t | fn_out.clone()
|
||||||
} else {
|
} else {
|
||||||
|
t
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if empty {
|
||||||
return Err(ToRunnableError::WrongArgsForLibFunction(
|
return Err(ToRunnableError::WrongArgsForLibFunction(
|
||||||
v.to_owned(),
|
v.to_owned(),
|
||||||
arg_types,
|
arg_types,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
RStatementEnum::LibFunctionCall(*libid, *fnid, rargs, fn_out.clone())
|
||||||
} else {
|
} else {
|
||||||
return Err(ToRunnableError::UseOfUndefinedFunction(v.clone()));
|
return Err(ToRunnableError::UseOfUndefinedFunction(v.clone()));
|
||||||
}
|
}
|
||||||
|
@ -318,7 +318,7 @@ impl VDataEnum {
|
|||||||
Self::String(..) => VSingleType::String,
|
Self::String(..) => VSingleType::String,
|
||||||
Self::Tuple(v) => VSingleType::Tuple(v.iter().map(|v| v.out_single().to()).collect()),
|
Self::Tuple(v) => VSingleType::Tuple(v.iter().map(|v| v.out_single().to()).collect()),
|
||||||
Self::List(t, _) => VSingleType::List(t.clone()),
|
Self::List(t, _) => VSingleType::List(t.clone()),
|
||||||
Self::Function(f) => VSingleType::Function(f.input_output_map.clone()),
|
Self::Function(f) => VSingleType::Function(f.out_map.clone()),
|
||||||
Self::Thread(_, o) => VSingleType::Thread(o.clone()),
|
Self::Thread(_, o) => VSingleType::Thread(o.clone()),
|
||||||
Self::Reference(r) => VSingleType::Reference(Box::new(r.out_single())),
|
Self::Reference(r) => VSingleType::Reference(Box::new(r.out_single())),
|
||||||
Self::EnumVariant(e, v) => VSingleType::EnumVariant(*e, v.out_single().to()),
|
Self::EnumVariant(e, v) => VSingleType::EnumVariant(*e, v.out_single().to()),
|
||||||
@ -560,7 +560,7 @@ impl FormatGs for VDataEnum {
|
|||||||
write!(f, "...]")
|
write!(f, "...]")
|
||||||
}
|
}
|
||||||
Self::Function(func) => {
|
Self::Function(func) => {
|
||||||
VSingleType::Function(func.input_output_map.clone()).fmtgs(f, info, form, file)
|
VSingleType::Function(func.out_map.clone()).fmtgs(f, info, form, file)
|
||||||
}
|
}
|
||||||
Self::Thread(..) => write!(f, "[TODO] THREAD"),
|
Self::Thread(..) => write!(f, "[TODO] THREAD"),
|
||||||
Self::Reference(inner) => {
|
Self::Reference(inner) => {
|
||||||
|
@ -2,11 +2,14 @@ use std::{
|
|||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt::{self, Debug, Display, Formatter},
|
fmt::{self, Debug, Display, Formatter},
|
||||||
ops::BitOr,
|
ops::BitOr,
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
code_runnable::{RFunction, RStatementEnum},
|
||||||
fmtgs::FormatGs,
|
fmtgs::FormatGs,
|
||||||
global_info::{self, GSInfo, GlobalScriptInfo},
|
global_info::{self, GSInfo, GlobalScriptInfo},
|
||||||
|
val_data::VDataEnum,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::global_info::LogMsg;
|
use super::global_info::LogMsg;
|
||||||
@ -25,7 +28,7 @@ pub enum VSingleType {
|
|||||||
String,
|
String,
|
||||||
Tuple(Vec<VType>),
|
Tuple(Vec<VType>),
|
||||||
List(VType),
|
List(VType),
|
||||||
Function(Vec<(Vec<VSingleType>, VType)>),
|
Function(Vec<(Vec<VType>, VType)>),
|
||||||
Thread(VType),
|
Thread(VType),
|
||||||
Reference(Box<Self>),
|
Reference(Box<Self>),
|
||||||
EnumVariant(usize, VType),
|
EnumVariant(usize, VType),
|
||||||
@ -467,18 +470,24 @@ impl VSingleType {
|
|||||||
(Self::Tuple(_), _) => false,
|
(Self::Tuple(_), _) => false,
|
||||||
(Self::List(a), Self::List(b)) => a.fits_in(b, info).is_empty(),
|
(Self::List(a), Self::List(b)) => a.fits_in(b, info).is_empty(),
|
||||||
(Self::List(_), _) => false,
|
(Self::List(_), _) => false,
|
||||||
(Self::Function(a), Self::Function(b)) => 'func_out: {
|
(Self::Function(a), Self::Function(b)) => 'fnt: {
|
||||||
for a in a {
|
// since RFunction.out only uses out_map, we can create a dummy RFunction here.
|
||||||
'search: {
|
let af = RFunction {
|
||||||
for b in b {
|
inputs: vec![],
|
||||||
if a.1.fits_in(&b.1, info).is_empty()
|
input_types: vec![],
|
||||||
&& a.0.len() == b.0.len()
|
statement: RStatementEnum::Value(VDataEnum::Bool(false).to()).to(),
|
||||||
&& a.0.iter().zip(b.0.iter()).all(|(a, b)| *a == *b)
|
out_map: a.clone(),
|
||||||
{
|
};
|
||||||
break 'search;
|
for (ins, out) in b {
|
||||||
|
// try everything that would be valid for b
|
||||||
|
if let Some(v) = af.out(ins, info) {
|
||||||
|
if !v.fits_in(out, info).is_empty() {
|
||||||
|
// found something that's valid for both, but a returns something that doesn't fit in what b would have returned -> a doesn't fit.
|
||||||
|
break 'fnt false;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
break 'func_out false;
|
// found something that's valid for b but not for a -> a doesn't fit.
|
||||||
|
break 'fnt false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
@ -192,7 +192,7 @@ pub fn parse_step_interpret(
|
|||||||
"args".to_string(),
|
"args".to_string(),
|
||||||
VSingleType::List(VSingleType::String.into()).to(),
|
VSingleType::List(VSingleType::String.into()).to(),
|
||||||
)],
|
)],
|
||||||
SStatementEnum::Block(parse_block_advanced(file, Some(false), false, true, false)?).to(),
|
SStatementEnum::Block(parse_block_advanced(file, Some(false), true, true, false)?).to(),
|
||||||
);
|
);
|
||||||
if ginfo.log.after_parse.log() {
|
if ginfo.log.after_parse.log() {
|
||||||
ginfo.log.log(LogMsg::AfterParse(
|
ginfo.log.log(LogMsg::AfterParse(
|
||||||
@ -855,6 +855,7 @@ pub mod implementation {
|
|||||||
file.skip_whitespaces();
|
file.skip_whitespaces();
|
||||||
// least local (evaluated last)
|
// least local (evaluated last)
|
||||||
// 0 =
|
// 0 =
|
||||||
|
// 015 && ||
|
||||||
// 020 == !=
|
// 020 == !=
|
||||||
// 025 > < >= <=
|
// 025 > < >= <=
|
||||||
// 050 + -
|
// 050 + -
|
||||||
@ -991,35 +992,6 @@ pub mod implementation {
|
|||||||
)
|
)
|
||||||
.to()
|
.to()
|
||||||
}
|
}
|
||||||
// 023 && ||
|
|
||||||
(0..=23, Some('&'))
|
|
||||||
if matches!(
|
|
||||||
file.get_char(file.get_pos().current_char_index + 1),
|
|
||||||
Some('&')
|
|
||||||
) =>
|
|
||||||
{
|
|
||||||
file.next();
|
|
||||||
file.next();
|
|
||||||
SStatementEnum::FunctionCall(
|
|
||||||
"and".to_owned(),
|
|
||||||
vec![out, parse_statement_adv(file, false, 24)?],
|
|
||||||
)
|
|
||||||
.to()
|
|
||||||
}
|
|
||||||
(0..=23, Some('|'))
|
|
||||||
if matches!(
|
|
||||||
file.get_char(file.get_pos().current_char_index + 1),
|
|
||||||
Some('|')
|
|
||||||
) =>
|
|
||||||
{
|
|
||||||
file.next();
|
|
||||||
file.next();
|
|
||||||
SStatementEnum::FunctionCall(
|
|
||||||
"or".to_owned(),
|
|
||||||
vec![out, parse_statement_adv(file, false, 24)?],
|
|
||||||
)
|
|
||||||
.to()
|
|
||||||
}
|
|
||||||
// 020 == !=
|
// 020 == !=
|
||||||
(0..=20, Some('='))
|
(0..=20, Some('='))
|
||||||
if matches!(
|
if matches!(
|
||||||
@ -1049,6 +1021,35 @@ pub mod implementation {
|
|||||||
)
|
)
|
||||||
.to()
|
.to()
|
||||||
}
|
}
|
||||||
|
// 015 && ||
|
||||||
|
(0..=15, Some('&'))
|
||||||
|
if matches!(
|
||||||
|
file.get_char(file.get_pos().current_char_index + 1),
|
||||||
|
Some('&')
|
||||||
|
) =>
|
||||||
|
{
|
||||||
|
file.next();
|
||||||
|
file.next();
|
||||||
|
SStatementEnum::FunctionCall(
|
||||||
|
"and".to_owned(),
|
||||||
|
vec![out, parse_statement_adv(file, false, 16)?],
|
||||||
|
)
|
||||||
|
.to()
|
||||||
|
}
|
||||||
|
(0..=15, Some('|'))
|
||||||
|
if matches!(
|
||||||
|
file.get_char(file.get_pos().current_char_index + 1),
|
||||||
|
Some('|')
|
||||||
|
) =>
|
||||||
|
{
|
||||||
|
file.next();
|
||||||
|
file.next();
|
||||||
|
SStatementEnum::FunctionCall(
|
||||||
|
"or".to_owned(),
|
||||||
|
vec![out, parse_statement_adv(file, false, 16)?],
|
||||||
|
)
|
||||||
|
.to()
|
||||||
|
}
|
||||||
// 000 = :=
|
// 000 = :=
|
||||||
(0..=0, Some('=')) => {
|
(0..=0, Some('=')) => {
|
||||||
file.next();
|
file.next();
|
||||||
@ -1145,6 +1146,7 @@ pub mod implementation {
|
|||||||
file.next();
|
file.next();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
Some(']') => break,
|
||||||
Some(ch) if ch.is_whitespace() => break,
|
Some(ch) if ch.is_whitespace() => break,
|
||||||
Some(ch) if ch == ',' => {
|
Some(ch) if ch == ',' => {
|
||||||
file.next();
|
file.next();
|
||||||
@ -1233,6 +1235,7 @@ pub mod implementation {
|
|||||||
match file.next() {
|
match file.next() {
|
||||||
Some('(') => {
|
Some('(') => {
|
||||||
break 'parse_single_type if name.as_str() == "fn" {
|
break 'parse_single_type if name.as_str() == "fn" {
|
||||||
|
// syntax: fn((arg1 arg2 arg3) returns1 (arg1 arg2) returns2)
|
||||||
let mut fn_types = vec![];
|
let mut fn_types = vec![];
|
||||||
loop {
|
loop {
|
||||||
file.skip_whitespaces();
|
file.skip_whitespaces();
|
||||||
@ -1240,6 +1243,7 @@ pub mod implementation {
|
|||||||
Some('(') => {
|
Some('(') => {
|
||||||
let mut args = vec![];
|
let mut args = vec![];
|
||||||
loop {
|
loop {
|
||||||
|
file.skip_whitespaces();
|
||||||
let (t, fn_args_closed) =
|
let (t, fn_args_closed) =
|
||||||
parse_type_adv(file, true)?;
|
parse_type_adv(file, true)?;
|
||||||
args.push(t);
|
args.push(t);
|
||||||
@ -1247,39 +1251,9 @@ pub mod implementation {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let out = if let Some(v) = args.pop() {
|
file.skip_whitespaces();
|
||||||
v
|
let out = parse_type(file)?;
|
||||||
} else {
|
fn_types.push((args, out));
|
||||||
VSingleType::Tuple(vec![]).to()
|
|
||||||
};
|
|
||||||
fn get_all_single_types(
|
|
||||||
types: &mut Vec<VType>,
|
|
||||||
) -> Vec<Vec<VSingleType>>
|
|
||||||
{
|
|
||||||
if types.is_empty() {
|
|
||||||
vec![]
|
|
||||||
} else if types.len() == 1 {
|
|
||||||
vec![types[0].types.clone()]
|
|
||||||
} else {
|
|
||||||
let last = types.pop().unwrap();
|
|
||||||
let o = get_all_single_types(types);
|
|
||||||
let mut out = Vec::with_capacity(
|
|
||||||
o.len() * last.types.len(),
|
|
||||||
);
|
|
||||||
for other in o {
|
|
||||||
for t in &last.types {
|
|
||||||
let mut vec = other.clone();
|
|
||||||
vec.push(t.clone());
|
|
||||||
out.push(vec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
types.push(last);
|
|
||||||
out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for t in get_all_single_types(&mut args) {
|
|
||||||
fn_types.push((t, out.clone()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Some(')') => break,
|
Some(')') => break,
|
||||||
Some(other) => {
|
Some(other) => {
|
||||||
|
3
mers/tests/destructuring_assignment.mers
Normal file
3
mers/tests/destructuring_assignment.mers
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[a, [b, c]] := [1, ["str", 12.5]]
|
||||||
|
|
||||||
|
a == 1 && b == "str" && c == 12.5
|
3
mers/tests/force_output_type.mers
Normal file
3
mers/tests/force_output_type.mers
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fn plus(a int, b int) -> int { a + b }
|
||||||
|
|
||||||
|
10.plus(20) == 30
|
18
mers/tests/functions_double_definitions.mers
Normal file
18
mers/tests/functions_double_definitions.mers
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
type person [string int]
|
||||||
|
fn name(p person/&person) p.0
|
||||||
|
fn age(p person/&person) p.1
|
||||||
|
|
||||||
|
type village [[float float] string]
|
||||||
|
fn name(v village/&village) v.1
|
||||||
|
fn location(v village/&village) v.0
|
||||||
|
fn x_coordinate(v village/&village) v.0.0
|
||||||
|
fn y_coordinate(v village/&village) v.0.1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
customer := ["Max M.", 43]
|
||||||
|
home_town := [[12.3, 5.09], "Maxburg"]
|
||||||
|
|
||||||
|
customer.name() == "Max M."
|
||||||
|
&& home_town.name() == "Maxburg"
|
||||||
|
&& home_town.location() == [12.3, 5.09]
|
10
mers/tests/get_ref.mers
Normal file
10
mers/tests/get_ref.mers
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
list := [1 2 3 4 5 6 7 8 9 ...]
|
||||||
|
|
||||||
|
// calling get on an &list will get a reference
|
||||||
|
&list.get(2).assume1() = 24
|
||||||
|
// calling get on a list will get a value
|
||||||
|
should_not_be_changeable := list.get(3).assume1()
|
||||||
|
&should_not_be_changeable = 24
|
||||||
|
|
||||||
|
list.get(2) == [24]
|
||||||
|
&& list.get(3) != [24]
|
7
mers/tests/macro.mers
Normal file
7
mers/tests/macro.mers
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
val := !(mers {
|
||||||
|
"macro returned value"
|
||||||
|
})
|
||||||
|
|
||||||
|
val := !(mers "my_macro.mers")
|
||||||
|
|
||||||
|
true
|
9
mers/tests/modify_variable.mers
Normal file
9
mers/tests/modify_variable.mers
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// NOTE: Might change, but this is the current state of things
|
||||||
|
x := 10
|
||||||
|
t := thread(() {
|
||||||
|
sleep(0.25)
|
||||||
|
x
|
||||||
|
})
|
||||||
|
&x = 20 // -> 20 20 because it modifies the original variable x
|
||||||
|
// x := 20 // -> 10 20 because it shadows the original variable x
|
||||||
|
t.await() == x
|
1
mers/tests/my_macro.mers
Normal file
1
mers/tests/my_macro.mers
Normal file
@ -0,0 +1 @@
|
|||||||
|
true
|
@ -14,10 +14,13 @@ fn run_all() {
|
|||||||
eprintln!("Checking {}", file_name);
|
eprintln!("Checking {}", file_name);
|
||||||
let mut file = File::new(fs::read_to_string(file.path()).unwrap(), file.path());
|
let mut file = File::new(fs::read_to_string(file.path()).unwrap(), file.path());
|
||||||
// has to return true, otherwise the test will fail
|
// has to return true, otherwise the test will fail
|
||||||
assert!(matches!(
|
assert!(
|
||||||
|
matches!(
|
||||||
parse::parse(&mut file).unwrap().run(vec![]).inner_cloned(),
|
parse::parse(&mut file).unwrap().run(vec![]).inner_cloned(),
|
||||||
VDataEnum::Bool(true)
|
VDataEnum::Bool(true)
|
||||||
));
|
),
|
||||||
|
"{file_name} didn't return true!"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
38
mers/tests/thread_NOCHECK_ONLY_COMPILE.mers
Normal file
38
mers/tests/thread_NOCHECK_ONLY_COMPILE.mers
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// this is true by default so the example doesn't finish too quickly or too slowly depending on your hardware.
|
||||||
|
// you can set it to false and tweak the max value for a more authentic cpu-heavy workload.
|
||||||
|
fake_delay := true
|
||||||
|
|
||||||
|
// this will be shared between the two threads to report the progress in percent (0-100%).
|
||||||
|
progress := 0
|
||||||
|
|
||||||
|
// an anonymous function that sums all numbers from 0 to max.
|
||||||
|
// it captures the progress variable and uses it to report its status to the main thread, which will periodically print the current progress.
|
||||||
|
// once done, it returns a string with the sum of all numbers.
|
||||||
|
calculator := (max int) {
|
||||||
|
sum := 0
|
||||||
|
for i max {
|
||||||
|
i := i + 1
|
||||||
|
// println("i: {0} s: {1}".format(i.to_string() sum.to_string()))
|
||||||
|
&sum = sum + i
|
||||||
|
// if fake_delay sleep(1)
|
||||||
|
&progress = i * 100 / max
|
||||||
|
}
|
||||||
|
"the sum of all numbers from 0 to {0} is {1}!".format(max.to_string() sum.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
// start the thread. if fake_delay is true, calculate 1+2+3+4+5+6+7+8+9+10. if fake_delay is false, count up to some ridiculously large number.
|
||||||
|
slow_calculation_thread := calculator.thread(if fake_delay 10 else 20000000)
|
||||||
|
|
||||||
|
// every second, print the progress. once it reaches 100%, stop
|
||||||
|
loop {
|
||||||
|
sleep(1)
|
||||||
|
println("{0}%".format(progress.to_string()))
|
||||||
|
progress == 100 // break from the loop
|
||||||
|
}
|
||||||
|
|
||||||
|
// use await() to get the result from the thread. if the thread is still running, this will block until the thread finishes.
|
||||||
|
result := slow_calculation_thread.await()
|
||||||
|
|
||||||
|
println("Thread finished, result: {0}".format(result))
|
||||||
|
|
||||||
|
true
|
Loading…
Reference in New Issue
Block a user