This commit is contained in:
parent
d1a9bb86c7
commit
67671ce475
|
@ -50,10 +50,10 @@ If COL is a list, `first` will consume from index 0 and will continue until eith
|
||||||
satisfies the predicate, or until the end of the list is reached. If COL is an iterator, `first` will continue
|
satisfies the predicate, or until the end of the list is reached. If COL is an iterator, `first` will continue
|
||||||
consuming values until a value satisfying the predicate is found, or until the iterator is exhausted.
|
consuming values until a value satisfying the predicate is found, or until the iterator is exhausted.
|
||||||
|
|
||||||
### foreach
|
### for
|
||||||
|
|
||||||
```
|
```
|
||||||
foreach COL BLOCK
|
for COL BLOCK
|
||||||
```
|
```
|
||||||
|
|
||||||
Iterates BLOCK over every element of the sequence.
|
Iterates BLOCK over every element of the sequence.
|
||||||
|
@ -63,16 +63,16 @@ If COL is a hash, BLOCK receives both the key and value of each element.
|
||||||
|
|
||||||
BLOCK can call `break` and `continue` which will exit out of the loop, or jump to the start of the next iteration
|
BLOCK can call `break` and `continue` which will exit out of the loop, or jump to the start of the next iteration
|
||||||
respectively.
|
respectively.
|
||||||
The return value of `foreach` will be the result of the last iteration, unless `break` is called with a value.
|
The return value of `for` will be the result of the last iteration, unless `break` is called with a value.
|
||||||
|
|
||||||
This is implemented as a macro but can be used in a pipeline.
|
This is implemented as a macro but can be used in a pipeline.
|
||||||
|
|
||||||
```
|
```
|
||||||
foreach [1 2 3] { |e|
|
for [1 2 3] { |e|
|
||||||
echo "Element = $e"
|
echo "Element = $e"
|
||||||
}
|
}
|
||||||
|
|
||||||
[a:"one" b:"two"] | foreach { |k v|
|
[a:"one" b:"two"] | for { |k v|
|
||||||
echo "Key $k = $v"
|
echo "Key $k = $v"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -167,7 +167,7 @@ If FROM and TO are equal, or if FROM is unspecified and TO is 0, then the sequen
|
||||||
If -inc is specified, then the sequence will step towards TO inclusively.
|
If -inc is specified, then the sequence will step towards TO inclusively.
|
||||||
|
|
||||||
```
|
```
|
||||||
foreach (seq 5) { |i|
|
for (seq 5) { |i|
|
||||||
echo $i
|
echo $i
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -182,3 +182,11 @@ Sets the value of variable NAME to VALUE. Any variable with NAME will be checked
|
||||||
within the scope first, including any parent scopes, before a new variable is defined.
|
within the scope first, including any parent scopes, before a new variable is defined.
|
||||||
Any new variables will only be defined with the current scope.
|
Any new variables will only be defined with the current scope.
|
||||||
|
|
||||||
|
### set!
|
||||||
|
|
||||||
|
```
|
||||||
|
set! NAME VALUE
|
||||||
|
```
|
||||||
|
|
||||||
|
Sets the value of variable NAME to VALUE. VALUE must be non-null otherwise `set!` will raise an error.
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,7 @@ func New(opts ...InstOption) *Inst {
|
||||||
rootEC.addCmd("error", invokableFunc(errorBuiltin))
|
rootEC.addCmd("error", invokableFunc(errorBuiltin))
|
||||||
|
|
||||||
rootEC.addMacro("if", macroFunc(ifBuiltin))
|
rootEC.addMacro("if", macroFunc(ifBuiltin))
|
||||||
rootEC.addMacro("foreach", macroFunc(foreachBuiltin))
|
rootEC.addMacro("for", macroFunc(foreachBuiltin))
|
||||||
rootEC.addMacro("proc", macroFunc(procBuiltin))
|
rootEC.addMacro("proc", macroFunc(procBuiltin))
|
||||||
rootEC.addMacro("try", macroFunc(tryBuiltin))
|
rootEC.addMacro("try", macroFunc(tryBuiltin))
|
||||||
|
|
||||||
|
|
|
@ -179,7 +179,7 @@ func TestInst_MissingVarHandler(t *testing.T) {
|
||||||
|
|
||||||
var parseComments1 = `
|
var parseComments1 = `
|
||||||
proc lookup { |file|
|
proc lookup { |file|
|
||||||
foreach { |toks|
|
for { |toks|
|
||||||
}
|
}
|
||||||
# this use to fail
|
# this use to fail
|
||||||
}
|
}
|
||||||
|
@ -187,7 +187,7 @@ var parseComments1 = `
|
||||||
|
|
||||||
var parseComments2 = `
|
var parseComments2 = `
|
||||||
proc lookup { |file|
|
proc lookup { |file|
|
||||||
foreach { |toks|
|
for { |toks|
|
||||||
}
|
}
|
||||||
|
|
||||||
# this use to fail
|
# this use to fail
|
||||||
|
@ -199,7 +199,7 @@ var parseComments2 = `
|
||||||
|
|
||||||
var parseComments3 = `
|
var parseComments3 = `
|
||||||
proc lookup { |file|
|
proc lookup { |file|
|
||||||
foreach { |toks|
|
for { |toks|
|
||||||
}
|
}
|
||||||
|
|
||||||
# this use to fail
|
# this use to fail
|
||||||
|
@ -211,7 +211,7 @@ var parseComments3 = `
|
||||||
|
|
||||||
var parseComments4 = `
|
var parseComments4 = `
|
||||||
proc lookup { |file|
|
proc lookup { |file|
|
||||||
foreach { |toks|
|
for { |toks|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
# this use to fail`
|
# this use to fail`
|
||||||
|
|
|
@ -129,7 +129,7 @@ func TestBuiltins_Echo(t *testing.T) {
|
||||||
{desc: "interpolated string 3", expr: `echo "separate\nlines\n\tand tabs"`, want: "separate\nlines\n\tand tabs\n"},
|
{desc: "interpolated string 3", expr: `echo "separate\nlines\n\tand tabs"`, want: "separate\nlines\n\tand tabs\n"},
|
||||||
{desc: "interpolated string 4", expr: `set what "Hello" ; set where "world" ; echo "$what, $where"`, want: "Hello, world\n"},
|
{desc: "interpolated string 4", expr: `set what "Hello" ; set where "world" ; echo "$what, $where"`, want: "Hello, world\n"},
|
||||||
{desc: "interpolated string 5", expr: `
|
{desc: "interpolated string 5", expr: `
|
||||||
foreach [123 "foo" true ()] { |x|
|
for [123 "foo" true ()] { |x|
|
||||||
echo "[[$x]]"
|
echo "[[$x]]"
|
||||||
}
|
}
|
||||||
`, want: "[[123]]\n[[foo]]\n[[true]]\n[[]]\n"},
|
`, want: "[[123]]\n[[foo]]\n[[true]]\n[[]]\n"},
|
||||||
|
@ -210,8 +210,8 @@ func TestBuiltins_If(t *testing.T) {
|
||||||
{desc: "compressed else", expr: `if $x { echo "true" } else { echo "false" }`, want: "false\n(nil)\n"},
|
{desc: "compressed else", expr: `if $x { echo "true" } else { echo "false" }`, want: "false\n(nil)\n"},
|
||||||
{desc: "compressed if", expr: `if $x { echo "x" } elif $y { echo "y" } else { echo "false" }`, want: "false\n(nil)\n"},
|
{desc: "compressed if", expr: `if $x { echo "x" } elif $y { echo "y" } else { echo "false" }`, want: "false\n(nil)\n"},
|
||||||
{desc: "if of itr 1", expr: `set i (itr) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
{desc: "if of itr 1", expr: `set i (itr) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
||||||
{desc: "if of itr 2", expr: `set i (itr) ; foreach (seq 1) { head $i } ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
{desc: "if of itr 2", expr: `set i (itr) ; for (seq 1) { head $i } ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
||||||
{desc: "if of itr 3", expr: `set i (itr) ; foreach (seq 3) { head $i } ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"},
|
{desc: "if of itr 3", expr: `set i (itr) ; for (seq 3) { head $i } ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"},
|
||||||
{desc: "if of itr 4", expr: `set i (itr | map { |x| add 2 $x }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
{desc: "if of itr 4", expr: `set i (itr | map { |x| add 2 $x }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
||||||
{desc: "if of itr 5", expr: `set i (itr | filter { |x| () }) ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"},
|
{desc: "if of itr 5", expr: `set i (itr | filter { |x| () }) ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"},
|
||||||
{desc: "if of itr 6", expr: `set i (itr | filter { |x| 1 }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
{desc: "if of itr 6", expr: `set i (itr | filter { |x| 1 }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
||||||
|
@ -430,26 +430,26 @@ func TestBuiltins_Try(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuiltins_ForEach(t *testing.T) {
|
func TestBuiltins_For(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
desc string
|
desc string
|
||||||
expr string
|
expr string
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{desc: "iterate over list 1", expr: `
|
{desc: "iterate over list 1", expr: `
|
||||||
foreach ["1" "2" "3"] { |v|
|
for ["1" "2" "3"] { |v|
|
||||||
echo $v
|
echo $v
|
||||||
}`, want: "1\n2\n3\n(nil)\n"},
|
}`, want: "1\n2\n3\n(nil)\n"},
|
||||||
{desc: "iterate over list 2",
|
{desc: "iterate over list 2",
|
||||||
expr: `foreach ["1" "2" "3"] echo`,
|
expr: `for ["1" "2" "3"] echo`,
|
||||||
want: "1\n2\n3\n(nil)\n"},
|
want: "1\n2\n3\n(nil)\n"},
|
||||||
// TODO: hash is not sorted, so need to find a way to sort it
|
// TODO: hash is not sorted, so need to find a way to sort it
|
||||||
{desc: "iterate over map 1", expr: `
|
{desc: "iterate over map 1", expr: `
|
||||||
foreach [a:"1"] { |k v| echo $k "=" $v }`, want: "a=1\n(nil)\n"},
|
for [a:"1"] { |k v| echo $k "=" $v }`, want: "a=1\n(nil)\n"},
|
||||||
{desc: "iterate over map 2", expr: `
|
{desc: "iterate over map 2", expr: `
|
||||||
foreach [a:"1"] echo`, want: "a1\n(nil)\n"},
|
for [a:"1"] echo`, want: "a1\n(nil)\n"},
|
||||||
{desc: "iterate via pipe", expr: `["2" "4" "6"] | foreach { |x| echo $x }`, want: "2\n4\n6\n(nil)\n"},
|
{desc: "iterate via pipe", expr: `["2" "4" "6"] | for { |x| echo $x }`, want: "2\n4\n6\n(nil)\n"},
|
||||||
{desc: "iterate from iterator 1", expr: `itr | foreach { |x| echo $x }`, want: "1\n2\n3\n(nil)\n"},
|
{desc: "iterate from iterator 1", expr: `itr | for { |x| echo $x }`, want: "1\n2\n3\n(nil)\n"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -473,29 +473,29 @@ func TestBuiltins_Break(t *testing.T) {
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{desc: "break unconditionally returning nothing", expr: `
|
{desc: "break unconditionally returning nothing", expr: `
|
||||||
foreach ["1" "2" "3"] { |v|
|
for ["1" "2" "3"] { |v|
|
||||||
break
|
break
|
||||||
echo $v
|
echo $v
|
||||||
}`, want: "(nil)\n"},
|
}`, want: "(nil)\n"},
|
||||||
{desc: "break conditionally returning nothing", expr: `
|
{desc: "break conditionally returning nothing", expr: `
|
||||||
foreach ["1" "2" "3"] { |v|
|
for ["1" "2" "3"] { |v|
|
||||||
echo $v
|
echo $v
|
||||||
if (eq $v "2") { break }
|
if (eq $v "2") { break }
|
||||||
}`, want: "1\n2\n(nil)\n"},
|
}`, want: "1\n2\n(nil)\n"},
|
||||||
{desc: "break inner loop only returning nothing", expr: `
|
{desc: "break inner loop only returning nothing", expr: `
|
||||||
foreach ["a" "b"] { |u|
|
for ["a" "b"] { |u|
|
||||||
foreach ["1" "2" "3"] { |v|
|
for ["1" "2" "3"] { |v|
|
||||||
echo $u $v
|
echo $u $v
|
||||||
if (eq $v "2") { break }
|
if (eq $v "2") { break }
|
||||||
}
|
}
|
||||||
}`, want: "a1\na2\nb1\nb2\n(nil)\n"},
|
}`, want: "a1\na2\nb1\nb2\n(nil)\n"},
|
||||||
{desc: "break returning value 1", expr: `
|
{desc: "break returning value 1", expr: `
|
||||||
echo (foreach ["1" "2" "3"] { |v|
|
echo (for ["1" "2" "3"] { |v|
|
||||||
echo $v
|
echo $v
|
||||||
if (eq $v "2") { break "hello" }
|
if (eq $v "2") { break "hello" }
|
||||||
})`, want: "1\n2\nhello\n(nil)\n"},
|
})`, want: "1\n2\nhello\n(nil)\n"},
|
||||||
{desc: "break returning value 2", expr: `
|
{desc: "break returning value 2", expr: `
|
||||||
echo (foreach (itr) { |v|
|
echo (for (itr) { |v|
|
||||||
echo $v
|
echo $v
|
||||||
if (eq $v 2) { break "hello" }
|
if (eq $v 2) { break "hello" }
|
||||||
})`, want: "1\n2\nhello\n(nil)\n"},
|
})`, want: "1\n2\nhello\n(nil)\n"},
|
||||||
|
@ -522,20 +522,20 @@ func TestBuiltins_Continue(t *testing.T) {
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{desc: "continue unconditionally", expr: `
|
{desc: "continue unconditionally", expr: `
|
||||||
foreach ["1" "2" "3"] { |v|
|
for ["1" "2" "3"] { |v|
|
||||||
echo $v "s"
|
echo $v "s"
|
||||||
continue
|
continue
|
||||||
echo $v "e"
|
echo $v "e"
|
||||||
}`, want: "1s\n2s\n3s\n(nil)\n"},
|
}`, want: "1s\n2s\n3s\n(nil)\n"},
|
||||||
{desc: "conditionally conditionally", expr: `
|
{desc: "conditionally conditionally", expr: `
|
||||||
foreach ["1" "2" "3"] { |v|
|
for ["1" "2" "3"] { |v|
|
||||||
echo $v "s"
|
echo $v "s"
|
||||||
if (eq $v "2") { continue }
|
if (eq $v "2") { continue }
|
||||||
echo $v "e"
|
echo $v "e"
|
||||||
}`, want: "1s\n1e\n2s\n3s\n3e\n(nil)\n"},
|
}`, want: "1s\n1e\n2s\n3s\n3e\n(nil)\n"},
|
||||||
{desc: "continue inner loop only", expr: `
|
{desc: "continue inner loop only", expr: `
|
||||||
foreach ["a" "b"] { |u|
|
for ["a" "b"] { |u|
|
||||||
foreach ["1" "2" "3"] { |v|
|
for ["1" "2" "3"] { |v|
|
||||||
if (eq $v "2") { continue }
|
if (eq $v "2") { continue }
|
||||||
echo $u $v
|
echo $u $v
|
||||||
}
|
}
|
||||||
|
@ -726,7 +726,7 @@ func TestBuiltins_Return(t *testing.T) {
|
||||||
`, want: "Greet the\nHello, moon\n(nil)\n"},
|
`, want: "Greet the\nHello, moon\n(nil)\n"},
|
||||||
{desc: "return in loop", expr: `
|
{desc: "return in loop", expr: `
|
||||||
proc countdown { |nums|
|
proc countdown { |nums|
|
||||||
foreach $nums { |n|
|
for $nums { |n|
|
||||||
echo $n
|
echo $n
|
||||||
if (eq $n 3) {
|
if (eq $n 3) {
|
||||||
return "abort"
|
return "abort"
|
||||||
|
@ -751,7 +751,7 @@ func TestBuiltins_Return(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
proc test-thing {
|
proc test-thing {
|
||||||
foreach [1 2 3] { |x|
|
for [1 2 3] { |x|
|
||||||
do-thing {
|
do-thing {
|
||||||
echo $x
|
echo $x
|
||||||
}
|
}
|
||||||
|
@ -766,7 +766,7 @@ func TestBuiltins_Return(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
proc test-thing {
|
proc test-thing {
|
||||||
foreach [1 2 3] { |x|
|
for [1 2 3] { |x|
|
||||||
do-thing (proc {
|
do-thing (proc {
|
||||||
echo $x
|
echo $x
|
||||||
})
|
})
|
||||||
|
@ -781,7 +781,7 @@ func TestBuiltins_Return(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
proc test-thing {
|
proc test-thing {
|
||||||
foreach [1 2 3] { |x|
|
for [1 2 3] { |x|
|
||||||
set myClosure (proc { echo $x })
|
set myClosure (proc { echo $x })
|
||||||
do-thing $myClosure
|
do-thing $myClosure
|
||||||
}
|
}
|
||||||
|
@ -800,7 +800,7 @@ func TestBuiltins_Return(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (test-thing) { |y| call $y }
|
for (test-thing) { |y| call $y }
|
||||||
`, want: "1\n2\n3\n(nil)\n"},
|
`, want: "1\n2\n3\n(nil)\n"},
|
||||||
{desc: "check closure 5", expr: `
|
{desc: "check closure 5", expr: `
|
||||||
proc do-thing { |p|
|
proc do-thing { |p|
|
||||||
|
@ -815,7 +815,7 @@ func TestBuiltins_Return(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
set hello "xx"
|
set hello "xx"
|
||||||
foreach (test-thing) { |y| call $y ; echo $hello }
|
for (test-thing) { |y| call $y ; echo $hello }
|
||||||
`, want: "1\nxx\n2\nxx\n3\nxx\n(nil)\n"},
|
`, want: "1\nxx\n2\nxx\n3\nxx\n(nil)\n"},
|
||||||
{desc: "check closure 7", expr: `
|
{desc: "check closure 7", expr: `
|
||||||
proc do-thing { |p|
|
proc do-thing { |p|
|
||||||
|
@ -832,7 +832,7 @@ func TestBuiltins_Return(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
set hello "xx"
|
set hello "xx"
|
||||||
foreach (test-thing) { |y| call $y ; echo $hello }
|
for (test-thing) { |y| call $y ; echo $hello }
|
||||||
`, want: "3\nxx\n3\nxx\n3\nxx\n(nil)\n"},
|
`, want: "3\nxx\n3\nxx\n3\nxx\n(nil)\n"},
|
||||||
{desc: "check closure 7", expr: `
|
{desc: "check closure 7", expr: `
|
||||||
proc do-thing { |p|
|
proc do-thing { |p|
|
||||||
|
@ -850,7 +850,7 @@ func TestBuiltins_Return(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
set hello "xx"
|
set hello "xx"
|
||||||
foreach (test-thing) { |y| call $y ; echo $hello }
|
for (test-thing) { |y| call $y ; echo $hello }
|
||||||
`, want: "1\nxx\n2\nxx\n3\nxx\n(nil)\n"},
|
`, want: "1\nxx\n2\nxx\n3\nxx\n(nil)\n"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -965,7 +965,7 @@ func TestBuiltins_Map(t *testing.T) {
|
||||||
set add2 (proc { |x| add $x 2 })
|
set add2 (proc { |x| add $x 2 })
|
||||||
|
|
||||||
set l (itr | map $add2)
|
set l (itr | map $add2)
|
||||||
foreach $l { |x| echo $x }
|
for $l { |x| echo $x }
|
||||||
`, want: "3\n4\n5\n(nil)\n"},
|
`, want: "3\n4\n5\n(nil)\n"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1218,7 +1218,7 @@ func TestBuiltins_Filter(t *testing.T) {
|
||||||
}},
|
}},
|
||||||
{desc: "filter map 3", expr: `filter [alpha:"hello" bravo:"world"] { |k v| eq $v "alpha" }`, want: map[string]any{}},
|
{desc: "filter map 3", expr: `filter [alpha:"hello" bravo:"world"] { |k v| eq $v "alpha" }`, want: map[string]any{}},
|
||||||
|
|
||||||
{desc: "filter itr 1", expr: `set s "" ; itr | filter { |x| ne $x 2 } | foreach { |x| set s "$s $x" }; $s`, want: " 1 3"},
|
{desc: "filter itr 1", expr: `set s "" ; itr | filter { |x| ne $x 2 } | for { |x| set s "$s $x" }; $s`, want: " 1 3"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
|
@ -187,7 +187,7 @@ func TestInst_SetBuiltin(t *testing.T) {
|
||||||
wantOut string
|
wantOut string
|
||||||
}{
|
}{
|
||||||
{descr: "return as is", expr: `countTo3`, want: []string{"1", "2", "3"}},
|
{descr: "return as is", expr: `countTo3`, want: []string{"1", "2", "3"}},
|
||||||
{descr: "iterate over", expr: `foreach (countTo3) { |x| echo $x }`, wantOut: "1\n2\n3\n"},
|
{descr: "iterate over", expr: `for (countTo3) { |x| echo $x }`, wantOut: "1\n2\n3\n"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -222,7 +222,7 @@ func TestInst_SetBuiltin(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{descr: "return as is", expr: `getOpaque`, wantErr: false},
|
{descr: "return as is", expr: `getOpaque`, wantErr: false},
|
||||||
{descr: "carry around ok", expr: `set x (getOpaque) ; $x`, wantErr: false},
|
{descr: "carry around ok", expr: `set x (getOpaque) ; $x`, wantErr: false},
|
||||||
{descr: "iterate over", expr: `foreach (countTo3) { |x| echo $x }`, wantErr: true},
|
{descr: "iterate over", expr: `for (countTo3) { |x| echo $x }`, wantErr: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
Loading…
Reference in a new issue