Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
T
typon-concurrency
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Xavier Thompson
typon-concurrency
Commits
c307f373
Commit
c307f373
authored
Mar 22, 2024
by
Tom Niget
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Work on mutexes (wip)
parent
e2d96f04
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
154 additions
and
94 deletions
+154
-94
typon/include/python/builtins.hpp
typon/include/python/builtins.hpp
+18
-4
typon/include/python/builtins/mutex.hpp
typon/include/python/builtins/mutex.hpp
+41
-38
typon/include/python/builtins/range.hpp
typon/include/python/builtins/range.hpp
+2
-1
typon/include/python/builtins/slice.hpp
typon/include/python/builtins/slice.hpp
+10
-12
typon/include/python/builtins/str.hpp
typon/include/python/builtins/str.hpp
+8
-8
typon/include/python/socket.hpp
typon/include/python/socket.hpp
+10
-11
typon/include/python/time.hpp
typon/include/python/time.hpp
+3
-3
typon/trans/tests/actor_inc.py
typon/trans/tests/actor_inc.py
+33
-0
typon/trans/tests/actors.py
typon/trans/tests/actors.py
+0
-4
typon/trans/tests/mutex_inc.py
typon/trans/tests/mutex_inc.py
+2
-2
typon/trans/transpiler/phases/emit_cpp/class_.py
typon/trans/transpiler/phases/emit_cpp/class_.py
+1
-1
typon/trans/transpiler/phases/emit_cpp/expr.py
typon/trans/transpiler/phases/emit_cpp/expr.py
+18
-7
typon/trans/transpiler/phases/typing/expr.py
typon/trans/transpiler/phases/typing/expr.py
+3
-1
typon/trans/transpiler/phases/typing/stdlib.py
typon/trans/transpiler/phases/typing/stdlib.py
+2
-2
typon/trans/transpiler/phases/typing/types.py
typon/trans/transpiler/phases/typing/types.py
+3
-0
No files found.
typon/include/python/builtins.hpp
View file @
c307f373
...
...
@@ -12,8 +12,10 @@
#include <string>
#include <python/basedef.hpp>
#include <typon/typon.hpp>
#ifdef __cpp_lib_unreachable
#include <utility>
[[
noreturn
]]
inline
void
TYPON_UNREACHABLE
()
{
std
::
unreachable
();
}
...
...
@@ -62,14 +64,13 @@ template <PySmartPtr T> struct RealType<T> {
namespace
typon
{
//class TyNone {};
//
class TyNone {};
using
TyNone
=
std
::
nullopt_t
;
auto
None
=
std
::
nullopt
;
}
// namespace typon
static
constexpr
auto
None
=
typon
::
None
;
// typon_len
...
...
@@ -127,7 +128,6 @@ std::ostream &operator<<(std::ostream &os, std::optional<T> const &opt) {
return
opt
?
os
<<
opt
.
value
()
:
os
<<
"None"
;
}
#define system_error(err, message) \
do { \
puts(message); \
...
...
@@ -140,12 +140,12 @@ std::ostream &operator<<(std::ostream &os, std::optional<T> const &opt) {
#include "builtins/dict.hpp"
#include "builtins/int.hpp"
#include "builtins/list.hpp"
#include "builtins/mutex.hpp"
#include "builtins/print.hpp"
#include "builtins/range.hpp"
#include "builtins/set.hpp"
#include "builtins/slice.hpp"
#include "builtins/str.hpp"
#include "builtins/mutex.hpp"
auto
is_cpp
()
{
return
typon
::
TyBool
(
true
);
}
...
...
@@ -421,8 +421,22 @@ template <auto IDX, typename T> auto constant_get(T &&val) {
}
}
struct
{
using
has_sync
=
std
::
true_type
;
template
<
typename
T
>
auto
typon
$$
sync
(
T
value
)
const
->
decltype
(
referencemodel
::
Rc
(
future
(
std
::
declval
<
Task
<
T
>>
())))
{
//return referencemodel::Rc(future(task));
throw
;
}
template
<
typename
Task
>
auto
operator
()(
Task
task
)
const
->
typon
::
Task
<
decltype
(
referencemodel
::
Rc
(
future
(
std
::
move
(
task
))))
>
{
co_return
referencemodel
::
Rc
(
co_await
future
(
std
::
move
(
task
)));
}
}
static
constexpr
future_stdlib
{};
}
;
// namespace typon
...
...
typon/include/python/builtins/mutex.hpp
View file @
c307f373
...
...
@@ -7,59 +7,62 @@
namespace
view
=
std
::
views
;
#include <python/basedef.hpp>
#include "print.hpp"
namespace
typon
{
using
namespace
referencemodel
;
template
<
typename
_Base0
=
object
>
template
<
typename
_Base0
=
object
>
struct
TyCell__oo
:
classtype
<
_Base0
,
TyCell__oo
<>>
{
static
constexpr
std
::
string_view
name
=
"Cell"
;
static
constexpr
std
::
string_view
name
=
"Cell"
;
template
<
typename
T
>
struct
Obj
:
instance
<
TyCell__oo
<>
,
Obj
<
T
>>
{
T
val
;
template
<
typename
T
>
struct
Obj
:
instance
<
TyCell__oo
<>
,
Obj
<
T
>>
{
T
val
;
Obj
()
=
default
;
Obj
(
Obj
const
&
)
=
delete
;
};
Obj
()
=
default
;
Obj
(
Obj
const
&
)
=
delete
;
};
template
<
typename
T
>
auto
operator
()(
T
val
)
const
{
return
Obj
<
T
>
{
val
};
}
template
<
typename
T
>
auto
operator
()(
T
val
)
const
{
return
Obj
<
T
>
{
val
};
}
};
template
<
typename
_Base0
=
object
>
template
<
typename
_Base0
=
object
>
struct
TyMutex__oo
:
classtype
<
_Base0
,
TyMutex__oo
<>>
{
static
constexpr
std
::
string_view
name
=
"Mutex"
;
template
<
typename
T
>
struct
Obj
:
instance
<
TyMutex__oo
<>
,
Obj
<
T
>>
{
typon
::
Mutex
mutex
;
TyCell__oo
<>::
Obj
<
T
>
cell
;
};
struct
:
method
{
auto
operator
()(
auto
self
,
auto
callback
)
->
typon
::
Task
<
TyNone
>
const
{
auto
lock
=
dot
(
self
,
mutex
).
lock
();
co_await
lock
;
co_await
callback
(
ref
(
dot
(
self
,
cell
))
);
co_return
None
;
}
}
static
constexpr
when
{}
;
template
<
typename
T
>
auto
operator
()(
T
val
)
const
{
auto
obj
=
referencemodel
::
meta
::
arc
<
Obj
<
T
>>
(
std
::
in_place
);
dot
(
obj
,
cell
).
val
=
val
;
return
obj
;
static
constexpr
std
::
string_view
name
=
"Mutex"
;
template
<
typename
T
>
struct
Obj
:
instance
<
TyMutex__oo
<>
,
Obj
<
T
>>
{
typon
::
Mutex
mutex
;
TyCell__oo
<>::
Obj
<
T
>
cell
;
}
;
struct
:
method
{
using
has_sync
=
std
::
true_type
;
auto
typon
$$
sync
(
auto
,
auto
)
->
TyNone
const
{
return
None
;
}
auto
operator
()(
auto
_self
,
auto
callback
)
->
typon
::
Task
<
TyNone
>
const
{
print
(
"1"
_ps
);
auto
self
=
arc
(
_self
)
;
print
(
"2"
_ps
);
auto
lock
=
dot
(
self
,
mutex
).
lock
()
;
print
(
"3"
_ps
);
co_await
lock
;
print
(
"4"
_ps
);
co_await
callback
(
ref
(
dot
(
self
,
cell
))
);
print
(
"5"
_ps
)
;
co_return
None
;
}
}
static
constexpr
when
{};
template
<
typename
T
>
auto
operator
()(
T
val
)
const
{
auto
obj
=
referencemodel
::
meta
::
arc
<
Obj
<
T
>>
(
std
::
in_place
);
dot
(
obj
,
cell
).
val
=
val
;
return
obj
;
}
};
template
<
typename
T
>
using
ArcMutex
=
Arc
<
TyMutex__oo
<>::
template
Obj
<
T
>
>
;
template
<
typename
T
>
using
ArcMutex
=
Arc
<
TyMutex__oo
<>::
template
Obj
<
T
>
>
;
}
}
// namespace typon
static
constexpr
typon
::
TyMutex__oo
<>
Mutex
;
...
...
typon/include/python/builtins/range.hpp
View file @
c307f373
...
...
@@ -10,6 +10,7 @@
namespace
view
=
std
::
views
;
#include <python/basedef.hpp>
#include "int.hpp"
auto
stride
=
[](
int
n
)
{
return
[
s
=
-
1
,
n
](
auto
const
&
)
mutable
{
...
...
@@ -30,7 +31,7 @@ struct range_s : TyBuiltin<range_s> {
auto
End
=
Step
<
0
?
Begin
:
std
::
max
(
start
,
stop
);
return
view
::
iota
(
Begin
,
End
)
|
view
::
filter
(
stride
(
std
::
abs
(
Step
)))
|
view
::
transform
([
start
,
stop
](
std
::
size_t
i
)
{
return
start
<
stop
?
i
:
stop
-
(
i
-
start
);
return
typon
::
TyInt
(
start
<
stop
?
i
:
stop
-
(
i
-
start
)
);
});
}
...
...
typon/include/python/builtins/slice.hpp
View file @
c307f373
...
...
@@ -23,30 +23,28 @@ struct TySlice__oo : classtype<_Base0, TySlice__oo<>> {
Stop
stop
;
Step
step
;
Obj
(
Start
start
,
Stop
stop
,
Step
step
)
:
start
(
start
),
stop
(
stop
),
step
(
step
)
{}
Obj
(
Start
start
,
Stop
stop
,
Step
step
)
:
start
(
start
),
stop
(
stop
),
step
(
step
)
{}
};
template
<
typename
Stop
>
auto
operator
()(
Stop
stop
)
const
{
template
<
typename
Stop
>
auto
operator
()(
Stop
stop
)
const
{
return
Obj
<
TyNone
,
Stop
,
TyNone
>
{
None
,
stop
,
None
};
}
template
<
typename
Start
,
typename
Stop
>
auto
operator
()(
Start
start
,
Stop
stop
)
const
{
template
<
typename
Start
,
typename
Stop
>
auto
operator
()(
Start
start
,
Stop
stop
)
const
{
return
Obj
<
Start
,
Stop
,
TyNone
>
{
start
,
stop
,
None
};
}
}
template
<
typename
Start
,
typename
Stop
,
typename
Step
>
auto
operator
()(
Start
start
,
Stop
stop
,
Step
step
)
const
{
template
<
typename
Start
,
typename
Stop
,
typename
Step
>
auto
operator
()(
Start
start
,
Stop
stop
,
Step
step
)
const
{
return
Obj
<
Start
,
Stop
,
Step
>
{
start
,
stop
,
step
};
}
}
};
static
constexpr
TySlice__oo
<>
TySlice
{};
}
}
// namespace typon
/*struct TySlice {
TySlice() = default;
TySlice(const TySlice &) = default;
...
...
typon/include/python/builtins/str.hpp
View file @
c307f373
...
...
@@ -177,14 +177,14 @@ struct TyStr__oo : classtype<_Base0, TyStr__oo<>> {
}
static
constexpr
oo__len__oo
{};
// getitem
struct
:
method
{
auto
operator
()(
auto
self
,
auto
index
)
const
{
if
(
index
<
0
)
{
index
+=
self
->
value
.
size
();
}
return
Obj
(
self
->
value
[
index
]);
}
}
static
constexpr
oo__getitem__oo
{};
struct
:
method
{
auto
operator
()(
auto
self
,
auto
index
)
const
{
if
(
index
<
0
)
{
index
+=
self
->
value
.
size
();
}
return
Obj
(
self
->
value
[
index
]);
}
}
static
constexpr
oo__getitem__oo
{};
/*struct : method {
auto operator()(auto self, auto other) const {
...
...
typon/include/python/socket.hpp
View file @
c307f373
...
...
@@ -185,17 +185,16 @@ struct socket__oo : referencemodel::moduletype<socket__oo<>> {
err
!=
0
)
{
system_error
(
err
,
"getaddrinfo()"
);
}
auto
rlist
=
typon
::
TyList
({
// make tuple (family, type, proto, canonname, sockaddr)
// (int, int, int, str, str)
std
::
make_tuple
(
typon
::
TyInt
(
res
->
ai_family
),
typon
::
TyInt
(
res
->
ai_socktype
),
typon
::
TyInt
(
res
->
ai_protocol
),
typon
::
TyStr
(
res
->
ai_canonname
?
res
->
ai_canonname
:
""
),
typon
::
TyStr
(
res
->
ai_addr
?
res
->
ai_addr
->
sa_data
:
""
))
});
::
freeaddrinfo
(
res
);
return
rlist
;
auto
rlist
=
typon
::
TyList
(
{
// make tuple (family, type, proto, canonname, sockaddr)
// (int, int, int, str, str)
std
::
make_tuple
(
typon
::
TyInt
(
res
->
ai_family
),
typon
::
TyInt
(
res
->
ai_socktype
),
typon
::
TyInt
(
res
->
ai_protocol
),
typon
::
TyStr
(
res
->
ai_canonname
?
res
->
ai_canonname
:
""
),
typon
::
TyStr
(
res
->
ai_addr
?
res
->
ai_addr
->
sa_data
:
""
))});
::
freeaddrinfo
(
res
);
return
rlist
;
}
}
static
constexpr
getaddrinfo
{};
...
...
typon/include/python/time.hpp
View file @
c307f373
...
...
@@ -15,9 +15,9 @@ struct time__oo : referencemodel::moduletype<time__oo<>> {
struct
:
referencemodel
::
function
{
typon
::
Task
<
typon
::
TyNone
>
operator
()(
auto
duration
)
const
{
co_await
typon
::
io
::
sleep
(
std
::
chrono
::
seconds
(
duration
));
co_return
None
;
}
co_await
typon
::
io
::
sleep
(
std
::
chrono
::
seconds
(
duration
));
co_return
None
;
}
}
static
constexpr
sleep
{};
};
time__oo
<>
all
;
...
...
typon/trans/tests/actor_inc.py
0 → 100644
View file @
c307f373
from
time
import
sleep
class
Actor
[
T
]:
mutex
:
Mutex
[
T
]
def
__init__
(
self
,
val
:
T
):
self
.
mutex
=
Mutex
(
val
)
def
when
[
U
](
self
,
f
:
Callable
[[
Cell
[
T
]],
U
]):
return
future
(
lambda
:
self
.
mutex
.
when
(
f
))
def
inc
(
cell
):
print
(
"inc"
)
x
=
cell
.
val
sleep
(
1
)
cell
.
val
=
x
+
1
print
(
"current:"
,
cell
.
val
)
def
truc
():
print
(
"a"
)
a
=
Actor
(
0
)
print
(
"b"
)
for
i
in
range
(
10
):
print
(
"spawn"
,
i
)
a
.
when
(
inc
)
print
(
"spawned"
,
i
)
future
=
a
.
when
(
lambda
cell
:
print
(
"final:"
,
cell
.
val
))
# future.get()
print
(
"done"
)
if
__name__
==
"__main__"
:
print
(
"Actor:"
)
truc
()
\ No newline at end of file
typon/trans/tests/actors.py
View file @
c307f373
...
...
@@ -20,9 +20,7 @@ class Actor[T]:
mutex
:
Mutex
[
T
]
def
__init__
(
self
,
val
:
T
):
print
(
"l"
)
self
.
mutex
=
Mutex
(
val
)
print
(
"m"
)
# def when(self, f: Callable[[T], object]):
# return future(lambda: self.mutex.when(f))
...
...
@@ -31,9 +29,7 @@ def thing(x):
print
(
"hello"
,
x
)
if
__name__
==
"__main__"
:
print
(
"j"
)
act
=
Actor
(
123
)
print
(
"k"
)
act
.
mutex
.
when
(
thing
)
# class Actor[T]:
...
...
typon/trans/tests/mutex_inc.py
View file @
c307f373
...
...
@@ -22,13 +22,13 @@ def truc():
m
=
Mutex
(
0
)
for
_
in
range
(
10
):
fork
(
lambda
:
m
.
when
(
inc
))
sync
()
#
sync()
def
nomutex
():
t
=
Thing
(
0
)
for
_
in
range
(
10
):
fork
(
lambda
:
t
.
inc
())
sync
()
#
sync()
if
__name__
==
"__main__"
:
...
...
typon/trans/transpiler/phases/emit_cpp/class_.py
View file @
c307f373
...
...
@@ -64,7 +64,7 @@ def emit_class(name: str, node: ConcreteType) -> Iterable[str]:
yield
"template <"
if
node
.
generic_parent
.
parameters
:
yield
from
join
(
","
,
(
f"typename
{
name
}
"
for
name
in
node
.
generic_parent
.
parameters
))
yield
from
join
(
","
,
(
f"typename
{
p
.
name
}
"
for
p
in
node
.
generic_parent
.
parameters
))
yield
", typename... $T"
else
:
yield
"typename... $T, typename _Void = void"
...
...
typon/trans/transpiler/phases/emit_cpp/expr.py
View file @
c307f373
...
...
@@ -127,8 +127,11 @@ class ExpressionVisitor(NodeVisitor):
assert
len
(
node
.
args
)
==
1
arg
=
node
.
args
[
0
]
assert
isinstance
(
arg
,
ast
.
Lambda
)
fixed
=
node
.
func
.
id
if
fixed
==
"future"
:
# Temporary until we separate the namespaces
fixed
=
"future_stdlib"
if
self
.
generator
!=
CoroutineMode
.
SYNC
:
yield
f"co_await typon::
{
node
.
func
.
i
d
}
("
yield
f"co_await typon::
{
fixe
d
}
("
assert
isinstance
(
arg
.
body
,
ast
.
Call
)
yield
from
self
.
visit
(
arg
.
body
.
func
)
yield
"("
...
...
@@ -136,7 +139,14 @@ class ExpressionVisitor(NodeVisitor):
yield
")"
yield
")"
else
:
yield
from
self
.
visit
(
arg
.
body
)
match
node
.
func
.
id
:
case
"fork"
:
yield
from
self
.
visit
(
arg
.
body
)
# fork is transparent
case
"future"
:
yield
"typon::future_stdlib.typon$$sync("
yield
from
self
.
visit
(
arg
.
body
)
yield
")"
return
if
isinstance
(
node
.
func
,
ast
.
Name
)
and
node
.
func
.
id
==
"sync"
:
...
...
@@ -154,7 +164,8 @@ class ExpressionVisitor(NodeVisitor):
if
self
.
generator
!=
CoroutineMode
.
SYNC
:
nty
=
node
.
type
.
resolve
()
if
isinstance
(
nty
,
ResolvedConcreteType
)
and
(
nty
.
inherits
(
TY_FUTURE
)
or
(
#nty.inherits(TY_FUTURE) or
(
is_get
and
nty
.
inherits
(
TY_FORKED
)
)
):
...
...
@@ -175,7 +186,7 @@ class ExpressionVisitor(NodeVisitor):
if
is_get
and
node
.
func
.
value
.
type
.
inherits
(
TY_FUTURE
,
TY_FORKED
):
yield
"("
yield
from
self
.
visit
(
node
.
func
.
value
)
yield
")
.
get"
yield
")
->
get"
else
:
yield
from
self
.
visit
(
node
.
func
)
...
...
@@ -252,10 +263,10 @@ class ExpressionVisitor(NodeVisitor):
templ
,
args
,
_
=
self
.
process_args
(
node
.
args
)
yield
templ
yield
args
yield
"->"
yield
"->
typon::Task<
"
yield
from
self
.
visit
(
node
.
type
.
deref
().
return_type
)
yield
"{"
yield
"return"
yield
"
>
{"
yield
"
co_
return"
yield
from
self
.
visit
(
node
.
body
)
yield
";"
yield
"}"
...
...
typon/trans/transpiler/phases/typing/expr.py
View file @
c307f373
...
...
@@ -9,7 +9,7 @@ from transpiler.phases.typing.exceptions import ArgumentCountMismatchError, Type
from
transpiler.phases.typing.types
import
BaseType
,
TY_STR
,
TY_BOOL
,
TY_INT
,
TY_COMPLEX
,
TY_FLOAT
,
TY_NONE
,
\
ClassTypeType
,
ResolvedConcreteType
,
GenericType
,
CallableInstanceType
,
TY_LIST
,
TY_SET
,
TY_DICT
,
RuntimeValue
,
\
TypeVariable
,
TY_LAMBDA
,
TypeListType
,
MethodType
,
TY_TUPLE
,
GenericInstanceType
,
PROMISES
,
TRANSPARENT_PROMISES
,
\
TY_FORKED
,
TY_JOIN
,
TypeTupleType
,
TupleInstanceType
,
TY_TYPE
,
TY_SLICE
TY_FORKED
,
TY_JOIN
,
TypeTupleType
,
TupleInstanceType
,
TY_TYPE
,
TY_SLICE
,
BoundFuncTypeBase
from
transpiler.phases.typing.scope
import
ScopeKind
,
VarDecl
,
VarKind
from
transpiler.utils
import
linenodata
...
...
@@ -166,6 +166,8 @@ class ScoperExprVisitor(ScoperVisitor):
raise
TypeMismatchError
(
a
,
b
,
TypeMismatchKind
.
DIFFERENT_TYPE
)
if
not
ftype
.
is_native
:
if
isinstance
(
ftype
.
generic_parent
,
BoundFuncTypeBase
):
i
+=
1
pname
=
ftype
.
block_data
.
node
.
args
.
args
[
i
].
arg
ftype
.
block_data
.
scope
.
declare_local
(
pname
,
b
)
...
...
typon/trans/transpiler/phases/typing/stdlib.py
View file @
c307f373
...
...
@@ -16,7 +16,7 @@ from transpiler.phases.typing.scope import Scope, VarDecl, VarKind, ScopeKind
from
transpiler.phases.typing.types
import
BaseType
,
BuiltinGenericType
,
BuiltinType
,
create_builtin_generic_type
,
\
create_builtin_type
,
ConcreteType
,
GenericInstanceType
,
TypeListType
,
TypeTupleType
,
GenericParameter
,
\
GenericParameterKind
,
TypeVariable
,
ResolvedConcreteType
,
MemberDef
,
ClassTypeType
,
CallableInstanceType
,
\
MethodType
,
UniqueTypeMixin
,
GenericType
,
BlockData
,
TY_TASK
,
UserGenericType
,
UserType
MethodType
,
UniqueTypeMixin
,
GenericType
,
BlockData
,
TY_TASK
,
UserGenericType
,
UserType
,
BoundFuncTypeBase
from
transpiler.phases.utils
import
NodeVisitorSeq
def
visit_generic_item
(
...
...
@@ -254,7 +254,7 @@ class StdlibVisitor(NodeVisitorSeq):
if
cur_class_ref
is
not
None
:
def
remove_self
(
self
,
self_type
):
class
BoundFuncType
(
UniqueTypeMixin
,
GenericType
):
class
BoundFuncType
(
BoundFuncTypeBase
,
GenericType
):
def
name
(
self
)
->
str
:
return
f"BoundFuncType$
{
node
.
name
}
"
...
...
typon/trans/transpiler/phases/typing/types.py
View file @
c307f373
...
...
@@ -238,6 +238,9 @@ class UniqueTypeMixin:
def
contains_internal
(
self
,
other
:
"BaseType"
)
->
bool
:
return
self
==
other
class
BoundFuncTypeBase
(
UniqueTypeMixin
):
pass
@
dataclass
(
eq
=
False
)
class
BuiltinType
(
UniqueTypeMixin
,
ResolvedConcreteType
):
pass
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment