Skip to content

Commit f1cbf13

Browse files
committed
feat: do while loop
1 parent b998508 commit f1cbf13

File tree

6 files changed

+90
-2
lines changed

6 files changed

+90
-2
lines changed

src/lib/compiler.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,8 @@ export default class Compiler {
321321

322322
private whileCount: number = 0;
323323

324+
private doWhileCount: number = 0;
325+
324326
private forCount: number = 0;
325327

326328
filename: string;
@@ -1973,6 +1975,13 @@ export default class Compiler {
19731975
else this.pushVoid(node, 'err');
19741976
}
19751977

1978+
private processDoStatement(node: ts.DoStatement) {
1979+
this.pushVoid(node, `do_while_${this.doWhileCount}:`);
1980+
this.processNode(node.statement);
1981+
this.processNode(node.expression);
1982+
this.pushVoid(node, `bnz do_while_${this.doWhileCount}`);
1983+
}
1984+
19761985
private processWhileStatement(node: ts.WhileStatement) {
19771986
this.pushVoid(node, `while_${this.whileCount}:`);
19781987
this.processNode(node.expression);
@@ -2039,6 +2048,7 @@ export default class Compiler {
20392048
else if (ts.isThrowStatement(node)) this.processThrowStatement(node);
20402049
else if (ts.isWhileStatement(node)) this.processWhileStatement(node);
20412050
else if (ts.isForStatement(node)) this.processForStatement(node);
2051+
else if (ts.isDoStatement(node)) this.processDoStatement(node);
20422052
// Vars/Consts
20432053
else if (ts.isIdentifier(node)) this.processIdentifier(node);
20442054
else if (ts.isVariableDeclarationList(node)) this.processVariableDeclaration(node);

tests/contracts/artifacts/LoopsTest.approval.teal

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,45 @@ for_0_end:
118118
log
119119
retsub
120120

121+
// doWhileLoop()uint64
122+
abi_route_doWhileLoop:
123+
byte 0x // push empty bytes to fill the stack frame for this subroutine's local variables
124+
125+
// execute doWhileLoop()uint64
126+
callsub doWhileLoop
127+
int 1
128+
return
129+
130+
doWhileLoop:
131+
proto 1 0
132+
133+
// tests/contracts/loops.algo.ts:26
134+
// i = 0
135+
int 0
136+
frame_bury -1 // i: uint64
137+
138+
do_while_0:
139+
// tests/contracts/loops.algo.ts:29
140+
// i = i + 1
141+
frame_dig -1 // i: uint64
142+
int 1
143+
+
144+
frame_bury -1 // i: uint64
145+
frame_dig -1 // i: uint64
146+
int 10
147+
<
148+
bnz do_while_0
149+
150+
// tests/contracts/loops.algo.ts:32
151+
// return i;
152+
frame_dig -1 // i: uint64
153+
itob
154+
byte 0x151f7c75
155+
swap
156+
concat
157+
log
158+
retsub
159+
121160
abi_route_createApplication:
122161
int 1
123162
return
@@ -131,6 +170,7 @@ create_NoOp:
131170
call_NoOp:
132171
method "whileLoop()uint64"
133172
method "forLoop()uint64"
173+
method "doWhileLoop()uint64"
134174
txna ApplicationArgs 0
135-
match abi_route_whileLoop abi_route_forLoop
175+
match abi_route_whileLoop abi_route_forLoop abi_route_doWhileLoop
136176
err

tests/contracts/artifacts/LoopsTest.arc32.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
"no_op": "CALL"
1111
}
1212
},
13+
"doWhileLoop()uint64": {
14+
"call_config": {
15+
"no_op": "CALL"
16+
}
17+
},
1318
"createApplication()void": {
1419
"call_config": {
1520
"no_op": "CREATE"
@@ -44,7 +49,7 @@
4449
}
4550
},
4651
"source": {
47-
"approval": "I3ByYWdtYSB2ZXJzaW9uIDkKCi8vIFRoaXMgVEVBTCB3YXMgZ2VuZXJhdGVkIGJ5IFRFQUxTY3JpcHQgdjAuNjMuMAovLyBodHRwczovL2dpdGh1Yi5jb20vYWxnb3JhbmRmb3VuZGF0aW9uL1RFQUxTY3JpcHQKCi8vIFRoaXMgY29udHJhY3QgaXMgY29tcGxpYW50IHdpdGggYW5kL29yIGltcGxlbWVudHMgdGhlIGZvbGxvd2luZyBBUkNzOiBbIEFSQzQgXQoKLy8gVGhlIGZvbGxvd2luZyB0ZW4gbGluZXMgb2YgVEVBTCBoYW5kbGUgaW5pdGlhbCBwcm9ncmFtIGZsb3cKLy8gVGhpcyBwYXR0ZXJuIGlzIHVzZWQgdG8gbWFrZSBpdCBlYXN5IGZvciBhbnlvbmUgdG8gcGFyc2UgdGhlIHN0YXJ0IG9mIHRoZSBwcm9ncmFtIGFuZCBkZXRlcm1pbmUgaWYgYSBzcGVjaWZpYyBhY3Rpb24gaXMgYWxsb3dlZAovLyBIZXJlLCBhY3Rpb24gcmVmZXJzIHRvIHRoZSBPbkNvbXBsZXRlIGluIGNvbWJpbmF0aW9uIHdpdGggd2hldGhlciB0aGUgYXBwIGlzIGJlaW5nIGNyZWF0ZWQgb3IgY2FsbGVkCi8vIEV2ZXJ5IHBvc3NpYmxlIGFjdGlvbiBmb3IgdGhpcyBjb250cmFjdCBpcyByZXByZXNlbnRlZCBpbiB0aGUgc3dpdGNoIHN0YXRlbWVudAovLyBJZiB0aGUgYWN0aW9uIGlzIG5vdCBpbXBsbWVudGVkIGluIHRoZSBjb250cmFjdCwgaXRzIHJlcHNlY3RpdmUgYnJhbmNoIHdpbGwgYmUgIk5PVF9JTVBMTUVOVEVEIiB3aGljaCBqdXN0IGNvbnRhaW5zICJlcnIiCnR4biBBcHBsaWNhdGlvbklECmludCAwCj4KaW50IDYKKgp0eG4gT25Db21wbGV0aW9uCisKc3dpdGNoIGNyZWF0ZV9Ob09wIE5PVF9JTVBMRU1FTlRFRCBOT1RfSU1QTEVNRU5URUQgTk9UX0lNUExFTUVOVEVEIE5PVF9JTVBMRU1FTlRFRCBOT1RfSU1QTEVNRU5URUQgY2FsbF9Ob09wCgpOT1RfSU1QTEVNRU5URUQ6CgllcnIKCi8vIHdoaWxlTG9vcCgpdWludDY0CmFiaV9yb3V0ZV93aGlsZUxvb3A6CglieXRlIDB4IC8vIHB1c2ggZW1wdHkgYnl0ZXMgdG8gZmlsbCB0aGUgc3RhY2sgZnJhbWUgZm9yIHRoaXMgc3Vicm91dGluZSdzIGxvY2FsIHZhcmlhYmxlcwoKCS8vIGV4ZWN1dGUgd2hpbGVMb29wKCl1aW50NjQKCWNhbGxzdWIgd2hpbGVMb29wCglpbnQgMQoJcmV0dXJuCgp3aGlsZUxvb3A6Cglwcm90byAxIDAKCgkvLyB0ZXN0cy9jb250cmFjdHMvbG9vcHMuYWxnby50czo2CgkvLyBpID0gMAoJaW50IDAKCWZyYW1lX2J1cnkgLTEgLy8gaTogdWludDY0Cgp3aGlsZV8wOgoJZnJhbWVfZGlnIC0xIC8vIGk6IHVpbnQ2NAoJaW50IDEwCgk8CglieiB3aGlsZV8wX2VuZAoKCS8vIHRlc3RzL2NvbnRyYWN0cy9sb29wcy5hbGdvLnRzOjkKCS8vIGkgPSBpICsgMQoJZnJhbWVfZGlnIC0xIC8vIGk6IHVpbnQ2NAoJaW50IDEKCSsKCWZyYW1lX2J1cnkgLTEgLy8gaTogdWludDY0CgliIHdoaWxlXzAKCndoaWxlXzBfZW5kOgoJLy8gdGVzdHMvY29udHJhY3RzL2xvb3BzLmFsZ28udHM6MTIKCS8vIHJldHVybiBpOwoJZnJhbWVfZGlnIC0xIC8vIGk6IHVpbnQ2NAoJaXRvYgoJYnl0ZSAweDE1MWY3Yzc1Cglzd2FwCgljb25jYXQKCWxvZwoJcmV0c3ViCgovLyBmb3JMb29wKCl1aW50NjQKYWJpX3JvdXRlX2Zvckxvb3A6CglieXRlIDB4OyBkdXAgLy8gcHVzaCBlbXB0eSBieXRlcyB0byBmaWxsIHRoZSBzdGFjayBmcmFtZSBmb3IgdGhpcyBzdWJyb3V0aW5lJ3MgbG9jYWwgdmFyaWFibGVzCgoJLy8gZXhlY3V0ZSBmb3JMb29wKCl1aW50NjQKCWNhbGxzdWIgZm9yTG9vcAoJaW50IDEKCXJldHVybgoKZm9yTG9vcDoKCXByb3RvIDIgMAoKCS8vIHRlc3RzL2NvbnRyYWN0cy9sb29wcy5hbGdvLnRzOjE2CgkvLyBpID0gMAoJaW50IDAKCWZyYW1lX2J1cnkgLTEgLy8gaTogdWludDY0CgoJLy8gdGVzdHMvY29udHJhY3RzL2xvb3BzLmFsZ28udHM6MTgKCS8vIGogPSAwCglpbnQgMAoJZnJhbWVfYnVyeSAtMiAvLyBqOiB1aW50NjQKCmZvcl8wOgoJZnJhbWVfZGlnIC0yIC8vIGo6IHVpbnQ2NAoJaW50IDEwCgk8CglieiBmb3JfMF9lbmQKCgkvLyB0ZXN0cy9jb250cmFjdHMvbG9vcHMuYWxnby50czoxOQoJLy8gaSA9IGkgKyAxCglmcmFtZV9kaWcgLTEgLy8gaTogdWludDY0CglpbnQgMQoJKwoJZnJhbWVfYnVyeSAtMSAvLyBpOiB1aW50NjQKCgkvLyB0ZXN0cy9jb250cmFjdHMvbG9vcHMuYWxnby50czoxOAoJLy8gaiA9IGogKyAxCglmcmFtZV9kaWcgLTIgLy8gajogdWludDY0CglpbnQgMQoJKwoJZnJhbWVfYnVyeSAtMiAvLyBqOiB1aW50NjQKCWIgZm9yXzAKCmZvcl8wX2VuZDoKCS8vIHRlc3RzL2NvbnRyYWN0cy9sb29wcy5hbGdvLnRzOjIyCgkvLyByZXR1cm4gaTsKCWZyYW1lX2RpZyAtMSAvLyBpOiB1aW50NjQKCWl0b2IKCWJ5dGUgMHgxNTFmN2M3NQoJc3dhcAoJY29uY2F0Cglsb2cKCXJldHN1YgoKYWJpX3JvdXRlX2NyZWF0ZUFwcGxpY2F0aW9uOgoJaW50IDEKCXJldHVybgoKY3JlYXRlX05vT3A6CgltZXRob2QgImNyZWF0ZUFwcGxpY2F0aW9uKCl2b2lkIgoJdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMAoJbWF0Y2ggYWJpX3JvdXRlX2NyZWF0ZUFwcGxpY2F0aW9uCgllcnIKCmNhbGxfTm9PcDoKCW1ldGhvZCAid2hpbGVMb29wKCl1aW50NjQiCgltZXRob2QgImZvckxvb3AoKXVpbnQ2NCIKCXR4bmEgQXBwbGljYXRpb25BcmdzIDAKCW1hdGNoIGFiaV9yb3V0ZV93aGlsZUxvb3AgYWJpX3JvdXRlX2Zvckxvb3AKCWVycg==",
52+
"approval": "I3ByYWdtYSB2ZXJzaW9uIDkKCi8vIFRoaXMgVEVBTCB3YXMgZ2VuZXJhdGVkIGJ5IFRFQUxTY3JpcHQgdjAuNjMuMAovLyBodHRwczovL2dpdGh1Yi5jb20vYWxnb3JhbmRmb3VuZGF0aW9uL1RFQUxTY3JpcHQKCi8vIFRoaXMgY29udHJhY3QgaXMgY29tcGxpYW50IHdpdGggYW5kL29yIGltcGxlbWVudHMgdGhlIGZvbGxvd2luZyBBUkNzOiBbIEFSQzQgXQoKLy8gVGhlIGZvbGxvd2luZyB0ZW4gbGluZXMgb2YgVEVBTCBoYW5kbGUgaW5pdGlhbCBwcm9ncmFtIGZsb3cKLy8gVGhpcyBwYXR0ZXJuIGlzIHVzZWQgdG8gbWFrZSBpdCBlYXN5IGZvciBhbnlvbmUgdG8gcGFyc2UgdGhlIHN0YXJ0IG9mIHRoZSBwcm9ncmFtIGFuZCBkZXRlcm1pbmUgaWYgYSBzcGVjaWZpYyBhY3Rpb24gaXMgYWxsb3dlZAovLyBIZXJlLCBhY3Rpb24gcmVmZXJzIHRvIHRoZSBPbkNvbXBsZXRlIGluIGNvbWJpbmF0aW9uIHdpdGggd2hldGhlciB0aGUgYXBwIGlzIGJlaW5nIGNyZWF0ZWQgb3IgY2FsbGVkCi8vIEV2ZXJ5IHBvc3NpYmxlIGFjdGlvbiBmb3IgdGhpcyBjb250cmFjdCBpcyByZXByZXNlbnRlZCBpbiB0aGUgc3dpdGNoIHN0YXRlbWVudAovLyBJZiB0aGUgYWN0aW9uIGlzIG5vdCBpbXBsbWVudGVkIGluIHRoZSBjb250cmFjdCwgaXRzIHJlcHNlY3RpdmUgYnJhbmNoIHdpbGwgYmUgIk5PVF9JTVBMTUVOVEVEIiB3aGljaCBqdXN0IGNvbnRhaW5zICJlcnIiCnR4biBBcHBsaWNhdGlvbklECmludCAwCj4KaW50IDYKKgp0eG4gT25Db21wbGV0aW9uCisKc3dpdGNoIGNyZWF0ZV9Ob09wIE5PVF9JTVBMRU1FTlRFRCBOT1RfSU1QTEVNRU5URUQgTk9UX0lNUExFTUVOVEVEIE5PVF9JTVBMRU1FTlRFRCBOT1RfSU1QTEVNRU5URUQgY2FsbF9Ob09wCgpOT1RfSU1QTEVNRU5URUQ6CgllcnIKCi8vIHdoaWxlTG9vcCgpdWludDY0CmFiaV9yb3V0ZV93aGlsZUxvb3A6CglieXRlIDB4IC8vIHB1c2ggZW1wdHkgYnl0ZXMgdG8gZmlsbCB0aGUgc3RhY2sgZnJhbWUgZm9yIHRoaXMgc3Vicm91dGluZSdzIGxvY2FsIHZhcmlhYmxlcwoKCS8vIGV4ZWN1dGUgd2hpbGVMb29wKCl1aW50NjQKCWNhbGxzdWIgd2hpbGVMb29wCglpbnQgMQoJcmV0dXJuCgp3aGlsZUxvb3A6Cglwcm90byAxIDAKCgkvLyB0ZXN0cy9jb250cmFjdHMvbG9vcHMuYWxnby50czo2CgkvLyBpID0gMAoJaW50IDAKCWZyYW1lX2J1cnkgLTEgLy8gaTogdWludDY0Cgp3aGlsZV8wOgoJZnJhbWVfZGlnIC0xIC8vIGk6IHVpbnQ2NAoJaW50IDEwCgk8CglieiB3aGlsZV8wX2VuZAoKCS8vIHRlc3RzL2NvbnRyYWN0cy9sb29wcy5hbGdvLnRzOjkKCS8vIGkgPSBpICsgMQoJZnJhbWVfZGlnIC0xIC8vIGk6IHVpbnQ2NAoJaW50IDEKCSsKCWZyYW1lX2J1cnkgLTEgLy8gaTogdWludDY0CgliIHdoaWxlXzAKCndoaWxlXzBfZW5kOgoJLy8gdGVzdHMvY29udHJhY3RzL2xvb3BzLmFsZ28udHM6MTIKCS8vIHJldHVybiBpOwoJZnJhbWVfZGlnIC0xIC8vIGk6IHVpbnQ2NAoJaXRvYgoJYnl0ZSAweDE1MWY3Yzc1Cglzd2FwCgljb25jYXQKCWxvZwoJcmV0c3ViCgovLyBmb3JMb29wKCl1aW50NjQKYWJpX3JvdXRlX2Zvckxvb3A6CglieXRlIDB4OyBkdXAgLy8gcHVzaCBlbXB0eSBieXRlcyB0byBmaWxsIHRoZSBzdGFjayBmcmFtZSBmb3IgdGhpcyBzdWJyb3V0aW5lJ3MgbG9jYWwgdmFyaWFibGVzCgoJLy8gZXhlY3V0ZSBmb3JMb29wKCl1aW50NjQKCWNhbGxzdWIgZm9yTG9vcAoJaW50IDEKCXJldHVybgoKZm9yTG9vcDoKCXByb3RvIDIgMAoKCS8vIHRlc3RzL2NvbnRyYWN0cy9sb29wcy5hbGdvLnRzOjE2CgkvLyBpID0gMAoJaW50IDAKCWZyYW1lX2J1cnkgLTEgLy8gaTogdWludDY0CgoJLy8gdGVzdHMvY29udHJhY3RzL2xvb3BzLmFsZ28udHM6MTgKCS8vIGogPSAwCglpbnQgMAoJZnJhbWVfYnVyeSAtMiAvLyBqOiB1aW50NjQKCmZvcl8wOgoJZnJhbWVfZGlnIC0yIC8vIGo6IHVpbnQ2NAoJaW50IDEwCgk8CglieiBmb3JfMF9lbmQKCgkvLyB0ZXN0cy9jb250cmFjdHMvbG9vcHMuYWxnby50czoxOQoJLy8gaSA9IGkgKyAxCglmcmFtZV9kaWcgLTEgLy8gaTogdWludDY0CglpbnQgMQoJKwoJZnJhbWVfYnVyeSAtMSAvLyBpOiB1aW50NjQKCgkvLyB0ZXN0cy9jb250cmFjdHMvbG9vcHMuYWxnby50czoxOAoJLy8gaiA9IGogKyAxCglmcmFtZV9kaWcgLTIgLy8gajogdWludDY0CglpbnQgMQoJKwoJZnJhbWVfYnVyeSAtMiAvLyBqOiB1aW50NjQKCWIgZm9yXzAKCmZvcl8wX2VuZDoKCS8vIHRlc3RzL2NvbnRyYWN0cy9sb29wcy5hbGdvLnRzOjIyCgkvLyByZXR1cm4gaTsKCWZyYW1lX2RpZyAtMSAvLyBpOiB1aW50NjQKCWl0b2IKCWJ5dGUgMHgxNTFmN2M3NQoJc3dhcAoJY29uY2F0Cglsb2cKCXJldHN1YgoKLy8gZG9XaGlsZUxvb3AoKXVpbnQ2NAphYmlfcm91dGVfZG9XaGlsZUxvb3A6CglieXRlIDB4IC8vIHB1c2ggZW1wdHkgYnl0ZXMgdG8gZmlsbCB0aGUgc3RhY2sgZnJhbWUgZm9yIHRoaXMgc3Vicm91dGluZSdzIGxvY2FsIHZhcmlhYmxlcwoKCS8vIGV4ZWN1dGUgZG9XaGlsZUxvb3AoKXVpbnQ2NAoJY2FsbHN1YiBkb1doaWxlTG9vcAoJaW50IDEKCXJldHVybgoKZG9XaGlsZUxvb3A6Cglwcm90byAxIDAKCgkvLyB0ZXN0cy9jb250cmFjdHMvbG9vcHMuYWxnby50czoyNgoJLy8gaSA9IDAKCWludCAwCglmcmFtZV9idXJ5IC0xIC8vIGk6IHVpbnQ2NAoKZG9fd2hpbGVfMDoKCS8vIHRlc3RzL2NvbnRyYWN0cy9sb29wcy5hbGdvLnRzOjI5CgkvLyBpID0gaSArIDEKCWZyYW1lX2RpZyAtMSAvLyBpOiB1aW50NjQKCWludCAxCgkrCglmcmFtZV9idXJ5IC0xIC8vIGk6IHVpbnQ2NAoJZnJhbWVfZGlnIC0xIC8vIGk6IHVpbnQ2NAoJaW50IDEwCgk8CglibnogZG9fd2hpbGVfMAoKCS8vIHRlc3RzL2NvbnRyYWN0cy9sb29wcy5hbGdvLnRzOjMyCgkvLyByZXR1cm4gaTsKCWZyYW1lX2RpZyAtMSAvLyBpOiB1aW50NjQKCWl0b2IKCWJ5dGUgMHgxNTFmN2M3NQoJc3dhcAoJY29uY2F0Cglsb2cKCXJldHN1YgoKYWJpX3JvdXRlX2NyZWF0ZUFwcGxpY2F0aW9uOgoJaW50IDEKCXJldHVybgoKY3JlYXRlX05vT3A6CgltZXRob2QgImNyZWF0ZUFwcGxpY2F0aW9uKCl2b2lkIgoJdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMAoJbWF0Y2ggYWJpX3JvdXRlX2NyZWF0ZUFwcGxpY2F0aW9uCgllcnIKCmNhbGxfTm9PcDoKCW1ldGhvZCAid2hpbGVMb29wKCl1aW50NjQiCgltZXRob2QgImZvckxvb3AoKXVpbnQ2NCIKCW1ldGhvZCAiZG9XaGlsZUxvb3AoKXVpbnQ2NCIKCXR4bmEgQXBwbGljYXRpb25BcmdzIDAKCW1hdGNoIGFiaV9yb3V0ZV93aGlsZUxvb3AgYWJpX3JvdXRlX2Zvckxvb3AgYWJpX3JvdXRlX2RvV2hpbGVMb29wCgllcnI=",
4853
"clear": "I3ByYWdtYSB2ZXJzaW9uIDk="
4954
},
5055
"contract": {
@@ -69,6 +74,15 @@
6974
"desc": ""
7075
}
7176
},
77+
{
78+
"name": "doWhileLoop",
79+
"args": [],
80+
"desc": "",
81+
"returns": {
82+
"type": "uint64",
83+
"desc": ""
84+
}
85+
},
7286
{
7387
"name": "createApplication",
7488
"desc": "",

tests/contracts/artifacts/LoopsTest.arc4.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@
2020
"desc": ""
2121
}
2222
},
23+
{
24+
"name": "doWhileLoop",
25+
"args": [],
26+
"desc": "",
27+
"returns": {
28+
"type": "uint64",
29+
"desc": ""
30+
}
31+
},
2332
{
2433
"name": "createApplication",
2534
"desc": "",

tests/contracts/loops.algo.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,14 @@ class LoopsTest extends Contract {
2121

2222
return i;
2323
}
24+
25+
doWhileLoop(): uint64 {
26+
let i = 0;
27+
28+
do {
29+
i = i + 1;
30+
} while (i < 10);
31+
32+
return i;
33+
}
2434
}

tests/loops.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,9 @@ describe('Loops', function () {
4040
const ret = await appClient.call({ method: 'forLoop', methodArgs: [], sendParams: { suppressLog: true } });
4141
expect(ret.return?.returnValue).toEqual(BigInt(10));
4242
});
43+
44+
test('doWhileLoop', async function () {
45+
const ret = await appClient.call({ method: 'doWhileLoop', methodArgs: [], sendParams: { suppressLog: true } });
46+
expect(ret.return?.returnValue).toEqual(BigInt(10));
47+
});
4348
});

0 commit comments

Comments
 (0)