Merge pull request #2891 from SillyTavern/if-command-default-arguments `/if` and `/while` now allow "right" and "rule" to be optional
Signed| @@ -12,7 +12,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom | |||
| 12 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; | 12 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; |
| 13 | import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | 13 | import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 14 | import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; | 14 | import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; |
| 15 | import { isFalseBoolean, convertValueType } from './utils.js'; | 15 | import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js'; |
| 16 | 16 | ||
| 17 | /** @typedef {import('./slash-commands/SlashCommandParser.js').NamedArguments} NamedArguments */ | 17 | /** @typedef {import('./slash-commands/SlashCommandParser.js').NamedArguments} NamedArguments */ |
| 18 | /** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */ | 18 | /** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */ |
| @@ -488,7 +488,7 @@ function existsGlobalVariable(name) { | |||
| 488 | /** | 488 | /** |
| 489 | * Parses boolean operands from command arguments. | 489 | * Parses boolean operands from command arguments. |
| 490 | * @param {object} args Command arguments | 490 | * @param {object} args Command arguments |
| 491 | * @returns {{a: string | number, b: string | number, rule: string}} Boolean operands | 491 | * @returns {{a: string | number, b: string | number?, rule: string}} Boolean operands |
| 492 | */ | 492 | */ |
| 493 | export function parseBooleanOperands(args) { | 493 | export function parseBooleanOperands(args) { |
| 494 | // Resolution order: numeric literal, local variable, global variable, string literal | 494 | // Resolution order: numeric literal, local variable, global variable, string literal |
| @@ -497,6 +497,9 @@ export function parseBooleanOperands(args) { | |||
| 497 | */ | 497 | */ |
| 498 | function getOperand(operand) { | 498 | function getOperand(operand) { |
| 499 | if (operand === undefined) { | 499 | if (operand === undefined) { |
| 500 | return undefined; | ||
| 501 | } | ||
| 502 | if (operand === '') { | ||
| 500 | return ''; | 503 | return ''; |
| 501 | } | 504 | } |
| 502 | 505 | ||
| @@ -525,8 +528,8 @@ export function parseBooleanOperands(args) { | |||
| 525 | return stringLiteral || ''; | 528 | return stringLiteral || ''; |
| 526 | } | 529 | } |
| 527 | 530 | ||
| 528 | const left = getOperand(args.a || args.left || args.first || args.x); | 531 | const left = getOperand(args.a ?? args.left ?? args.first ?? args.x); |
| 529 | const right = getOperand(args.b || args.right || args.second || args.y); | 532 | const right = getOperand(args.b ?? args.right ?? args.second ?? args.y); |
| 530 | const rule = args.rule; | 533 | const rule = args.rule; |
| 531 | 534 | ||
| 532 | return { a: left, b: right, rule }; | 535 | return { a: left, b: right, rule }; |
| @@ -534,84 +537,79 @@ export function parseBooleanOperands(args) { | |||
| 534 | 537 | ||
| 535 | /** | 538 | /** |
| 536 | * Evaluates a boolean comparison rule. | 539 | * Evaluates a boolean comparison rule. |
| 537 | * @param {string} rule Boolean comparison rule | 540 | * |
| 541 | * @param {string?} rule Boolean comparison rule | ||
| 538 | * @param {string|number} a The left operand | 542 | * @param {string|number} a The left operand |
| 539 | * @param {string|number} b The right operand | 543 | * @param {string|number?} b The right operand |
| 540 | * @returns {boolean} True if the rule yields true, false otherwise | 544 | * @returns {boolean} True if the rule yields true, false otherwise |
| 541 | */ | 545 | */ |
| 542 | export function evalBoolean(rule, a, b) { | 546 | export function evalBoolean(rule, a, b) { |
| 543 | if (!rule) { | 547 | if (a === undefined) { |
| 544 | toastr.warning('The rule must be specified for the boolean comparison.', 'Invalid command'); | 548 | throw new Error('Left operand is not provided'); |
| 545 | throw new Error('Invalid command.'); | 549 | } |
| 550 | |||
| 551 | // If right-hand side was not provided, whe just check if the left side is truthy | ||
| 552 | if (b === undefined) { | ||
| 553 | switch (rule) { | ||
| 554 | case undefined: | ||
| 555 | case 'not': { | ||
| 556 | const resultOnTruthy = rule !== 'not'; | ||
| 557 | if (isTrueBoolean(String(a))) return resultOnTruthy; | ||
| 558 | if (isFalseBoolean(String(a))) return !resultOnTruthy; | ||
| 559 | return a ? resultOnTruthy : !resultOnTruthy; | ||
| 560 | } | ||
| 561 | default: | ||
| 562 | throw new Error(`Unknown boolean comparison rule for truthy check. If right operand is not provided, the rule must not provided or be 'not'. Provided: ${rule}`); | ||
| 563 | } | ||
| 546 | } | 564 | } |
| 547 | 565 | ||
| 548 | let result = false; | 566 | // If no rule was provided, we are implicitly using 'eq', as defined for the slash commands |
| 567 | rule ??= 'eq'; | ||
| 568 | |||
| 549 | if (typeof a === 'number' && typeof b === 'number') { | 569 | if (typeof a === 'number' && typeof b === 'number') { |
| 550 | // only do numeric comparison if both operands are numbers | 570 | // only do numeric comparison if both operands are numbers |
| 551 | const aNumber = Number(a); | 571 | const aNumber = Number(a); |
| 552 | const bNumber = Number(b); | 572 | const bNumber = Number(b); |
| 553 | 573 | ||
| 554 | switch (rule) { | 574 | switch (rule) { |
| 555 | case 'not': | ||
| 556 | result = !aNumber; | ||
| 557 | break; | ||
| 558 | case 'gt': | 575 | case 'gt': |
| 559 | result = aNumber > bNumber; | 576 | return aNumber > bNumber; |
| 560 | break; | ||
| 561 | case 'gte': | 577 | case 'gte': |
| 562 | result = aNumber >= bNumber; | 578 | return aNumber >= bNumber; |
| 563 | break; | ||
| 564 | case 'lt': | 579 | case 'lt': |
| 565 | result = aNumber < bNumber; | 580 | return aNumber < bNumber; |
| 566 | break; | ||
| 567 | case 'lte': | 581 | case 'lte': |
| 568 | result = aNumber <= bNumber; | 582 | return aNumber <= bNumber; |
| 569 | break; | ||
| 570 | case 'eq': | 583 | case 'eq': |
| 571 | result = aNumber === bNumber; | 584 | return aNumber === bNumber; |
| 572 | break; | ||
| 573 | case 'neq': | 585 | case 'neq': |
| 574 | result = aNumber !== bNumber; | 586 | return aNumber !== bNumber; |
| 575 | break; | ||
| 576 | default: | ||
| 577 | toastr.error('Unknown boolean comparison rule for type number.', 'Invalid command'); | ||
| 578 | throw new Error('Invalid command.'); | ||
| 579 | } | ||
| 580 | } else { | ||
| 581 | // otherwise do case-insensitive string comparsion, stringify non-strings | ||
| 582 | let aString; | ||
| 583 | let bString; | ||
| 584 | if (typeof a == 'string') { | ||
| 585 | aString = a.toLowerCase(); | ||
| 586 | } else { | ||
| 587 | aString = JSON.stringify(a).toLowerCase(); | ||
| 588 | } | ||
| 589 | if (typeof b == 'string') { | ||
| 590 | bString = b.toLowerCase(); | ||
| 591 | } else { | ||
| 592 | bString = JSON.stringify(b).toLowerCase(); | ||
| 593 | } | ||
| 594 | |||
| 595 | switch (rule) { | ||
| 596 | case 'in': | 587 | case 'in': |
| 597 | result = aString.includes(bString); | ||
| 598 | break; | ||
| 599 | case 'nin': | 588 | case 'nin': |
| 600 | result = !aString.includes(bString); | 589 | // Fall through to string comparison. Otherwise you could not check if 12345 contains 45 for example. |
| 601 | break; | 590 | console.debug(`Boolean comparison rule '${rule}' is not supported for type number. Falling back to string comparison.`); |
| 602 | case 'eq': | ||
| 603 | result = aString === bString; | ||
| 604 | break; | ||
| 605 | case 'neq': | ||
| 606 | result = aString !== bString; | ||
| 607 | break; | 591 | break; |
| 608 | default: | 592 | default: |
| 609 | toastr.error('Unknown boolean comparison rule for type string.', 'Invalid /if command'); | 593 | throw new Error(`Unknown boolean comparison rule for type number. Accepted: gt, gte, lt, lte, eq, neq. Provided: ${rule}`); |
| 610 | throw new Error('Invalid command.'); | ||
| 611 | } | 594 | } |
| 612 | } | 595 | } |
| 613 | 596 | ||
| 614 | return result; | 597 | // otherwise do case-insensitive string comparsion, stringify non-strings |
| 598 | let aString = (typeof a === 'string') ? a.toLowerCase() : JSON.stringify(a).toLowerCase(); | ||
| 599 | let bString = (typeof b === 'string') ? b.toLowerCase() : JSON.stringify(b).toLowerCase(); | ||
| 600 | |||
| 601 | switch (rule) { | ||
| 602 | case 'in': | ||
| 603 | return aString.includes(bString); | ||
| 604 | case 'nin': | ||
| 605 | return !aString.includes(bString); | ||
| 606 | case 'eq': | ||
| 607 | return aString === bString; | ||
| 608 | case 'neq': | ||
| 609 | return aString !== bString; | ||
| 610 | default: | ||
| 611 | throw new Error(`Unknown boolean comparison rule for type number. Accepted: in, nin, eq, neq. Provided: ${rule}`); | ||
| 612 | } | ||
| 615 | } | 613 | } |
| 616 | 614 | ||
| 617 | /** | 615 | /** |
| @@ -1317,32 +1315,36 @@ export function registerVariableCommands() { | |||
| 1317 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], | 1315 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], |
| 1318 | isRequired: true, | 1316 | isRequired: true, |
| 1319 | enumProvider: commonEnumProviders.variables('all'), | 1317 | enumProvider: commonEnumProviders.variables('all'), |
| 1320 | forceEnum: false, | ||
| 1321 | }), | 1318 | }), |
| 1322 | SlashCommandNamedArgument.fromProps({ | 1319 | SlashCommandNamedArgument.fromProps({ |
| 1323 | name: 'right', | 1320 | name: 'right', |
| 1324 | description: 'right operand', | 1321 | description: 'right operand', |
| 1325 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], | 1322 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], |
| 1326 | isRequired: true, | ||
| 1327 | enumProvider: commonEnumProviders.variables('all'), | 1323 | enumProvider: commonEnumProviders.variables('all'), |
| 1328 | forceEnum: false, | ||
| 1329 | }), | 1324 | }), |
| 1330 | new SlashCommandNamedArgument( | 1325 | SlashCommandNamedArgument.fromProps({ |
| 1331 | 'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, [ | 1326 | name: 'rule', |
| 1332 | new SlashCommandEnumValue('gt', 'a > b'), | 1327 | description: 'comparison rule', |
| 1333 | new SlashCommandEnumValue('gte', 'a >= b'), | 1328 | typeList: [ARGUMENT_TYPE.STRING], |
| 1334 | new SlashCommandEnumValue('lt', 'a < b'), | 1329 | defaultValue: 'eq', |
| 1335 | new SlashCommandEnumValue('lte', 'a <= b'), | 1330 | enumList: [ |
| 1336 | new SlashCommandEnumValue('eq', 'a == b'), | 1331 | new SlashCommandEnumValue('eq', 'a == b (strings & numbers)'), |
| 1337 | new SlashCommandEnumValue('neq', 'a !== b'), | 1332 | new SlashCommandEnumValue('neq', 'a !== b (strings & numbers)'), |
| 1338 | new SlashCommandEnumValue('not', '!a'), | 1333 | new SlashCommandEnumValue('in', 'a includes b (strings & numbers as strings)'), |
| 1339 | new SlashCommandEnumValue('in', 'a includes b'), | 1334 | new SlashCommandEnumValue('nin', 'a not includes b (strings & numbers as strings)'), |
| 1340 | new SlashCommandEnumValue('nin', 'a not includes b'), | 1335 | new SlashCommandEnumValue('gt', 'a > b (numbers)'), |
| 1341 | ], | 1336 | new SlashCommandEnumValue('gte', 'a >= b (numbers)'), |
| 1342 | ), | 1337 | new SlashCommandEnumValue('lt', 'a < b (numbers)'), |
| 1343 | new SlashCommandNamedArgument( | 1338 | new SlashCommandEnumValue('lte', 'a <= b (numbers)'), |
| 1344 | 'else', 'command to execute if not true', [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], false, | 1339 | new SlashCommandEnumValue('not', '!a (truthy)'), |
| 1345 | ), | 1340 | ], |
| 1341 | forceEnum: true, | ||
| 1342 | }), | ||
| 1343 | SlashCommandNamedArgument.fromProps({ | ||
| 1344 | name: 'else', | ||
| 1345 | description: 'command to execute if not true', | ||
| 1346 | typeList: [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], | ||
| 1347 | }), | ||
| 1346 | ], | 1348 | ], |
| 1347 | unnamedArgumentList: [ | 1349 | unnamedArgumentList: [ |
| 1348 | new SlashCommandArgument( | 1350 | new SlashCommandArgument( |
| @@ -1360,17 +1362,25 @@ export function registerVariableCommands() { | |||
| 1360 | Numeric values and string literals for left and right operands supported. | 1362 | Numeric values and string literals for left and right operands supported. |
| 1361 | </div> | 1363 | </div> |
| 1362 | <div> | 1364 | <div> |
| 1365 | If the rule is not provided, it defaults to <code>eq</code>. | ||
| 1366 | </div> | ||
| 1367 | <div> | ||
| 1368 | If no right operand is provided, it defaults to checking the <code>left</code> value to be truthy. | ||
| 1369 | A non-empty string or non-zero number is considered truthy, as is the value <code>true</code> or <code>on</code>.<br /> | ||
| 1370 | Only acceptable rules for no provided right operand are <code>not</code>, and no provided rule - which default to returning whether it is not or is truthy. | ||
| 1371 | </div> | ||
| 1372 | <div> | ||
| 1363 | <strong>Available rules:</strong> | 1373 | <strong>Available rules:</strong> |
| 1364 | <ul> | 1374 | <ul> |
| 1365 | <li>gt => a > b</li> | 1375 | <li><code>eq</code> => a == b <small>(strings & numbers)</small></li> |
| 1366 | <li>gte => a >= b</li> | 1376 | <li><code>neq</code> => a !== b <small>(strings & numbers)</small></li> |
| 1367 | <li>lt => a < b</li> | 1377 | <li><code>in</code> => a includes b <small>(strings & numbers as strings)</small></li> |
| 1368 | <li>lte => a <= b</li> | 1378 | <li><code>nin</code> => a not includes b <small>(strings & numbers as strings)</small></li> |
| 1369 | <li>eq => a == b</li> | 1379 | <li><code>gt</code> => a > b <small>(numbers)</small></li> |
| 1370 | <li>neq => a != b</li> | 1380 | <li><code>gte</code> => a >= b <small>(numbers)</small></li> |
| 1371 | <li>not => !a</li> | 1381 | <li><code>lt</code> => a < b <small>(numbers)</small></li> |
| 1372 | <li>in (strings) => a includes b</li> | 1382 | <li><code>lte</code> => a <= b <small>(numbers)</small></li> |
| 1373 | <li>nin (strings) => a not includes b</li> | 1383 | <li><code>not</code> => !a <small>(truthy)</small></li> |
| 1374 | </ul> | 1384 | </ul> |
| 1375 | </div> | 1385 | </div> |
| 1376 | <div> | 1386 | <div> |
| @@ -1380,6 +1390,17 @@ export function registerVariableCommands() { | |||
| 1380 | <pre><code class="language-stscript">/if left=score right=10 rule=gte "/speak You win"</code></pre> | 1390 | <pre><code class="language-stscript">/if left=score right=10 rule=gte "/speak You win"</code></pre> |
| 1381 | triggers a /speak command if the value of "score" is greater or equals 10. | 1391 | triggers a /speak command if the value of "score" is greater or equals 10. |
| 1382 | </li> | 1392 | </li> |
| 1393 | <li> | ||
| 1394 | <pre><code class="language-stscript">/if left={{lastMessage}} rule=in right=surprise {: /echo SURPISE! :}</code></pre> | ||
| 1395 | executes a subcommand defined as a closure if the given value contains a specified word. | ||
| 1396 | <li> | ||
| 1397 | <pre><code class="language-stscript">/if left=myContent {: /echo My content had some content. :}</code></pre> | ||
| 1398 | executes the defined subcommand, if the provided value of left is truthy (contains some kind of contant that is not empty or false) | ||
| 1399 | </li> | ||
| 1400 | <li> | ||
| 1401 | <pre><code class="language-stscript">/if left=tree right={{getvar::object}} {: /echo The object is a tree! :}</code></pre> | ||
| 1402 | executes the defined subcommand, if the left and right values are equals. | ||
| 1403 | </li> | ||
| 1383 | </ul> | 1404 | </ul> |
| 1384 | </div> | 1405 | </div> |
| 1385 | `, | 1406 | `, |
| @@ -1395,32 +1416,38 @@ export function registerVariableCommands() { | |||
| 1395 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], | 1416 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], |
| 1396 | isRequired: true, | 1417 | isRequired: true, |
| 1397 | enumProvider: commonEnumProviders.variables('all'), | 1418 | enumProvider: commonEnumProviders.variables('all'), |
| 1398 | forceEnum: false, | ||
| 1399 | }), | 1419 | }), |
| 1400 | SlashCommandNamedArgument.fromProps({ | 1420 | SlashCommandNamedArgument.fromProps({ |
| 1401 | name: 'right', | 1421 | name: 'right', |
| 1402 | description: 'right operand', | 1422 | description: 'right operand', |
| 1403 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], | 1423 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], |
| 1404 | isRequired: true, | ||
| 1405 | enumProvider: commonEnumProviders.variables('all'), | 1424 | enumProvider: commonEnumProviders.variables('all'), |
| 1406 | forceEnum: false, | ||
| 1407 | }), | 1425 | }), |
| 1408 | new SlashCommandNamedArgument( | 1426 | SlashCommandNamedArgument.fromProps({ |
| 1409 | 'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, [ | 1427 | name: 'rule', |
| 1410 | new SlashCommandEnumValue('gt', 'a > b'), | 1428 | description: 'comparison rule', |
| 1411 | new SlashCommandEnumValue('gte', 'a >= b'), | 1429 | typeList: [ARGUMENT_TYPE.STRING], |
| 1412 | new SlashCommandEnumValue('lt', 'a < b'), | 1430 | defaultValue: 'eq', |
| 1413 | new SlashCommandEnumValue('lte', 'a <= b'), | 1431 | enumList: [ |
| 1414 | new SlashCommandEnumValue('eq', 'a == b'), | 1432 | new SlashCommandEnumValue('eq', 'a == b (strings & numbers)'), |
| 1415 | new SlashCommandEnumValue('neq', 'a !== b'), | 1433 | new SlashCommandEnumValue('neq', 'a !== b (strings & numbers)'), |
| 1416 | new SlashCommandEnumValue('not', '!a'), | 1434 | new SlashCommandEnumValue('in', 'a includes b (strings & numbers as strings)'), |
| 1417 | new SlashCommandEnumValue('in', 'a includes b'), | 1435 | new SlashCommandEnumValue('nin', 'a not includes b (strings & numbers as strings)'), |
| 1418 | new SlashCommandEnumValue('nin', 'a not includes b'), | 1436 | new SlashCommandEnumValue('gt', 'a > b (numbers)'), |
| 1419 | ], | 1437 | new SlashCommandEnumValue('gte', 'a >= b (numbers)'), |
| 1420 | ), | 1438 | new SlashCommandEnumValue('lt', 'a < b (numbers)'), |
| 1421 | new SlashCommandNamedArgument( | 1439 | new SlashCommandEnumValue('lte', 'a <= b (numbers)'), |
| 1422 | 'guard', 'disable loop iteration limit', [ARGUMENT_TYPE.STRING], false, false, null, commonEnumProviders.boolean('onOff')(), | 1440 | new SlashCommandEnumValue('not', '!a (truthy)'), |
| 1423 | ), | 1441 | ], |
| 1442 | forceEnum: true, | ||
| 1443 | }), | ||
| 1444 | SlashCommandNamedArgument.fromProps({ | ||
| 1445 | name: 'guard', | ||
| 1446 | description: 'disable loop iteration limit', | ||
| 1447 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 1448 | defaultValue: 'off', | ||
| 1449 | enumList: commonEnumProviders.boolean('onOff')(), | ||
| 1450 | }), | ||
| 1424 | ], | 1451 | ], |
| 1425 | unnamedArgumentList: [ | 1452 | unnamedArgumentList: [ |
| 1426 | new SlashCommandArgument( | 1453 | new SlashCommandArgument( |
| @@ -1439,15 +1466,15 @@ export function registerVariableCommands() { | |||
| 1439 | <div> | 1466 | <div> |
| 1440 | <strong>Available rules:</strong> | 1467 | <strong>Available rules:</strong> |
| 1441 | <ul> | 1468 | <ul> |
| 1442 | <li>gt => a > b</li> | 1469 | <li><code>eq</code> => a == b <small>(strings & numbers)</small></li> |
| 1443 | <li>gte => a >= b</li> | 1470 | <li><code>neq</code> => a !== b <small>(strings & numbers)</small></li> |
| 1444 | <li>lt => a < b</li> | 1471 | <li><code>in</code> => a includes b <small>(strings & numbers as strings)</small></li> |
| 1445 | <li>lte => a <= b</li> | 1472 | <li><code>nin</code> => a not includes b <small>(strings & numbers as strings)</small></li> |
| 1446 | <li>eq => a == b</li> | 1473 | <li><code>gt</code> => a > b <small>(numbers)</small></li> |
| 1447 | <li>neq => a != b</li> | 1474 | <li><code>gte</code> => a >= b <small>(numbers)</small></li> |
| 1448 | <li>not => !a</li> | 1475 | <li><code>lt</code> => a < b <small>(numbers)</small></li> |
| 1449 | <li>in (strings) => a includes b</li> | 1476 | <li><code>lte</code> => a <= b <small>(numbers)</small></li> |
| 1450 | <li>nin (strings) => a not includes b</li> | 1477 | <li><code>not</code> => !a <small>(truthy)</small></li> |
| 1451 | </ul> | 1478 | </ul> |
| 1452 | </div> | 1479 | </div> |
| 1453 | <div> | 1480 | <div> |
| @@ -1457,7 +1484,11 @@ export function registerVariableCommands() { | |||
| 1457 | <pre><code class="language-stscript">/setvar key=i 0 | /while left=i right=10 rule=lte "/addvar key=i 1"</code></pre> | 1484 | <pre><code class="language-stscript">/setvar key=i 0 | /while left=i right=10 rule=lte "/addvar key=i 1"</code></pre> |
| 1458 | adds 1 to the value of "i" until it reaches 10. | 1485 | adds 1 to the value of "i" until it reaches 10. |
| 1459 | </li> | 1486 | </li> |
| 1460 | </ul> | 1487 | <li> |
| 1488 | <pre><code class="language-stscript">/while left={{getvar::currentword}} {: /setvar key=currentword {: /do-something-and-return :}() | /echo The current work is "{{getvar::currentword}}" :}</code></pre> | ||
| 1489 | executes the defined subcommand as long as the "currentword" variable is truthy (has any content that is not false/empty) | ||
| 1490 | </ul> | ||
| 1491 | </li> | ||
| 1461 | </div> | 1492 | </div> |
| 1462 | <div> | 1493 | <div> |
| 1463 | Loops are limited to 100 iterations by default, pass <code>guard=off</code> to disable. | 1494 | Loops are limited to 100 iterations by default, pass <code>guard=off</code> to disable. |
| @@ -1572,7 +1603,7 @@ export function registerVariableCommands() { | |||
| 1572 | typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], | 1603 | typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], |
| 1573 | isRequired: true, | 1604 | isRequired: true, |
| 1574 | acceptsMultiple: true, | 1605 | acceptsMultiple: true, |
| 1575 | enumProvider: (executor, scope)=>{ | 1606 | enumProvider: (executor, scope) => { |
| 1576 | const vars = commonEnumProviders.variables('all')(executor, scope); | 1607 | const vars = commonEnumProviders.variables('all')(executor, scope); |
| 1577 | vars.push( | 1608 | vars.push( |
| 1578 | new SlashCommandEnumValue( | 1609 | new SlashCommandEnumValue( |
| @@ -1580,16 +1611,16 @@ export function registerVariableCommands() { | |||
| 1580 | null, | 1611 | null, |
| 1581 | enumTypes.variable, | 1612 | enumTypes.variable, |
| 1582 | enumIcons.variable, | 1613 | enumIcons.variable, |
| 1583 | (input)=>/^\w*$/.test(input), | 1614 | (input) => /^\w*$/.test(input), |
| 1584 | (input)=>input, | 1615 | (input) => input, |
| 1585 | ), | 1616 | ), |
| 1586 | new SlashCommandEnumValue( | 1617 | new SlashCommandEnumValue( |
| 1587 | 'any number', | 1618 | 'any number', |
| 1588 | null, | 1619 | null, |
| 1589 | enumTypes.number, | 1620 | enumTypes.number, |
| 1590 | enumIcons.number, | 1621 | enumIcons.number, |
| 1591 | (input)=>input == '' || !Number.isNaN(Number(input)), | 1622 | (input) => input == '' || !Number.isNaN(Number(input)), |
| 1592 | (input)=>input, | 1623 | (input) => input, |
| 1593 | ), | 1624 | ), |
| 1594 | ); | 1625 | ); |
| 1595 | return vars; | 1626 | return vars; |