Added dice functions to parse engine
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import resolve from '/imports/parser/resolve.js'
|
||||
import rollDice from '/imports/parser/rollDice.js';
|
||||
|
||||
export default {
|
||||
'abs': {
|
||||
comment: 'Returns the absolute value of a number',
|
||||
examples: [
|
||||
{input: 'abs(9)', result: '9'},
|
||||
{input: 'abs(-3)', result: '3'},
|
||||
{ input: 'abs(9)', result: '9' },
|
||||
{ input: 'abs(-3)', result: '3' },
|
||||
],
|
||||
arguments: ['number'],
|
||||
resultType: 'number',
|
||||
@@ -14,8 +15,8 @@ export default {
|
||||
'sqrt': {
|
||||
comment: 'Returns the square root of a number',
|
||||
examples: [
|
||||
{input: 'sqrt(16)', result: '4'},
|
||||
{input: 'sqrt(10)', result: '3.1622776601683795'},
|
||||
{ input: 'sqrt(16)', result: '4' },
|
||||
{ input: 'sqrt(10)', result: '3.1622776601683795' },
|
||||
],
|
||||
arguments: ['number'],
|
||||
resultType: 'number',
|
||||
@@ -23,14 +24,14 @@ export default {
|
||||
},
|
||||
'max': {
|
||||
comment: 'Returns the largest of the given numbers',
|
||||
examples: [{input: 'max(12, 6, 3, 168)', result: '168'}],
|
||||
examples: [{ input: 'max(12, 6, 3, 168)', result: '168' }],
|
||||
arguments: anyNumberOf('number'),
|
||||
resultType: 'number',
|
||||
fn: Math.max,
|
||||
},
|
||||
'min': {
|
||||
comment: 'Returns the smallest of the given numbers',
|
||||
examples: [{input: 'min(12, 6, 3, 168)', result: '3'}],
|
||||
examples: [{ input: 'min(12, 6, 3, 168)', result: '3' }],
|
||||
arguments: anyNumberOf('number'),
|
||||
resultType: 'number',
|
||||
fn: Math.min,
|
||||
@@ -38,9 +39,9 @@ export default {
|
||||
'round': {
|
||||
comment: 'Returns the value of a number rounded to the nearest integer',
|
||||
examples: [
|
||||
{input: 'round(5.95)', result: '6'},
|
||||
{input: 'round(5.5)', result: '6'},
|
||||
{input: 'round(5.05)', result: '5'},
|
||||
{ input: 'round(5.95)', result: '6' },
|
||||
{ input: 'round(5.5)', result: '6' },
|
||||
{ input: 'round(5.05)', result: '5' },
|
||||
],
|
||||
arguments: ['number'],
|
||||
resultType: 'number',
|
||||
@@ -49,10 +50,10 @@ export default {
|
||||
'floor': {
|
||||
comment: 'Rounds a number down to the next smallest integer',
|
||||
examples: [
|
||||
{input: 'floor(5.95)', result: '5'},
|
||||
{input: 'floor(5.05)', result: '5'},
|
||||
{input: 'floor(5)', result: '5'},
|
||||
{input: 'floor(-5.5)', result: '-6'},
|
||||
{ input: 'floor(5.95)', result: '5' },
|
||||
{ input: 'floor(5.05)', result: '5' },
|
||||
{ input: 'floor(5)', result: '5' },
|
||||
{ input: 'floor(-5.5)', result: '-6' },
|
||||
],
|
||||
arguments: ['number'],
|
||||
resultType: 'number',
|
||||
@@ -61,10 +62,10 @@ export default {
|
||||
'ceil': {
|
||||
comment: 'Rounds a number up to the next largest integer',
|
||||
examples: [
|
||||
{input: 'ceil(5.95)', result: '6'},
|
||||
{input: 'ceil(5.05)', result: '6'},
|
||||
{input: 'ceil(5)', result: '5'},
|
||||
{input: 'ceil(-5.5)', result: '-5'},
|
||||
{ input: 'ceil(5.95)', result: '6' },
|
||||
{ input: 'ceil(5.05)', result: '6' },
|
||||
{ input: 'ceil(5)', result: '5' },
|
||||
{ input: 'ceil(-5.5)', result: '-5' },
|
||||
],
|
||||
arguments: ['number'],
|
||||
resultType: 'number',
|
||||
@@ -73,21 +74,21 @@ export default {
|
||||
'trunc': {
|
||||
comment: 'Returns the integer part of a number by removing any fractional digits',
|
||||
examples: [
|
||||
{input: 'trunc(5.95)', result: '5'},
|
||||
{input: 'trunc(5.05)', result: '5'},
|
||||
{input: 'trunc(5)', result: '5'},
|
||||
{input: 'trunc(-5.5)', result: '-5'},
|
||||
{ input: 'trunc(5.95)', result: '5' },
|
||||
{ input: 'trunc(5.05)', result: '5' },
|
||||
{ input: 'trunc(5)', result: '5' },
|
||||
{ input: 'trunc(-5.5)', result: '-5' },
|
||||
],
|
||||
arguments:[ 'number'],
|
||||
arguments: ['number'],
|
||||
resultType: 'number',
|
||||
fn: Math.trunc,
|
||||
},
|
||||
'sign': {
|
||||
comment: 'Returns either a positive or negative 1, indicating the sign of a number, or zero',
|
||||
examples: [
|
||||
{input: 'sign(-3)', result: '-1'},
|
||||
{input: 'sign(3)', result: '1'},
|
||||
{input: 'sign(0)', result: '0'},
|
||||
{ input: 'sign(-3)', result: '-1' },
|
||||
{ input: 'sign(3)', result: '1' },
|
||||
{ input: 'sign(0)', result: '0' },
|
||||
],
|
||||
arguments: ['number'],
|
||||
resultType: 'number',
|
||||
@@ -96,15 +97,15 @@ export default {
|
||||
'tableLookup': {
|
||||
comment: 'Returns the index of the last value in the array that is less than the specified amount',
|
||||
examples: [
|
||||
{input: 'tableLookup([100, 300, 900], 457)', result: '2'},
|
||||
{input: 'tableLookup([100, 300, 900], 23)', result: '0'},
|
||||
{input: 'tableLookup([100, 300, 900, 1200], 900)', result: '3'},
|
||||
{input: 'tableLookup([100, 300], 594)', result: '2'},
|
||||
{ input: 'tableLookup([100, 300, 900], 457)', result: '2' },
|
||||
{ input: 'tableLookup([100, 300, 900], 23)', result: '0' },
|
||||
{ input: 'tableLookup([100, 300, 900, 1200], 900)', result: '3' },
|
||||
{ input: 'tableLookup([100, 300], 594)', result: '2' },
|
||||
],
|
||||
arguments: ['array', 'number'],
|
||||
resultType: 'number',
|
||||
fn: function tableLookup(arrayNode, number){
|
||||
for(let i in arrayNode.values){
|
||||
fn: function tableLookup(arrayNode, number) {
|
||||
for (let i in arrayNode.values) {
|
||||
let node = arrayNode.values[i];
|
||||
if (node.value > number) return +i;
|
||||
}
|
||||
@@ -114,18 +115,146 @@ export default {
|
||||
'resolve': {
|
||||
comment: 'Forces the given calcultion to resolve into a number, even in calculations where it would usually keep the unknown values as is',
|
||||
examples: [
|
||||
{input: 'resolve(someUndefinedVariable + 3 + 4)', result: '7'},
|
||||
{input: 'resolve(1d6)', result: '4'},
|
||||
{ input: 'resolve(someUndefinedVariable + 3 + 4)', result: '7' },
|
||||
{ input: 'resolve(1d6)', result: '4' },
|
||||
],
|
||||
arguments: ['parseNode'],
|
||||
fn: function resolveFn(node){
|
||||
let {result} = resolve('reduce', node, this.scope, this.context);
|
||||
fn: function resolveFn(node) {
|
||||
let { result } = resolve('reduce', node, this.scope, this.context);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
},
|
||||
'dropLowest': {
|
||||
comment: 'Removes one or more of the lowest values in a roll',
|
||||
examples: [
|
||||
],
|
||||
arguments: ['rollArray', 'number'],
|
||||
maxResolveLevels: ['roll', 'reduce'],
|
||||
minArguments: 1,
|
||||
maxArguments: 2,
|
||||
resultType: 'rollArray',
|
||||
fn: function dropLowestFn(rollArray, numberToDrop = 1) {
|
||||
// Create a new array where the values are sorted in ascending order
|
||||
const sortedArray = [...rollArray.values].sort(function (a, b) {
|
||||
return a.value - b.value;
|
||||
});
|
||||
|
||||
// mark the N smallest elements as dropped
|
||||
for (let i = 0; i < numberToDrop; i += 1) {
|
||||
console.log('dropped ' + sortedArray[i].value);
|
||||
sortedArray[i].disabled = true;
|
||||
sortedArray[i].disabledBy = 'dropLowest';
|
||||
}
|
||||
return rollArray;
|
||||
},
|
||||
},
|
||||
'dropHighest': {
|
||||
comment: 'Removes one or more of the highest values in a roll',
|
||||
examples: [
|
||||
],
|
||||
arguments: ['rollArray', 'number'],
|
||||
maxResolveLevels: ['roll', 'reduce'],
|
||||
minArguments: 1,
|
||||
maxArguments: 2,
|
||||
resultType: 'rollArray',
|
||||
fn: function dropHighestFn(rollArray, numberToDrop = 1) {
|
||||
// Create a new array where the values are sorted in ascending order
|
||||
const sortedArray = [...rollArray.values].sort(function (a, b) {
|
||||
return b.value - a.value;
|
||||
});
|
||||
|
||||
// mark the N smallest elements as dropped
|
||||
for (let i = 0; i < numberToDrop; i += 1) {
|
||||
sortedArray[i].disabled = true;
|
||||
sortedArray[i].disabledBy = 'dropHighest';
|
||||
}
|
||||
return rollArray;
|
||||
},
|
||||
},
|
||||
'reroll': {
|
||||
comment: 'Rerolls if a number is less than or equal to the given value',
|
||||
examples: [
|
||||
],
|
||||
arguments: ['rollArray', 'number', 'boolean'],
|
||||
maxResolveLevels: ['roll', 'reduce'],
|
||||
minArguments: 1,
|
||||
maxArguments: 3,
|
||||
resultType: 'rollArray',
|
||||
fn: function rerollFn(rollArray, numberToReroll = 1, keepNewRoll = false) {
|
||||
let rollValues = rollArray.values
|
||||
// Iterate through the roll values
|
||||
for (let i = 0; i < rollValues.length; i += 1) {
|
||||
// If the number is less than the reroll limit
|
||||
if (rollValues[i].value <= numberToReroll) {
|
||||
// Disable it
|
||||
rollValues[i].disabled = true;
|
||||
rollValues[i].disabledBy = 'reroll';
|
||||
// Roll it again, insert the new roll into the list at the next index
|
||||
rollValues.splice(i + 1, 0, {
|
||||
value: rollDice(1, rollArray.diceSize)[0],
|
||||
});
|
||||
// Skip iterating the inserted roll if we are forced to keep it
|
||||
if (keepNewRoll) {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
if (i >= 100) {
|
||||
this.context.error('Can\'t roll more than 100 dice at once');
|
||||
return rollArray;
|
||||
}
|
||||
}
|
||||
return rollArray;
|
||||
},
|
||||
},
|
||||
'explode': {
|
||||
comment: 'Rerolls if a number is greater than or equal to the given value',
|
||||
examples: [
|
||||
],
|
||||
arguments: ['rollArray', 'number', 'number'],
|
||||
maxResolveLevels: ['roll', 'reduce', 'reduce'],
|
||||
minArguments: 1,
|
||||
maxArguments: 3,
|
||||
resultType: 'rollArray',
|
||||
fn: function explodeFn(rollArray, depth = 1, numberToReroll = rollArray.diceSize) {
|
||||
let overflowErrored = false;
|
||||
if (depth > 99) depth = 99;
|
||||
let rollValues = rollArray.values
|
||||
// Iterate through the roll values
|
||||
for (let i = 0; i < rollValues.length; i += 1) {
|
||||
// If the number is greater than or equal to the reroll limit
|
||||
// And there is space to reroll it
|
||||
if (rollValues[i].value >= numberToReroll) {
|
||||
rollValues[i].bold = true;
|
||||
let explodeDepth = 1;
|
||||
let explodeRoll;
|
||||
do {
|
||||
// Before inserting this roll, make sure the total dice in the roll
|
||||
// Doesn't exceed 100
|
||||
if (rollValues.length >= 100) {
|
||||
if (!overflowErrored) {
|
||||
this.context.error('Can\'t roll more than 100 dice at once');
|
||||
overflowErrored = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
explodeDepth += 1;
|
||||
explodeRoll = rollDice(1, rollArray.diceSize)[0];
|
||||
const rollObj = {
|
||||
value: explodeRoll,
|
||||
italics: true,
|
||||
};
|
||||
// Insert the roll
|
||||
rollValues.splice(i + 1, 0, rollObj);
|
||||
i += 1;
|
||||
} while (explodeDepth <= depth && explodeRoll >= numberToReroll)
|
||||
}
|
||||
}
|
||||
return rollArray;
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function anyNumberOf(type){
|
||||
function anyNumberOf(type) {
|
||||
let argumentArray = [type];
|
||||
argumentArray.anyLength = true;
|
||||
return argumentArray;
|
||||
|
||||
@@ -4,14 +4,14 @@ import functions from '/imports/parser/functions.js';
|
||||
import resolve, { toString, traverse, map } from '../resolve.js';
|
||||
|
||||
const call = {
|
||||
create({functionName, args}) {
|
||||
create({ functionName, args }) {
|
||||
return {
|
||||
parseType: 'call',
|
||||
functionName,
|
||||
args,
|
||||
}
|
||||
},
|
||||
resolve(fn, node, scope, context){
|
||||
resolve(fn, node, scope, context) {
|
||||
let func = functions[node.functionName];
|
||||
// Check that the function exists
|
||||
if (!func) {
|
||||
@@ -25,9 +25,22 @@ const call = {
|
||||
};
|
||||
}
|
||||
|
||||
// Resolve a given node to a maximum depth of resolution
|
||||
const resolveToLevel = (node, maxResolveFn = 'reduce') => {
|
||||
// Determine the actual depth to resolve to
|
||||
let resolveFn = 'reduce';
|
||||
if (fn === 'compile' || maxResolveFn === 'compile') {
|
||||
resolveFn = 'compile';
|
||||
} else if (fn === 'roll' || maxResolveFn === 'roll') {
|
||||
resolveFn = 'roll';
|
||||
}
|
||||
// Resolve
|
||||
return resolve(resolveFn, node, scope, context);
|
||||
}
|
||||
|
||||
// Resolve the arguments
|
||||
let resolvedArgs = node.args.map(arg => {
|
||||
let { result } = resolve(fn, arg, scope, context);
|
||||
let resolvedArgs = node.args.map((arg, i) => {
|
||||
let { result } = resolveToLevel(arg, func.maxResolveLevels?.[i]);
|
||||
return result;
|
||||
});
|
||||
|
||||
@@ -36,12 +49,12 @@ const call = {
|
||||
node,
|
||||
fn,
|
||||
resolvedArgs,
|
||||
argumentsExpected: func.arguments,
|
||||
func,
|
||||
context,
|
||||
});
|
||||
|
||||
if (checkFailed){
|
||||
if (fn === 'reduce'){
|
||||
if (checkFailed) {
|
||||
if (fn === 'reduce') {
|
||||
context.error(`Invalid arguments to ${node.functionName} function`);
|
||||
return {
|
||||
result: error.create({
|
||||
@@ -66,7 +79,7 @@ const call = {
|
||||
if (
|
||||
arg.parseType === 'constant' &&
|
||||
func.arguments[index] !== 'parseNode'
|
||||
){
|
||||
) {
|
||||
return arg.value;
|
||||
} else {
|
||||
return arg;
|
||||
@@ -75,20 +88,21 @@ const call = {
|
||||
|
||||
try {
|
||||
// Run the function
|
||||
let value = func.fn.apply({scope, context}, mappedArgs);
|
||||
let value = func.fn.apply({
|
||||
scope,
|
||||
context,
|
||||
}, mappedArgs);
|
||||
|
||||
let valueType = typeof value;
|
||||
if (valueType === 'number' || valueType === 'string' || valueType === 'boolean'){
|
||||
if (valueType === 'number' || valueType === 'string' || valueType === 'boolean') {
|
||||
// Convert constant results into constant nodes
|
||||
return {
|
||||
result: constant.create({ value, valueType }),
|
||||
result: constant.create({ value }),
|
||||
context,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
result: value,
|
||||
context,
|
||||
};
|
||||
// Resolve the return value
|
||||
return resolve(fn, value, scope, context);
|
||||
}
|
||||
} catch (error) {
|
||||
context.error(error.message || error);
|
||||
@@ -101,26 +115,28 @@ const call = {
|
||||
}
|
||||
}
|
||||
},
|
||||
toString(node){
|
||||
toString(node) {
|
||||
return `${node.functionName}(${node.args.map(arg => toString(arg)).join(', ')})`;
|
||||
},
|
||||
traverse(node, fn){
|
||||
traverse(node, fn) {
|
||||
fn(node);
|
||||
node.args.forEach(arg => traverse(arg, fn));
|
||||
},
|
||||
map(node, fn){
|
||||
map(node, fn) {
|
||||
const resultingNode = fn(node);
|
||||
if (resultingNode === node){
|
||||
if (resultingNode === node) {
|
||||
node.args = node.args.map(arg => map(arg, fn));
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
checkArugments({node, fn, argumentsExpected, resolvedArgs, context}){
|
||||
checkArugments({ node, fn, func, resolvedArgs, context }) {
|
||||
const argumentsExpected = func.arguments;
|
||||
// Check that the number of arguments matches the number expected
|
||||
if (
|
||||
!argumentsExpected.anyLength &&
|
||||
argumentsExpected.length !== resolvedArgs.length
|
||||
){
|
||||
resolvedArgs.length > (func.maxArguments ?? argumentsExpected.length) ||
|
||||
resolvedArgs.length < (func.minArguments ?? argumentsExpected.length)
|
||||
) {
|
||||
context.error('Incorrect number of arguments ' +
|
||||
`to ${node.functionName} function, ` +
|
||||
`expected ${argumentsExpected.length} got ${resolvedArgs.length}`);
|
||||
@@ -131,14 +147,14 @@ const call = {
|
||||
// Check that each argument is of the correct type
|
||||
resolvedArgs.forEach((node, index) => {
|
||||
let type;
|
||||
if (argumentsExpected.anyLength){
|
||||
if (argumentsExpected.anyLength) {
|
||||
type = argumentsExpected[0];
|
||||
} else {
|
||||
type = argumentsExpected[index];
|
||||
}
|
||||
if (type === 'parseNode') return;
|
||||
if (node.parseType !== type && node.valueType !== type) failed = true;
|
||||
if (failed && fn === 'reduce'){
|
||||
if (failed && fn === 'reduce') {
|
||||
let typeName = typeof type === 'string' ? type : type.constructor.name;
|
||||
let nodeName = node.parseType;
|
||||
context.error(`Incorrect arguments to ${node.functionName} function` +
|
||||
|
||||
@@ -4,7 +4,7 @@ const rollArray = {
|
||||
create({ values, diceSize, diceNum }) {
|
||||
return {
|
||||
parseType: 'rollArray',
|
||||
values,
|
||||
values: values.map(v => ({ value: v })),
|
||||
diceSize,
|
||||
diceNum,
|
||||
};
|
||||
@@ -16,10 +16,13 @@ const rollArray = {
|
||||
};
|
||||
},
|
||||
toString(node) {
|
||||
return `${node.diceNum || ''}d${node.diceSize} [ ${node.values.join(', ')} ]`;
|
||||
return `${node.diceNum || ''}d${node.diceSize} [${valuesToString(node.values)}]`;
|
||||
},
|
||||
reduce(node, scope, context) {
|
||||
const total = node.values.reduce((a, b) => a + b, 0);
|
||||
const total = node.values.reduce((a, b) => {
|
||||
if (b.disabled) return a;
|
||||
return a + b.value;
|
||||
}, 0);
|
||||
return {
|
||||
result: constant.create({
|
||||
value: total,
|
||||
@@ -29,4 +32,15 @@ const rollArray = {
|
||||
},
|
||||
}
|
||||
|
||||
function valuesToString(values) {
|
||||
return values.map(v => {
|
||||
let text = `${v.value}`;
|
||||
if (v.disabled) text = `~~${text}~~`;
|
||||
if (v.italics) text = `*${text}*`;
|
||||
if (v.bold) text = `**${text}**`;
|
||||
if (v.underline) text = `__${text}__`;
|
||||
return text;
|
||||
}).join(', ');
|
||||
}
|
||||
|
||||
export default rollArray;
|
||||
|
||||
Reference in New Issue
Block a user