Merge pull request #2925 from SillyTavern/fix-math-commands Fix and improve math slash commands

c42df886c57f3df66430819ecd40f3ce0ab76086

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
2 files changed, +106 -58Showing whitespace changes
public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js+29 -0
@@ -153,6 +153,35 @@ export const commonEnumProviders = {
153 },153 },
154154
155 /**155 /**
156 * Enum values for numbers and variable names
157 *
158 * Includes all variable names and the ability to specify any number
159 *
160 * @param {SlashCommandExecutor} executor - The executor of the slash command
161 * @param {SlashCommandScope} scope - The scope of the slash command
162 * @returns {SlashCommandEnumValue[]} The enum values
163 */
164 numbersAndVariables: (executor, scope) => [
165 ...commonEnumProviders.variables('all')(executor, scope),
166 new SlashCommandEnumValue(
167 'any variable name',
168 null,
169 enumTypes.variable,
170 enumIcons.variable,
171 (input) => /^\w*$/.test(input),
172 (input) => input,
173 ),
174 new SlashCommandEnumValue(
175 'any number',
176 null,
177 enumTypes.number,
178 enumIcons.number,
179 (input) => input == '' || !Number.isNaN(Number(input)),
180 (input) => input,
181 ),
182 ],
183
184 /**
156 * All possible char entities, like characters and groups. Can be filtered down to just one type.185 * All possible char entities, like characters and groups. Can be filtered down to just one type.
157 *186 *
158 * @param {('all' | 'character' | 'group')?} [mode='all'] - Which type to return187 * @param {('all' | 'character' | 'group')?} [mode='all'] - Which type to return
public/scripts/variables.js+77 -58
@@ -669,8 +669,8 @@ function deleteGlobalVariable(name) {
669}669}
670670
671/**671/**
672 * Parses a series of numeric values from a string.672 * Parses a series of numeric values from a string or a string array.
673 * @param {string} value A space-separated list of numeric values or variable names673 * @param {string|string[]} value A space-separated list of numeric values or variable names
674 * @param {SlashCommandScope} scope Scope674 * @param {SlashCommandScope} scope Scope
675 * @returns {number[]} An array of numeric values675 * @returns {number[]} An array of numeric values
676 */676 */
@@ -679,11 +679,17 @@ function parseNumericSeries(value, scope = null) {
679 return [value];679 return [value];
680 }680 }
681681
682 const array = value682 /** @type {(string|number)[]} */
683 .split(' ')683 let values = Array.isArray(value) ? value : value.split(' ');
684 .map(i => i.trim())684
685 // If a JSON array was provided as the only value, convert it to an array
686 if (values.length === 1 && typeof values[0] === 'string' && values[0].startsWith('[')) {
687 values = convertValueType(values[0], 'array');
688 }
689
690 const array = values.map(i => typeof i === 'string' ? i.trim() : i)
685 .filter(i => i !== '')691 .filter(i => i !== '')
686 .map(i => isNaN(Number(i)) ? Number(resolveVariable(i, scope)) : Number(i))692 .map(i => isNaN(Number(i)) ? Number(resolveVariable(String(i), scope)) : Number(i))
687 .filter(i => !isNaN(i));693 .filter(i => !isNaN(i));
688694
689 return array;695 return array;
@@ -703,7 +709,7 @@ function performOperation(value, operation, singleOperand = false, scope = null)
703709
704 const result = singleOperand ? operation(array[0]) : operation(array);710 const result = singleOperand ? operation(array[0]) : operation(array);
705711
706 if (isNaN(result) || !isFinite(result)) {712 if (isNaN(result)) {
707 return 0;713 return 0;
708 }714 }
709715
@@ -731,7 +737,7 @@ function maxValuesCallback(args, value) {
731}737}
732738
733function subValuesCallback(args, value) {739function subValuesCallback(args, value) {
734 return performOperation(value, (array) => array[0] - array[1], false, args._scope);740 return performOperation(value, (array) => array.reduce((a, b) => a - b, array.shift() ?? 0), false, args._scope);
735}741}
736742
737function divValuesCallback(args, value) {743function divValuesCallback(args, value) {
@@ -1595,36 +1601,15 @@ export function registerVariableCommands() {
1595 }));1601 }));
1596 SlashCommandParser.addCommandObject(SlashCommand.fromProps({1602 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1597 name: 'add',1603 name: 'add',
1598 callback: (args, /**@type {string[]}*/value) => addValuesCallback(args, value.join(' ')),1604 callback: (args, value) => addValuesCallback(args, value),
1599 returns: 'sum of the provided values',1605 returns: 'sum of the provided values',
1600 unnamedArgumentList: [1606 unnamedArgumentList: [
1601 SlashCommandArgument.fromProps({1607 SlashCommandArgument.fromProps({
1602 description: 'values to sum',1608 description: 'values to sum',
1603 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1609 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
1604 isRequired: true,1610 isRequired: true,
1605 acceptsMultiple: true,1611 acceptsMultiple: true,
1606 enumProvider: (executor, scope) => {1612 enumProvider: commonEnumProviders.numbersAndVariables,
1607 const vars = commonEnumProviders.variables('all')(executor, scope);
1608 vars.push(
1609 new SlashCommandEnumValue(
1610 'any variable name',
1611 null,
1612 enumTypes.variable,
1613 enumIcons.variable,
1614 (input) => /^\w*$/.test(input),
1615 (input) => input,
1616 ),
1617 new SlashCommandEnumValue(
1618 'any number',
1619 null,
1620 enumTypes.number,
1621 enumIcons.number,
1622 (input) => input == '' || !Number.isNaN(Number(input)),
1623 (input) => input,
1624 ),
1625 );
1626 return vars;
1627 },
1628 forceEnum: false,1613 forceEnum: false,
1629 }),1614 }),
1630 ],1615 ],
@@ -1632,7 +1617,9 @@ export function registerVariableCommands() {
1632 helpString: `1617 helpString: `
1633 <div>1618 <div>
1634 Performs an addition of the set of values and passes the result down the pipe.1619 Performs an addition of the set of values and passes the result down the pipe.
1635 Can use variable names.1620 </div>
1621 <div>
1622 Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
1636 </div>1623 </div>
1637 <div>1624 <div>
1638 <strong>Example:</strong>1625 <strong>Example:</strong>
@@ -1640,6 +1627,9 @@ export function registerVariableCommands() {
1640 <li>1627 <li>
1641 <pre><code class="language-stscript">/add 10 i 30 j</code></pre>1628 <pre><code class="language-stscript">/add 10 i 30 j</code></pre>
1642 </li>1629 </li>
1630 <li>
1631 <pre><code class="language-stscript">/add ["count", 15, 2, "i"]</code></pre>
1632 </li>
1643 </ul>1633 </ul>
1644 </div>1634 </div>
1645 `,1635 `,
@@ -1651,16 +1641,20 @@ export function registerVariableCommands() {
1651 unnamedArgumentList: [1641 unnamedArgumentList: [
1652 SlashCommandArgument.fromProps({1642 SlashCommandArgument.fromProps({
1653 description: 'values to multiply',1643 description: 'values to multiply',
1654 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1644 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
1655 isRequired: true,1645 isRequired: true,
1656 acceptsMultiple: true,1646 acceptsMultiple: true,
1657 enumProvider: commonEnumProviders.variables('all'),1647 enumProvider: commonEnumProviders.numbersAndVariables,
1658 forceEnum: false,1648 forceEnum: false,
1659 }),1649 }),
1660 ],1650 ],
1651 splitUnnamedArgument: true,
1661 helpString: `1652 helpString: `
1662 <div>1653 <div>
1663 Performs a multiplication of the set of values and passes the result down the pipe. Can use variable names.1654 Performs a multiplication of the set of values and passes the result down the pipe.
1655 </div>
1656 <div>
1657 Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
1664 </div>1658 </div>
1665 <div>1659 <div>
1666 <strong>Examples:</strong>1660 <strong>Examples:</strong>
@@ -1668,6 +1662,9 @@ export function registerVariableCommands() {
1668 <li>1662 <li>
1669 <pre><code class="language-stscript">/mul 10 i 30 j</code></pre>1663 <pre><code class="language-stscript">/mul 10 i 30 j</code></pre>
1670 </li>1664 </li>
1665 <li>
1666 <pre><code class="language-stscript">/mul ["count", 15, 2, "i"]</code></pre>
1667 </li>
1671 </ul>1668 </ul>
1672 </div>1669 </div>
1673 `,1670 `,
@@ -1679,16 +1676,20 @@ export function registerVariableCommands() {
1679 unnamedArgumentList: [1676 unnamedArgumentList: [
1680 SlashCommandArgument.fromProps({1677 SlashCommandArgument.fromProps({
1681 description: 'values to find the max',1678 description: 'values to find the max',
1682 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1679 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
1683 isRequired: true,1680 isRequired: true,
1684 acceptsMultiple: true,1681 acceptsMultiple: true,
1685 enumProvider: commonEnumProviders.variables('all'),1682 enumProvider: commonEnumProviders.numbersAndVariables,
1686 forceEnum: false,1683 forceEnum: false,
1687 }),1684 }),
1688 ],1685 ],
1686 splitUnnamedArgument: true,
1689 helpString: `1687 helpString: `
1690 <div>1688 <div>
1691 Returns the maximum value of the set of values and passes the result down the pipe. Can use variable names.1689 Returns the maximum value of the set of values and passes the result down the pipe.
1690 </div>
1691 <div>
1692 Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
1692 </div>1693 </div>
1693 <div>1694 <div>
1694 <strong>Examples:</strong>1695 <strong>Examples:</strong>
@@ -1696,6 +1697,9 @@ export function registerVariableCommands() {
1696 <li>1697 <li>
1697 <pre><code class="language-stscript">/max 10 i 30 j</code></pre>1698 <pre><code class="language-stscript">/max 10 i 30 j</code></pre>
1698 </li>1699 </li>
1700 <li>
1701 <pre><code class="language-stscript">/max ["count", 15, 2, "i"]</code></pre>
1702 </li>
1699 </ul>1703 </ul>
1700 </div>1704 </div>
1701 `,1705 `,
@@ -1707,17 +1711,20 @@ export function registerVariableCommands() {
1707 unnamedArgumentList: [1711 unnamedArgumentList: [
1708 SlashCommandArgument.fromProps({1712 SlashCommandArgument.fromProps({
1709 description: 'values to find the min',1713 description: 'values to find the min',
1710 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1714 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
1711 isRequired: true,1715 isRequired: true,
1712 acceptsMultiple: true,1716 acceptsMultiple: true,
1713 enumProvider: commonEnumProviders.variables('all'),1717 enumProvider: commonEnumProviders.numbersAndVariables,
1714 forceEnum: false,1718 forceEnum: false,
1715 }),1719 }),
1716 ],1720 ],
1721 splitUnnamedArgument: true,
1717 helpString: `1722 helpString: `
1718 <div>1723 <div>
1719 Returns the minimum value of the set of values and passes the result down the pipe.1724 Returns the minimum value of the set of values and passes the result down the pipe.
1720 Can use variable names.1725 </div>
1726 <div>
1727 Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
1721 </div>1728 </div>
1722 <div>1729 <div>
1723 <strong>Example:</strong>1730 <strong>Example:</strong>
@@ -1725,6 +1732,9 @@ export function registerVariableCommands() {
1725 <li>1732 <li>
1726 <pre><code class="language-stscript">/min 10 i 30 j</code></pre>1733 <pre><code class="language-stscript">/min 10 i 30 j</code></pre>
1727 </li>1734 </li>
1735 <li>
1736 <pre><code class="language-stscript">/min ["count", 15, 2, "i"]</code></pre>
1737 </li>
1728 </ul>1738 </ul>
1729 </div>1739 </div>
1730 `,1740 `,
@@ -1735,18 +1745,21 @@ export function registerVariableCommands() {
1735 returns: 'difference of the provided values',1745 returns: 'difference of the provided values',
1736 unnamedArgumentList: [1746 unnamedArgumentList: [
1737 SlashCommandArgument.fromProps({1747 SlashCommandArgument.fromProps({
1738 description: 'values to find the difference',1748 description: 'values to subtract, starting form the first provided value',
1739 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1749 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
1740 isRequired: true,1750 isRequired: true,
1741 acceptsMultiple: true,1751 acceptsMultiple: true,
1742 enumProvider: commonEnumProviders.variables('all'),1752 enumProvider: commonEnumProviders.numbersAndVariables,
1743 forceEnum: false,1753 forceEnum: false,
1744 }),1754 }),
1745 ],1755 ],
1756 splitUnnamedArgument: true,
1746 helpString: `1757 helpString: `
1747 <div>1758 <div>
1748 Performs a subtraction of the set of values and passes the result down the pipe.1759 Performs a subtraction of the set of values and passes the result down the pipe.
1749 Can use variable names.1760 </div>
1761 <div>
1762 Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
1750 </div>1763 </div>
1751 <div>1764 <div>
1752 <strong>Example:</strong>1765 <strong>Example:</strong>
@@ -1754,6 +1767,9 @@ export function registerVariableCommands() {
1754 <li>1767 <li>
1755 <pre><code class="language-stscript">/sub i 5</code></pre>1768 <pre><code class="language-stscript">/sub i 5</code></pre>
1756 </li>1769 </li>
1770 <li>
1771 <pre><code class="language-stscript">/sub ["count", 4, "i"]</code></pre>
1772 </li>
1757 </ul>1773 </ul>
1758 </div>1774 </div>
1759 `,1775 `,
@@ -1767,17 +1783,18 @@ export function registerVariableCommands() {
1767 description: 'dividend',1783 description: 'dividend',
1768 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1784 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1769 isRequired: true,1785 isRequired: true,
1770 enumProvider: commonEnumProviders.variables('all'),1786 enumProvider: commonEnumProviders.numbersAndVariables,
1771 forceEnum: false,1787 forceEnum: false,
1772 }),1788 }),
1773 SlashCommandArgument.fromProps({1789 SlashCommandArgument.fromProps({
1774 description: 'divisor',1790 description: 'divisor',
1775 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1791 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1776 isRequired: true,1792 isRequired: true,
1777 enumProvider: commonEnumProviders.variables('all'),1793 enumProvider: commonEnumProviders.numbersAndVariables,
1778 forceEnum: false,1794 forceEnum: false,
1779 }),1795 }),
1780 ],1796 ],
1797 splitUnnamedArgument: true,
1781 helpString: `1798 helpString: `
1782 <div>1799 <div>
1783 Performs a division of two values and passes the result down the pipe.1800 Performs a division of two values and passes the result down the pipe.
@@ -1802,17 +1819,18 @@ export function registerVariableCommands() {
1802 description: 'dividend',1819 description: 'dividend',
1803 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1820 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1804 isRequired: true,1821 isRequired: true,
1805 enumProvider: commonEnumProviders.variables('all'),1822 enumProvider: commonEnumProviders.numbersAndVariables,
1806 forceEnum: false,1823 forceEnum: false,
1807 }),1824 }),
1808 SlashCommandArgument.fromProps({1825 SlashCommandArgument.fromProps({
1809 description: 'divisor',1826 description: 'divisor',
1810 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1827 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1811 isRequired: true,1828 isRequired: true,
1812 enumProvider: commonEnumProviders.variables('all'),1829 enumProvider: commonEnumProviders.numbersAndVariables,
1813 forceEnum: false,1830 forceEnum: false,
1814 }),1831 }),
1815 ],1832 ],
1833 splitUnnamedArgument: true,
1816 helpString: `1834 helpString: `
1817 <div>1835 <div>
1818 Performs a modulo operation of two values and passes the result down the pipe.1836 Performs a modulo operation of two values and passes the result down the pipe.
@@ -1837,17 +1855,18 @@ export function registerVariableCommands() {
1837 description: 'base',1855 description: 'base',
1838 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1856 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1839 isRequired: true,1857 isRequired: true,
1840 enumProvider: commonEnumProviders.variables('all'),1858 enumProvider: commonEnumProviders.numbersAndVariables,
1841 forceEnum: false,1859 forceEnum: false,
1842 }),1860 }),
1843 SlashCommandArgument.fromProps({1861 SlashCommandArgument.fromProps({
1844 description: 'exponent',1862 description: 'exponent',
1845 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1863 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1846 isRequired: true,1864 isRequired: true,
1847 enumProvider: commonEnumProviders.variables('all'),1865 enumProvider: commonEnumProviders.numbersAndVariables,
1848 forceEnum: false,1866 forceEnum: false,
1849 }),1867 }),
1850 ],1868 ],
1869 splitUnnamedArgument: true,
1851 helpString: `1870 helpString: `
1852 <div>1871 <div>
1853 Performs a power operation of two values and passes the result down the pipe.1872 Performs a power operation of two values and passes the result down the pipe.
@@ -1872,7 +1891,7 @@ export function registerVariableCommands() {
1872 description: 'value',1891 description: 'value',
1873 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1892 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1874 isRequired: true,1893 isRequired: true,
1875 enumProvider: commonEnumProviders.variables('all'),1894 enumProvider: commonEnumProviders.numbersAndVariables,
1876 forceEnum: false,1895 forceEnum: false,
1877 }),1896 }),
1878 ],1897 ],
@@ -1900,7 +1919,7 @@ export function registerVariableCommands() {
1900 description: 'value',1919 description: 'value',
1901 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1920 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1902 isRequired: true,1921 isRequired: true,
1903 enumProvider: commonEnumProviders.variables('all'),1922 enumProvider: commonEnumProviders.numbersAndVariables,
1904 forceEnum: false,1923 forceEnum: false,
1905 }),1924 }),
1906 ],1925 ],
@@ -1929,7 +1948,7 @@ export function registerVariableCommands() {
1929 description: 'value',1948 description: 'value',
1930 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1949 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1931 isRequired: true,1950 isRequired: true,
1932 enumProvider: commonEnumProviders.variables('all'),1951 enumProvider: commonEnumProviders.numbersAndVariables,
1933 forceEnum: false,1952 forceEnum: false,
1934 }),1953 }),
1935 ],1954 ],
@@ -1957,7 +1976,7 @@ export function registerVariableCommands() {
1957 description: 'value',1976 description: 'value',
1958 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],1977 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1959 isRequired: true,1978 isRequired: true,
1960 enumProvider: commonEnumProviders.variables('all'),1979 enumProvider: commonEnumProviders.numbersAndVariables,
1961 forceEnum: false,1980 forceEnum: false,
1962 }),1981 }),
1963 ],1982 ],
@@ -1985,7 +2004,7 @@ export function registerVariableCommands() {
1985 description: 'value',2004 description: 'value',
1986 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],2005 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
1987 isRequired: true,2006 isRequired: true,
1988 enumProvider: commonEnumProviders.variables('all'),2007 enumProvider: commonEnumProviders.numbersAndVariables,
1989 forceEnum: false,2008 forceEnum: false,
1990 }),2009 }),
1991 ],2010 ],
@@ -2013,7 +2032,7 @@ export function registerVariableCommands() {
2013 description: 'value',2032 description: 'value',
2014 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],2033 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
2015 isRequired: true,2034 isRequired: true,
2016 enumProvider: commonEnumProviders.variables('all'),2035 enumProvider: commonEnumProviders.numbersAndVariables,
2017 forceEnum: false,2036 forceEnum: false,
2018 }),2037 }),
2019 ],2038 ],