Typescript all the parser things
This commit is contained in:
@@ -20,8 +20,6 @@ ejson@1.1.3
|
||||
check@1.3.2
|
||||
standard-minifier-js@2.8.1
|
||||
shell-server@0.5.0
|
||||
ecmascript@0.16.8
|
||||
es5-shim@4.8.0
|
||||
service-configuration@1.3.3
|
||||
dynamic-import@0.7.3
|
||||
ddp-rate-limiter@1.2.1
|
||||
@@ -47,8 +45,9 @@ simple:rest-bearer-token-parser
|
||||
simple:rest-json-error-handler
|
||||
littledata:synced-cron
|
||||
#mdg:meteor-apm-agent
|
||||
typescript@4.9.5
|
||||
seba:minifiers-autoprefixer
|
||||
mixmax:smart-disconnect
|
||||
zodern:types
|
||||
zodern:fix-async-stubs
|
||||
typescript
|
||||
ecmascript
|
||||
|
||||
@@ -16,14 +16,14 @@ babel-compiler@7.10.5
|
||||
babel-runtime@1.5.1
|
||||
base64@1.0.12
|
||||
binary-heap@1.0.11
|
||||
blaze-tools@1.1.3
|
||||
blaze-tools@1.1.4
|
||||
boilerplate-generator@1.7.2
|
||||
bozhao:link-accounts@2.8.0
|
||||
caching-compiler@1.2.2
|
||||
caching-html-compiler@1.2.1
|
||||
caching-html-compiler@1.2.2
|
||||
callback-hook@1.5.1
|
||||
check@1.3.2
|
||||
coffeescript@2.4.1
|
||||
coffeescript@2.7.0
|
||||
coffeescript-compiler@2.4.1
|
||||
dburles:mongo-collection-instances@0.4.0
|
||||
ddp@1.4.1
|
||||
@@ -44,8 +44,8 @@ fetch@0.1.4
|
||||
geojson-utils@1.0.11
|
||||
google-oauth@1.4.4
|
||||
hot-code-push@1.0.4
|
||||
html-tools@1.1.3
|
||||
htmljs@1.1.1
|
||||
html-tools@1.1.4
|
||||
htmljs@1.2.0
|
||||
http@2.0.0
|
||||
id-map@1.1.1
|
||||
inter-process-messaging@0.1.1
|
||||
@@ -57,7 +57,7 @@ logging@1.3.3
|
||||
mdg:validated-method@1.3.0
|
||||
meteor@1.11.5
|
||||
meteor-base@1.5.1
|
||||
meteortesting:browser-tests@1.4.2
|
||||
meteortesting:browser-tests@1.5.3
|
||||
meteortesting:mocha@2.1.0
|
||||
meteortesting:mocha-core@8.1.2
|
||||
mikowals:batch-insert@1.3.0
|
||||
@@ -78,7 +78,7 @@ npm-mongo@4.17.2
|
||||
oauth@2.2.1
|
||||
oauth2@1.3.2
|
||||
ordered-dict@1.1.0
|
||||
ostrio:cookies@2.7.2
|
||||
ostrio:cookies@2.8.0
|
||||
ostrio:files@2.3.3
|
||||
patreon-oauth@0.1.0
|
||||
peerlibrary:assert@0.3.0
|
||||
@@ -113,10 +113,10 @@ simple:rest-bearer-token-parser@1.1.1
|
||||
simple:rest-json-error-handler@1.1.1
|
||||
simple:rest-method-mixin@1.1.0
|
||||
socket-stream-client@0.5.2
|
||||
spacebars-compiler@1.3.1
|
||||
spacebars-compiler@1.3.2
|
||||
standard-minifier-js@2.8.1
|
||||
static-html@1.3.2
|
||||
templating-tools@1.2.2
|
||||
templating-tools@1.2.3
|
||||
tmeasday:check-npm-versions@1.0.2
|
||||
tracker@1.3.3
|
||||
typescript@4.9.5
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { getSingleProperty } from '/imports/api/engine/loadCreatures';
|
||||
import array from '/imports/parser/parseTree/constant';
|
||||
import constant from '/imports/parser/parseTree/constant';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import array from '/imports/parser/parseTree/array';
|
||||
import constant, { isFiniteNode } from '/imports/parser/parseTree/constant';
|
||||
|
||||
//set up the collection for creature variables
|
||||
let CreatureVariables = new Mongo.Collection('creatureVariables');
|
||||
const CreatureVariables = new Mongo.Collection('creatureVariables');
|
||||
|
||||
// Unique index on _creatureId
|
||||
if (Meteor.isServer) {
|
||||
@@ -28,7 +29,7 @@ if (Meteor.isServer) {
|
||||
* Get the property from the given scope, respecting properties that are just a link to the actual
|
||||
* property document
|
||||
*/
|
||||
export function getFromScope(name, scope) {
|
||||
export function getFromScope(name: string, scope) {
|
||||
let value = scope?.[name];
|
||||
if (value?._propId) {
|
||||
value = getSingleProperty(scope._creatureId, value._propId);
|
||||
@@ -38,13 +39,13 @@ export function getFromScope(name, scope) {
|
||||
|
||||
export function getNumberFromScope(name, scope) {
|
||||
const parseNode = getParseNodeFromScope(name, scope);
|
||||
if (!parseNode || parseNode.valueType !== 'number') {
|
||||
if (!parseNode || !isFiniteNode(parseNode)) {
|
||||
return undefined;
|
||||
}
|
||||
return parseNode.value;
|
||||
}
|
||||
|
||||
export function getParseNodeFromScope(name, scope) {
|
||||
export function getParseNodeFromScope(name, scope): ParseNode | undefined {
|
||||
let value = getFromScope(name, scope);
|
||||
if (!value) return;
|
||||
let valueType = getType(value);
|
||||
@@ -6,7 +6,8 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions';
|
||||
import { parse, prettifyParseError } from '/imports/parser/parser';
|
||||
import resolve, { toString } from '/imports/parser/resolve';
|
||||
import resolve from '/imports/parser/resolve';
|
||||
import toString from '/imports/parser/toString';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
|
||||
import { assertUserInTabletop } from '/imports/api/tabletop/methods/shared/tabletopPermissions.js';
|
||||
|
||||
@@ -196,7 +197,7 @@ const logRoll = new ValidatedMethod({
|
||||
optional: true,
|
||||
},
|
||||
}).validator(),
|
||||
run({ roll, tabletopId, creatureId }) {
|
||||
async run({ roll, tabletopId, creatureId }) {
|
||||
if (!creatureId && !tabletopId) throw new Meteor.Error('no-id',
|
||||
'A creature id or tabletop id must be given'
|
||||
);
|
||||
@@ -230,7 +231,7 @@ const logRoll = new ValidatedMethod({
|
||||
let {
|
||||
result: compiled,
|
||||
context
|
||||
} = resolve('compile', parsedResult, variables);
|
||||
} = await resolve('compile', parsedResult, variables);
|
||||
const compiledString = toString(compiled);
|
||||
if (!equalIgnoringWhitespace(compiledString, roll)) logContent.push({
|
||||
value: roll
|
||||
@@ -238,12 +239,12 @@ const logRoll = new ValidatedMethod({
|
||||
logContent.push({
|
||||
value: compiledString
|
||||
});
|
||||
let { result: rolled } = resolve('roll', compiled, variables, context);
|
||||
let { result: rolled } = await resolve('roll', compiled, variables, context);
|
||||
let rolledString = toString(rolled);
|
||||
if (rolledString !== compiledString) logContent.push({
|
||||
value: rolledString
|
||||
});
|
||||
let { result } = resolve('reduce', rolled, variables, context);
|
||||
let { result } = await resolve('reduce', rolled, variables, context);
|
||||
let resultString = toString(result);
|
||||
if (resultString !== rolledString) logContent.push({
|
||||
value: resultString
|
||||
|
||||
@@ -9,6 +9,14 @@ import {
|
||||
} from '/imports/api/engine/action/functions/actionEngineTest.testFn';
|
||||
import { Mutation, Update } from '/imports/api/engine/action/tasks/TaskResult';
|
||||
import Alea from 'alea';
|
||||
import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||
|
||||
process.on('unhandledRejection', (error, p) => {
|
||||
console.dir(error.stack);
|
||||
console.error('Unhandled Rejection at:', p, 'reason:', error)
|
||||
process.exit(1)
|
||||
});
|
||||
|
||||
const [
|
||||
creatureId, targetCreatureId, targetCreature2Id,
|
||||
|
||||
@@ -23,7 +23,7 @@ export default async function applyActionProperty(
|
||||
//Log the name and summary, check that the property has enough resources to fire
|
||||
const content: LogContent = { name: getPropertyTitle(prop) };
|
||||
if (prop.summary?.text) {
|
||||
await recalculateInlineCalculations(prop.summary, action);
|
||||
await recalculateInlineCalculations(prop.summary, action, 'reduce', userInput);
|
||||
content.value = prop.summary.value;
|
||||
}
|
||||
if (prop.silent) content.silenced = true;
|
||||
@@ -188,7 +188,7 @@ async function rollAttack(attack, scope, resultPushScope, userInput: InputProvid
|
||||
const rollModifierText = numberToSignedString(attack.value, true);
|
||||
let value, resultPrefix;
|
||||
if (scope['~attackAdvantage']?.value === 1) {
|
||||
const [[a, b]] = await userInput.rollDice(attack, [{ number: 2, diceSize: 20 }]);
|
||||
const [[a, b]] = await userInput.rollDice([{ number: 2, diceSize: 20 }]);
|
||||
if (a >= b) {
|
||||
value = a;
|
||||
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText}`;
|
||||
@@ -197,7 +197,7 @@ async function rollAttack(attack, scope, resultPushScope, userInput: InputProvid
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`;
|
||||
}
|
||||
} else if (scope['~attackAdvantage']?.value === -1) {
|
||||
const [[a, b]] = await userInput.rollDice(attack, [{ number: 2, diceSize: 20 }]);
|
||||
const [[a, b]] = await userInput.rollDice([{ number: 2, diceSize: 20 }]);
|
||||
if (a <= b) {
|
||||
value = a;
|
||||
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText}`;
|
||||
@@ -206,7 +206,7 @@ async function rollAttack(attack, scope, resultPushScope, userInput: InputProvid
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`;
|
||||
}
|
||||
} else {
|
||||
[[value]] = await userInput.rollDice(attack, [{ number: 1, diceSize: 20 }]);
|
||||
[[value]] = await userInput.rollDice([{ number: 1, diceSize: 20 }]);
|
||||
resultPrefix = `1d20 [${value}] ${rollModifierText}`
|
||||
}
|
||||
resultPushScope['~attackDiceRoll'] = { value };
|
||||
@@ -218,10 +218,12 @@ async function rollAttack(attack, scope, resultPushScope, userInput: InputProvid
|
||||
|
||||
function applyCrits(value, scope, resultPushScope) {
|
||||
const scopeCritTarget = getNumberFromScope('~criticalHitTarget', scope);
|
||||
const criticalHitTarget = Number.isFinite(scopeCritTarget) ? scopeCritTarget : 20;
|
||||
const criticalHitTarget = scopeCritTarget !== undefined &&
|
||||
Number.isFinite(scopeCritTarget) ? scopeCritTarget : 20;
|
||||
|
||||
const scopeCritMissTarget = getNumberFromScope('~criticalMissTarget', scope);
|
||||
const criticalMissTarget = Number.isFinite(scopeCritMissTarget) ? scopeCritMissTarget : 1;
|
||||
const criticalMissTarget = scopeCritMissTarget !== undefined &&
|
||||
Number.isFinite(scopeCritMissTarget) ? scopeCritMissTarget : 1;
|
||||
|
||||
const criticalHit = value >= criticalHitTarget;
|
||||
const criticalMiss = value <= criticalMissTarget;
|
||||
|
||||
@@ -115,7 +115,7 @@ export default async function applyBranchProperty(
|
||||
let choices: string[];
|
||||
let chosenChildren: typeof children = [];
|
||||
if (children.length) {
|
||||
choices = await userInput.choose(action, children);
|
||||
choices = await userInput.choose(children);
|
||||
chosenChildren = filter(children, child => choices.includes(child._id));
|
||||
}
|
||||
if (!children.length || !chosenChildren.length) {
|
||||
|
||||
@@ -3,9 +3,11 @@ import { get } from 'lodash';
|
||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
import { PropTask } from '/imports/api/engine/action/tasks/Task';
|
||||
import { getPropertyDescendants } from '/imports/api/engine/loadCreatures';
|
||||
import resolve, { toString, map } from '/imports/parser/resolve';
|
||||
import resolve from '/imports/parser/resolve';
|
||||
import map from '/imports/parser/map';
|
||||
import toString from '/imports/parser/toString';
|
||||
import computedSchemas from '/imports/api/properties/computedOnlyPropertySchemasIndex.js';
|
||||
import applyFnToKey from '/imports/api/engine/computation/utility/applyFnToKey';
|
||||
import applyFnToKey, { applyFnToKeyAsync } from '/imports/api/engine/computation/utility/applyFnToKey';
|
||||
import accessor from '/imports/parser/parseTree/accessor';
|
||||
import TaskResult, { Mutation } from '/imports/api/engine/action/tasks/TaskResult';
|
||||
import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope';
|
||||
@@ -55,7 +57,7 @@ export default async function applyBuffProperty(
|
||||
//Log the buff
|
||||
let logValue = prop.description?.value
|
||||
if (prop.description?.text) {
|
||||
recalculateInlineCalculations(prop.description, action);
|
||||
recalculateInlineCalculations(prop.description, action, 'resolve', userInput);
|
||||
logValue = prop.description?.value;
|
||||
}
|
||||
result.appendLog({
|
||||
@@ -83,17 +85,17 @@ async function crystalizeVariables(
|
||||
action: EngineAction, propList: any[], task: PropTask, result: TaskResult
|
||||
) {
|
||||
const scope = await getEffectiveActionScope(action);
|
||||
propList.forEach(prop => {
|
||||
for (const prop of propList) {
|
||||
if (prop._skipCrystalize) {
|
||||
delete prop._skipCrystalize;
|
||||
return;
|
||||
}
|
||||
// Iterate through all the calculations and crystalize them
|
||||
computedSchemas[prop.type].computedFields().forEach(calcKey => {
|
||||
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||
for (const calcKey of computedSchemas[prop.type].computedFields()) {
|
||||
await applyFnToKeyAsync(prop, calcKey, async (prop, key) => {
|
||||
const calcObj = get(prop, key);
|
||||
if (!calcObj?.parseNode) return;
|
||||
calcObj.parseNode = map(calcObj.parseNode, node => {
|
||||
calcObj.parseNode = await map(calcObj.parseNode, async node => {
|
||||
// Skip nodes that aren't symbols or accessors
|
||||
if (
|
||||
node.parseType !== 'accessor'
|
||||
@@ -117,22 +119,17 @@ async function crystalizeVariables(
|
||||
return node;
|
||||
} else {
|
||||
// Resolve all other variables
|
||||
const { result, context } = resolve('reduce', node, scope);
|
||||
context.errors?.forEach(error => {
|
||||
result.appendLog({
|
||||
name: 'Error',
|
||||
value: error,
|
||||
}, task.targetIds);
|
||||
});
|
||||
return result;
|
||||
const { result: nodeResult, context } = await resolve('reduce', node, scope);
|
||||
result.appendParserContextErrors(context, task.targetIds);
|
||||
return nodeResult;
|
||||
}
|
||||
});
|
||||
calcObj.calculation = toString(calcObj.parseNode);
|
||||
calcObj.hash = cyrb53(calcObj.calculation);
|
||||
});
|
||||
});
|
||||
}
|
||||
// For each key in the schema
|
||||
computedSchemas[prop.type].inlineCalculationFields().forEach(calcKey => {
|
||||
for (const calcKey of computedSchemas[prop.type].inlineCalculationFields()) {
|
||||
// That ends in .inlineCalculations
|
||||
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||
const inlineCalcObj = get(prop, key);
|
||||
@@ -161,6 +158,6 @@ async function crystalizeVariables(
|
||||
}
|
||||
inlineCalcObj.hash = inlineCalcHash;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +1,88 @@
|
||||
// TODO
|
||||
import { some, includes, difference, intersection } from 'lodash';
|
||||
|
||||
export default function applyDamage(node, actionContext) {
|
||||
applyNodeTriggers(node, 'before', actionContext);
|
||||
import { getParseNodeFromScope } from '/imports/api/creature/creatures/CreatureVariables';
|
||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
import { applyDefaultAfterPropTasks } from '/imports/api/engine/action/functions/applyTaskGroups';
|
||||
import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope';
|
||||
import recalculateCalculation from '/imports/api/engine/action/functions/recalculateCalculation';
|
||||
import { PropTask } from '/imports/api/engine/action/tasks/Task';
|
||||
import TaskResult from '/imports/api/engine/action/tasks/TaskResult';
|
||||
import { isFiniteNode } from '/imports/parser/parseTree/constant';
|
||||
import resolve from '/imports/parser/resolve';
|
||||
import Context from '../../../../parser/types/Context';
|
||||
import toString from '/imports/parser/toString';
|
||||
import { getPropertiesOfType } from '/imports/api/engine/loadCreatures';
|
||||
import applyTask from '/imports/api/engine/action/tasks/applyTask';
|
||||
import InputProvider from '/imports/api/engine/action/functions/InputProvider';
|
||||
import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags';
|
||||
|
||||
const prop = node.doc
|
||||
const scope = actionContext.scope;
|
||||
export default async function applyDamageProperty(
|
||||
task: PropTask, action: EngineAction, result: TaskResult, userInput
|
||||
) {
|
||||
const prop = task.prop;
|
||||
const scope = getEffectiveActionScope(action);
|
||||
|
||||
// Skip if there is no parse node to work with
|
||||
if (!prop.amount?.parseNode) return;
|
||||
|
||||
// Choose target
|
||||
let damageTargets = prop.target === 'self' ? [actionContext.creature] : actionContext.targets;
|
||||
const damageTargets = prop.target === 'self' ? [action.creatureId] : task.targetIds;
|
||||
// Determine if the hit is critical
|
||||
let criticalHit = scope['~criticalHit']?.value &&
|
||||
prop.damageType !== 'healing' // Can't critically heal
|
||||
;
|
||||
const criticalHit = getParseNodeFromScope('~criticalHit', scope)?.value
|
||||
&& prop.damageType !== 'healing'; // Can't critically heal
|
||||
// Double the damage rolls if the hit is critical
|
||||
let context = new Context({
|
||||
const context = new Context({
|
||||
options: { doubleRolls: criticalHit },
|
||||
});
|
||||
|
||||
// Gather all the lines we need to log into an array
|
||||
const logValue = [];
|
||||
const logValue: string[] = [];
|
||||
const logName = prop.damageType === 'healing' ? 'Healing' : 'Damage';
|
||||
|
||||
// roll the dice only and store that string
|
||||
recalculateCalculation(prop.amount, actionContext, 'compile');
|
||||
const { result: rolled } = resolve('roll', prop.amount.valueNode, scope, context);
|
||||
recalculateCalculation(prop.amount, action, 'compile', userInput);
|
||||
const { result: rolled } = await resolve('roll', prop.amount.valueNode, scope, context);
|
||||
if (rolled.parseType !== 'constant') {
|
||||
logValue.push(toString(rolled));
|
||||
}
|
||||
logErrors(context.errors, actionContext);
|
||||
result.appendParserContextErrors(context, damageTargets);
|
||||
|
||||
// Reset the errors so we don't log the same errors twice
|
||||
context.errors = [];
|
||||
|
||||
// Resolve the roll to a final value
|
||||
const { result: reduced } = resolve('reduce', rolled, scope, context);
|
||||
logErrors(context.errors, actionContext);
|
||||
const { result: reduced } = await resolve('reduce', rolled, scope, context);
|
||||
result.appendParserContextErrors(context, damageTargets);
|
||||
|
||||
// Store the result
|
||||
let damage: number | undefined = undefined;
|
||||
if (reduced.parseType === 'constant') {
|
||||
prop.amount.value = reduced.value;
|
||||
if (typeof reduced.value === 'number') {
|
||||
damage = reduced.value;
|
||||
}
|
||||
} else if (reduced.parseType === 'error') {
|
||||
prop.amount.value = null;
|
||||
} else {
|
||||
prop.amount.value = toString(reduced);
|
||||
}
|
||||
let damage = +reduced.value;
|
||||
|
||||
// If we didn't end up with a constant of finite amount, give up
|
||||
if (reduced?.parseType !== 'constant' || !isFinite(reduced.value)) {
|
||||
return applyChildren(node, actionContext);
|
||||
// If we didn't end up with damage of finite amount, give up
|
||||
if (
|
||||
typeof damage !== 'number'
|
||||
|| !isFinite(damage)
|
||||
) {
|
||||
return applyDefaultAfterPropTasks(action, prop, damageTargets, userInput);
|
||||
}
|
||||
|
||||
// Round the damage to a whole number
|
||||
damage = Math.floor(damage);
|
||||
scope['~damage'] = damage;
|
||||
scope['~damage'] = { value: damage };
|
||||
|
||||
// Convert extra damage into the stored type
|
||||
if (prop.damageType === 'extra' && scope['~lastDamageType']?.value) {
|
||||
prop.damageType = scope['~lastDamageType']?.value;
|
||||
const lastDamageType = getParseNodeFromScope('~lastDamageType')?.value;
|
||||
if (prop.damageType === 'extra' && typeof lastDamageType === 'string') {
|
||||
prop.damageType = lastDamageType;
|
||||
}
|
||||
// Store current damage type
|
||||
if (prop.damageType !== 'healing') {
|
||||
@@ -68,7 +90,7 @@ export default function applyDamage(node, actionContext) {
|
||||
}
|
||||
|
||||
// Memoise the damage suffix for the log
|
||||
let suffix = (criticalHit ? ' critical ' : ' ') +
|
||||
const suffix = (criticalHit ? ' critical ' : ' ') +
|
||||
prop.damageType +
|
||||
(prop.damageType !== 'healing' ? ' damage ' : '');
|
||||
|
||||
@@ -76,17 +98,24 @@ export default function applyDamage(node, actionContext) {
|
||||
let damageOnSave, saveNode, saveRoll;
|
||||
if (prop.save) {
|
||||
if (prop.save.damageFunction?.calculation) {
|
||||
recalculateCalculation(prop.save.damageFunction, actionContext, undefined, 'compile');
|
||||
let { result: saveDamageRolled } = resolve('roll', prop.save.damageFunction.valueNode, scope, context);
|
||||
recalculateCalculation(prop.save.damageFunction, action, 'compile', userInput);
|
||||
context.errors = [];
|
||||
const { result: saveDamageRolled } = await resolve(
|
||||
'roll', prop.save.damageFunction.valueNode, scope, context
|
||||
);
|
||||
saveRoll = toString(saveDamageRolled);
|
||||
let { result: saveDamageResult } = resolve('reduce', saveDamageRolled, scope, context);
|
||||
const { result: saveDamageResult } = await resolve(
|
||||
'reduce', saveDamageRolled, scope, context
|
||||
);
|
||||
result.appendParserContextErrors(context, damageTargets);
|
||||
// If we didn't end up with a constant of finite amount, give up
|
||||
if (reduced?.parseType !== 'constant' || !isFinite(reduced.value)) {
|
||||
return applyChildren(node, actionContext);
|
||||
if (
|
||||
!isFiniteNode(saveDamageResult)
|
||||
) {
|
||||
return applyDefaultAfterPropTasks(action, prop, damageTargets, userInput);
|
||||
}
|
||||
damageOnSave = +saveDamageResult.value;
|
||||
// Round the damage to a whole number
|
||||
damageOnSave = Math.floor(damageOnSave);
|
||||
damageOnSave = Math.floor(saveDamageResult.value);
|
||||
} else {
|
||||
damageOnSave = Math.floor(damage / 2);
|
||||
}
|
||||
@@ -102,14 +131,13 @@ export default function applyDamage(node, actionContext) {
|
||||
|
||||
if (damageTargets && damageTargets.length) {
|
||||
// Iterate through all the targets
|
||||
damageTargets.forEach(target => {
|
||||
actionContext.target = [target];
|
||||
let damageToApply = damage;
|
||||
for (const target of damageTargets) {
|
||||
let damageToApply = damage || 0;
|
||||
|
||||
// If there is a saving throw, apply that first
|
||||
if (prop.save) {
|
||||
applySavingThrow(saveNode, actionContext);
|
||||
if (scope['~saveSucceeded']?.value) {
|
||||
await applySavingThrow(saveNode, actionContext);
|
||||
if (getParseNodeFromScope('~saveSucceeded', scope)?.value) {
|
||||
// Log the total damage
|
||||
logValue.push(toString(reduced));
|
||||
// Log the save damage
|
||||
@@ -136,49 +164,28 @@ export default function applyDamage(node, actionContext) {
|
||||
});
|
||||
|
||||
// Deal the damage to the target
|
||||
let damageDealt = dealDamage({
|
||||
target,
|
||||
damageType: prop.damageType,
|
||||
amount: damageToApply,
|
||||
actionContext
|
||||
});
|
||||
|
||||
// Log the damage done
|
||||
if (target._id === actionContext.creature._id) {
|
||||
// Target is same as self, log damage as such
|
||||
logValue.push(`**${damageDealt}** ${suffix} to self`);
|
||||
} else {
|
||||
logValue.push(`Dealt **${damageDealt}** ${suffix} ${target.name && ' to '}${target.name}`);
|
||||
// Log the damage received on that creature's log as well
|
||||
insertCreatureLog.call({
|
||||
log: {
|
||||
creatureId: target._id,
|
||||
content: [{
|
||||
name,
|
||||
value: `Received **${damageDealt}** ${suffix}`,
|
||||
}],
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
await dealDamage(
|
||||
action, prop, result, userInput, target, prop.damageType, damageToApply
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// There are no targets, just log the result
|
||||
logValue.push(`**${damage}** ${suffix}`);
|
||||
if (prop.save) {
|
||||
applySavingThrow(saveNode, actionContext);
|
||||
await applySavingThrow(saveNode, actionContext);
|
||||
logValue.push(`**${damageOnSave}** ${suffix} on a successful save`);
|
||||
}
|
||||
}
|
||||
if (!prop.silent) actionContext.addLog({
|
||||
if (logValue.length) result.appendLog({
|
||||
name: logName,
|
||||
value: logValue.join('\n'),
|
||||
inline: true,
|
||||
});
|
||||
return applyChildren(node, actionContext);
|
||||
}, damageTargets);
|
||||
return applyDefaultAfterPropTasks(action, prop, damageTargets, userInput);
|
||||
}
|
||||
|
||||
function damageFunctionText(save, scope, context, actionContext) {
|
||||
if (!save) return [];
|
||||
function damageFunctionText(save) {
|
||||
if (!save) return;
|
||||
if (!save.damageFunction) {
|
||||
return '**Half damage on successful save**';
|
||||
}
|
||||
@@ -239,9 +246,12 @@ function multiplierAppliesTo(damageProp, multiplierType) {
|
||||
}
|
||||
}
|
||||
|
||||
function dealDamage({ target, damageType, amount, actionContext }) {
|
||||
async function dealDamage(
|
||||
action: EngineAction, prop: any, result: TaskResult, userInput: InputProvider,
|
||||
targetId: string, damageType: string, amount: number
|
||||
) {
|
||||
// Get all the health bars and do damage to them
|
||||
let healthBars = getPropertiesOfType(target._id, 'attribute');
|
||||
let healthBars = getPropertiesOfType(targetId, 'attribute');
|
||||
|
||||
// Keep only the healthbars that can take damage/healing
|
||||
healthBars = healthBars.filter((bar) => {
|
||||
@@ -276,24 +286,20 @@ function dealDamage({ target, damageType, amount, actionContext }) {
|
||||
const totalDamage = amount;
|
||||
let damageLeft = totalDamage;
|
||||
if (damageType === 'healing') damageLeft = -totalDamage;
|
||||
healthBars.forEach(healthBar => {
|
||||
for (const healthBar of healthBars) {
|
||||
if (damageLeft === 0) return;
|
||||
// Replace the healthbar by the one in the action context if we can
|
||||
// The damagePropertyWork function bashes the prop with the damage
|
||||
// So we can use the new value in later action properties
|
||||
if (healthBar.variableName) {
|
||||
const targetHealthBar = target.variables[healthBar.variableName];
|
||||
if (targetHealthBar?._id === healthBar._id) {
|
||||
healthBar = targetHealthBar;
|
||||
}
|
||||
}
|
||||
// Do the damage
|
||||
let damageAdded = damagePropertyWork({
|
||||
prop: healthBar,
|
||||
operation: 'increment',
|
||||
value: damageLeft,
|
||||
actionContext
|
||||
});
|
||||
const damageAdded = await applyTask(action, {
|
||||
prop,
|
||||
targetIds: [targetId],
|
||||
subtaskFn: 'damageProp',
|
||||
params: {
|
||||
operation: 'increment',
|
||||
value: +damageLeft || 0,
|
||||
targetProp: healthBar,
|
||||
},
|
||||
}, userInput);
|
||||
|
||||
damageLeft -= damageAdded;
|
||||
// Prevent overflow
|
||||
if (
|
||||
@@ -303,6 +309,6 @@ function dealDamage({ target, damageType, amount, actionContext }) {
|
||||
) {
|
||||
damageLeft = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
return totalDamage;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { applyDefaultAfterPropTasks } from '/imports/api/engine/action/functions
|
||||
import { rollAndReduceCalculation } from '/imports/api/engine/action/functions/recalculateCalculation';
|
||||
import { PropTask } from '/imports/api/engine/action/tasks/Task';
|
||||
import TaskResult from '/imports/api/engine/action/tasks/TaskResult';
|
||||
import { toString } from '/imports/parser/resolve';
|
||||
import toString from '/imports/parser/toString';
|
||||
|
||||
export default async function roll(
|
||||
task: PropTask, action: EngineAction, result: TaskResult, userInput
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
|
||||
type InputProvider = {
|
||||
rollDice(
|
||||
action: EngineAction, dice: { number: number, diceSize: number }[]
|
||||
dice: { number: number, diceSize: number }[]
|
||||
): Promise<number[][]>;
|
||||
/**
|
||||
* Choose from a provided selection
|
||||
@@ -11,7 +9,6 @@ type InputProvider = {
|
||||
* @param quantity Number of choices to make [min, max] inclusive, where -1 means no limit
|
||||
*/
|
||||
choose(
|
||||
action: EngineAction,
|
||||
choices: ({ _id: string } & Record<string, any>)[],
|
||||
quantity?: [min: number, max: number],
|
||||
): Promise<string[]>;
|
||||
|
||||
@@ -6,7 +6,7 @@ const inputProviderForTests: InputProvider = {
|
||||
* rollDice function returns the average roll for every dice rolled
|
||||
* [5d10, 1d4] => [[6,6,6,6,6], [3]]
|
||||
*/
|
||||
async rollDice(action, dice) {
|
||||
async rollDice(dice = []) {
|
||||
const result: number[][] = [];
|
||||
for (const diceRoll of dice) {
|
||||
const averageRoll = Math.round(diceRoll.diceSize / 2);
|
||||
@@ -21,7 +21,7 @@ const inputProviderForTests: InputProvider = {
|
||||
/**
|
||||
* For testing, always return the minimum number of choices, always choosing the first options
|
||||
*/
|
||||
async choose(action, choices, quantity = [1, 1]) {
|
||||
async choose(choices, quantity = [1, 1]) {
|
||||
const chosen: string[] = [];
|
||||
const choiceQuantity = quantity[0] <= 0 ? 1 : quantity[0];
|
||||
for (let i = 0; i < choiceQuantity && i < choices.length; i += 1) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Context, toPrimitiveOrString } from '/imports/parser/resolve';
|
||||
import Context from '../../../../parser/types/Context';
|
||||
import toPrimitiveOrString from '/imports/parser/toPrimitiveOrString';
|
||||
import {
|
||||
aggregateCalculationEffects,
|
||||
aggregateCalculationProficiencies,
|
||||
@@ -26,7 +27,7 @@ export default async function recalculateCalculation(
|
||||
const {
|
||||
result: unaffectedResult,
|
||||
context
|
||||
} = resolve(parseLevel, calcObj.parseNode, scope);
|
||||
} = await resolve(parseLevel, calcObj.parseNode, scope);
|
||||
calcObj.valueNode = unaffectedResult;
|
||||
|
||||
// store the unaffected value
|
||||
@@ -47,7 +48,7 @@ export default async function recalculateCalculation(
|
||||
// Resolve the modified valueNode, use the same context
|
||||
const {
|
||||
result: finalResult
|
||||
} = resolve(parseLevel, calcObj.parseNode, scope, context);
|
||||
} = await resolve(parseLevel, calcObj.parseNode, scope, context);
|
||||
|
||||
// Store the errors
|
||||
calcObj.errors = context.errors;
|
||||
@@ -55,12 +56,12 @@ export default async function recalculateCalculation(
|
||||
// Store the value and its primitive
|
||||
calcObj.value = toPrimitiveOrString(finalResult);
|
||||
calcObj.valueNode = finalResult;
|
||||
|
||||
}
|
||||
|
||||
export async function rollAndReduceCalculation(
|
||||
calcObj: CalculatedField, action: EngineAction, userInput: InputProvider
|
||||
) {
|
||||
if (!calcObj) throw new Error('calcObj is required');
|
||||
const context = new Context();
|
||||
const scope = await getEffectiveActionScope(action);
|
||||
// Compile
|
||||
@@ -68,10 +69,10 @@ export async function rollAndReduceCalculation(
|
||||
const compiled = calcObj.valueNode;
|
||||
|
||||
// Roll
|
||||
const { result: rolled } = resolve('roll', calcObj.valueNode, scope, context);
|
||||
const { result: rolled } = await resolve('roll', calcObj.valueNode, scope, context, userInput);
|
||||
|
||||
// Reduce
|
||||
const { result: reduced } = resolve('reduce', rolled, scope, context);
|
||||
const { result: reduced } = await resolve('reduce', rolled, scope, context, userInput);
|
||||
|
||||
// Return
|
||||
return { compiled, rolled, reduced, errors: context.errors };
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import embedInlineCalculations from '/imports/api/engine/computation/utility/embedInlineCalculations';
|
||||
import recalculateCalculation from './recalculateCalculation'
|
||||
|
||||
export default async function recalculateInlineCalculations(inlineCalcObj, action) {
|
||||
export default async function recalculateInlineCalculations(inlineCalcObj, action, parseLevel, userInput) {
|
||||
// Skip if there are no calculations
|
||||
if (!inlineCalcObj?.inlineCalculations?.length) return;
|
||||
// Recalculate each calculation with the current scope
|
||||
const promises = [];
|
||||
for (const calc of inlineCalcObj.inlineCalculations) {
|
||||
promises.push(recalculateCalculation(calc, action));
|
||||
await recalculateCalculation(calc, action, undefined, userInput);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
// Embed the new calculated values
|
||||
embedInlineCalculations(inlineCalcObj);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getFromScope } from '/imports/api/creature/creatures/CreatureVariables';
|
||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope';
|
||||
import recalculateCalculation from '/imports/api/engine/action/functions/recalculateCalculation';
|
||||
import TaskResult from '/imports/api/engine/action/tasks/TaskResult';
|
||||
import applyTask from '/imports/api/engine/action/tasks/applyTask';
|
||||
import { getSingleProperty } from '/imports/api/engine/loadCreatures';
|
||||
@@ -31,7 +32,7 @@ export default async function spendResources(
|
||||
for (const att of prop.resources.attributesConsumed) {
|
||||
const scope = await getEffectiveActionScope(action);
|
||||
const statToDamage = await getFromScope(att.variableName, scope);
|
||||
await recalculateCalculation(att.quantity, action, 'reduce');
|
||||
await recalculateCalculation(att.quantity, action, 'reduce', userInput);
|
||||
await applyTask(action, {
|
||||
prop,
|
||||
targetIds: [action.creatureId],
|
||||
@@ -48,7 +49,7 @@ export default async function spendResources(
|
||||
// Iterate through all the items consumed and consume them
|
||||
if (prop.resources?.itemsConsumed?.length) {
|
||||
for (const itemConsumed of prop.resources.itemsConsumed) {
|
||||
await recalculateCalculation(itemConsumed.quantity, action, 'reduce');
|
||||
await recalculateCalculation(itemConsumed.quantity, action, 'reduce', userInput);
|
||||
if (!itemConsumed.itemId) {
|
||||
throw 'No ammo was selected';
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import Context from '../../../../parser/types/Context';
|
||||
|
||||
/**
|
||||
* The result of running a task containing all the changes that need to be made to the listed
|
||||
* targets
|
||||
@@ -36,6 +38,22 @@ export default class TaskResult {
|
||||
}
|
||||
latestMutation.contents.push(content);
|
||||
}
|
||||
appendParserContextErrors(context: Context, targetIds) {
|
||||
if (!context.errors?.length) return;
|
||||
if (!this.mutations.length) {
|
||||
this.mutations.push({ targetIds, contents: [] });
|
||||
}
|
||||
const latestMutation = this.mutations[this.mutations.length - 1]
|
||||
if (!latestMutation.contents) {
|
||||
latestMutation.contents = [];
|
||||
}
|
||||
context.errors?.forEach(error => {
|
||||
latestMutation.contents?.push({
|
||||
name: 'Error',
|
||||
value: error.message,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export type Mutation = {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { getSingleProperty } from '/imports/api/engine/loadCreatures';
|
||||
|
||||
export default async function applyDamagePropTask(
|
||||
task: DamagePropTask, action: EngineAction, result: TaskResult, userInput
|
||||
): Promise<void> {
|
||||
): Promise<number> {
|
||||
const prop = task.prop;
|
||||
|
||||
if (task.targetIds.length > 1) {
|
||||
@@ -74,7 +74,7 @@ export default async function applyDamagePropTask(
|
||||
let damage, newValue, increment;
|
||||
targetProp = await getSingleProperty(targetId, targetPropId);
|
||||
|
||||
if (!targetProp) return;
|
||||
if (!targetProp) return value;
|
||||
|
||||
if (operation === 'set') {
|
||||
const total = targetProp.total || 0;
|
||||
@@ -128,4 +128,5 @@ export default async function applyDamagePropTask(
|
||||
});
|
||||
}
|
||||
await applyTriggers(action, prop, [action.creatureId], 'damageProperty.after', userInput);
|
||||
return increment;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
import Task from './Task';
|
||||
import Task, { DamagePropTask, ItemAsAmmoTask, PropTask } from './Task';
|
||||
import TaskResult from '/imports/api/engine/action/tasks/TaskResult';
|
||||
import applyDamagePropTask from '/imports/api/engine/action/tasks/applyDamagePropTask';
|
||||
import applyItemAsAmmoTask from '/imports/api/engine/action/tasks/applyItemAsAmmoTask';
|
||||
@@ -7,9 +7,19 @@ import { getSingleProperty } from '/imports/api/engine/loadCreatures';
|
||||
import applyProperties from '/imports/api/engine/action/applyProperties';
|
||||
import InputProvider from '/imports/api/engine/action/functions/InputProvider';
|
||||
|
||||
// DamagePropTask promises a number of actual damage done
|
||||
export default async function applyTask(
|
||||
action: EngineAction, task: DamagePropTask, userInput: InputProvider
|
||||
): Promise<number>
|
||||
|
||||
// Other tasks promise nothing
|
||||
export default async function applyTask(
|
||||
action: EngineAction, task: PropTask | ItemAsAmmoTask, userInput: InputProvider
|
||||
): Promise<void>
|
||||
|
||||
export default async function applyTask(
|
||||
action: EngineAction, task: Task, userInput: InputProvider
|
||||
): Promise<void> {
|
||||
): Promise<void | number> {
|
||||
action.taskCount += 1;
|
||||
if (action.taskCount > 100) throw 'Only 100 properties can be applied at once';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { traverse } from '/imports/parser/resolve';
|
||||
import traverse from '/imports/parser/traverse';
|
||||
|
||||
export default function linkCalculationDependencies(dependencyGraph, prop, { propsById }) {
|
||||
prop._computationDetails.calculations.forEach(calcObj => {
|
||||
|
||||
@@ -125,6 +125,6 @@ function parseCalculation(calcObj) {
|
||||
message: prettifyParseError(e),
|
||||
};
|
||||
calcObj.parseError = error;
|
||||
calcObj.parseNode = errorNode.create({ error });
|
||||
calcObj.parseNode = errorNode.create({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
export default function computeAction(computation, node) {
|
||||
import CreatureComputation from '/imports/api/engine/computation/CreatureComputation';
|
||||
import { Node } from 'ngraph.graph';
|
||||
|
||||
export default function computeAction(computation: CreatureComputation, node: Node) {
|
||||
const prop = node.data;
|
||||
if (Number.isFinite(prop.uses?.value)) {
|
||||
prop.usesLeft = prop.uses.value - (prop.usesUsed || 0);
|
||||
@@ -2,27 +2,30 @@ import call from '/imports/parser/parseTree/call';
|
||||
import constant from '/imports/parser/parseTree/constant';
|
||||
import operator from '/imports/parser/parseTree/operator';
|
||||
import parenthesis from '/imports/parser/parseTree/parenthesis';
|
||||
import resolve, { toPrimitiveOrString } from '/imports/parser/resolve';
|
||||
import resolve from '/imports/parser/resolve';
|
||||
import toPrimitiveOrString from '/imports/parser/toPrimitiveOrString';
|
||||
|
||||
export default function computeCalculation(computation, node) {
|
||||
export default async function computeCalculation(computation, node) {
|
||||
const calcObj = node.data;
|
||||
if (!calcObj) return;
|
||||
// resolve the parse node into the initial value
|
||||
resolveCalculationNode(calcObj, calcObj.parseNode, computation.scope);
|
||||
await resolveCalculationNode(calcObj, calcObj.parseNode, computation.scope);
|
||||
|
||||
// link and aggregate the effects and proficiencies
|
||||
// link the effects and proficiencies
|
||||
linkCalculationEffects(node, computation);
|
||||
aggregateCalculationEffects(calcObj, id => computation.propsById[id]);
|
||||
linkCalculationProficiencies(node, computation)
|
||||
aggregateCalculationProficiencies(calcObj, id => computation.propsById[id], computation.scope['proficiencyBonus']?.value || 0);
|
||||
|
||||
// Store the unaffected value
|
||||
if (calcObj.effectIds || calcObj.proficiencyIds) {
|
||||
calcObj.unaffected = toPrimitiveOrString(calcObj.valueNode);
|
||||
}
|
||||
|
||||
// Aggregate the effects and proficiencies
|
||||
aggregateCalculationEffects(calcObj, id => computation.propsById[id]);
|
||||
aggregateCalculationProficiencies(calcObj, id => computation.propsById[id], computation.scope['proficiencyBonus']?.value || 0);
|
||||
|
||||
// Resolve the valueNode after effects and proficiencies have been applied to it
|
||||
resolveCalculationNode(calcObj, calcObj.valueNode, computation.scope);
|
||||
await resolveCalculationNode(calcObj, calcObj.valueNode, computation.scope);
|
||||
|
||||
// Store the value as a primitive
|
||||
calcObj.value = toPrimitiveOrString(calcObj.valueNode);
|
||||
@@ -32,10 +35,13 @@ export default function computeCalculation(computation, node) {
|
||||
delete calcObj._localScope;
|
||||
}
|
||||
|
||||
export function resolveCalculationNode(calculation, parseNode, scope, givenContext) {
|
||||
export async function resolveCalculationNode(calculation, parseNode, scope, givenContext) {
|
||||
if (!parseNode) throw new Error('parseNode is required');
|
||||
const fn = calculation._parseLevel;
|
||||
const calculationScope = { ...calculation._localScope, ...scope };
|
||||
const { result: resultNode, context } = resolve(fn, parseNode, calculationScope, givenContext);
|
||||
const { result: resultNode, context } = await resolve(fn, parseNode, calculationScope, givenContext);
|
||||
if (calculation.hash === 1318417319946211 && calculation._key === 'attackRoll') console.log({ calculation, resultNode, parseNode, ers: context.errors })
|
||||
|
||||
calculation.errors = context.errors;
|
||||
calculation.valueNode = resultNode;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { has } from 'lodash';
|
||||
import { resolveCalculationNode } from '/imports/api/engine/computation/computeComputation/computeByType/computeCalculation';
|
||||
|
||||
export default function computePointBuy(computation, node) {
|
||||
export default async function computePointBuy(computation, node) {
|
||||
const prop = node.data;
|
||||
const min = has(prop, 'min.value') ? prop.min.value : null;
|
||||
const max = has(prop, 'max.value') ? prop.max.value : null;
|
||||
prop.spent = 0;
|
||||
prop.values?.forEach(row => {
|
||||
|
||||
for (const row of prop.values || []) {
|
||||
row.spent = 0;
|
||||
if (row.value === undefined) return;
|
||||
const costFunction = EJSON.clone(prop.cost);
|
||||
@@ -22,7 +21,7 @@ export default function computePointBuy(computation, node) {
|
||||
}
|
||||
// Evaluate the cost function
|
||||
if (!costFunction) return;
|
||||
resolveCalculationNode(costFunction, costFunction.parseNode, {
|
||||
await resolveCalculationNode(costFunction, costFunction.parseNode, {
|
||||
...computation.scope, value: row.value
|
||||
});
|
||||
// Write calculation errors
|
||||
@@ -37,7 +36,7 @@ export default function computePointBuy(computation, node) {
|
||||
row.spent = costFunction.value;
|
||||
prop.spent += costFunction.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
prop.pointsLeft = (prop.total?.value || 0) - (prop.spent || 0);
|
||||
if (prop.spent > prop.total?.value) {
|
||||
prop.errors = prop.errors || [];
|
||||
|
||||
@@ -8,11 +8,9 @@ export default function computeVariableAsAttribute(computation, node, prop) {
|
||||
// Apply damage in a way that respects the damage rules, modifying damage if need be
|
||||
// Bound the damage
|
||||
if (!prop.ignoreLowerLimit && prop.damage > prop.total) {
|
||||
console.log(`reducing damage from ${prop.damage} to ${prop.total}`);
|
||||
prop.damage = prop.total;
|
||||
}
|
||||
if (!prop.ignoreUpperLimit && prop.damage < 0) {
|
||||
console.log(`increasing damage from ${prop.damage} to 0`);
|
||||
prop.damage = 0;
|
||||
}
|
||||
// Apply damage
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import getAggregatorResult from './getAggregatorResult';
|
||||
|
||||
export default function computeVariableAsToggle(computation, node, prop) {
|
||||
let result = getAggregatorResult(node, prop) || 0;
|
||||
let result = getAggregatorResult(node) || 0;
|
||||
|
||||
prop.value = !!result || !!prop.enabled || !!prop.condition?.value;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import { assert } from 'chai';
|
||||
import computeCreatureComputation from '../../computeCreatureComputation';
|
||||
import clean from '../../utility/cleanProp.testFn';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
|
||||
const prop = computation.propsById['actionId'];
|
||||
assert.equal(prop.summary.value, 'test summary 3 without referencing anything 7');
|
||||
|
||||
@@ -3,9 +3,9 @@ import { assert } from 'chai';
|
||||
import computeCreatureComputation from '../../computeCreatureComputation';
|
||||
import clean from '../../utility/cleanProp.testFn';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const prop = id => computation.propsById[id];
|
||||
const scope = variableName => computation.scope[variableName];
|
||||
assert.equal(prop('emptyId').value, 0, 'calculates empty props to zero');
|
||||
|
||||
@@ -3,10 +3,13 @@ import { assert } from 'chai';
|
||||
import computeCreatureComputation from '../../computeCreatureComputation.js';
|
||||
import clean from '../../utility/cleanProp.testFn.js';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const prop = id => computation.propsById[id];
|
||||
// Tag targeted effects make complicated parse trees
|
||||
console.log(prop('attackAction2'));
|
||||
assert.equal(prop('attackAction2').attackRoll.value, 'min(3 + d4, d100)', 'Tag targeted effects change the attack roll correctly');
|
||||
// Tags target effects on attributes
|
||||
assert.equal(prop('taggedCon').value, 26, 'Tagged targeted effects affect attribute values');
|
||||
assert.equal(prop('taggedCon').baseValue.value, 10, 'Tag targeted effects target the attribute itself, not the base value');
|
||||
@@ -14,7 +17,6 @@ export default function () {
|
||||
assert.equal(prop('attackAction').attackRoll.value, 20, 'Tag targeted effects change the attack roll correctly');
|
||||
// Tag target effects can deal with rolls
|
||||
assert.equal(prop('attackAction').attackRoll.value, 20, 'Tag targeted effects change the attack roll correctly');
|
||||
assert.equal(prop('attackAction2').attackRoll.value, 'min(3 + d4, d100)', 'Tag targeted effects change the attack roll correctly');
|
||||
}
|
||||
|
||||
var testProperties = [
|
||||
|
||||
@@ -4,9 +4,9 @@ import computeCreatureComputation from '../../computeCreatureComputation';
|
||||
import clean from '../../utility/cleanProp.testFn';
|
||||
import { applyNestedSetProperties } from '/imports/api/parenting/parentingFunctions';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const scope = id => computation.scope[id];
|
||||
const prop = id => computation.propsById[id];
|
||||
assert.equal(scope('level').value, 5);
|
||||
|
||||
@@ -4,9 +4,9 @@ import computeCreatureComputation from '../../computeCreatureComputation';
|
||||
import clean from '../../utility/cleanProp.testFn';
|
||||
import { applyNestedSetProperties } from '/imports/api/parenting/parentingFunctions';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const prop = id => computation.propsById[id];
|
||||
assert.equal(prop('attId').value, 6);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import { assert } from 'chai';
|
||||
import computeCreatureComputation from '../../computeCreatureComputation';
|
||||
import clean from '../../utility/cleanProp.testFn';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const scope = id => computation.scope[id];
|
||||
assert.isTrue(scope('blugeoning').vulnerability);
|
||||
assert.isTrue(scope('customDamage').resistance);
|
||||
|
||||
@@ -4,9 +4,9 @@ import computeCreatureComputation from '../../computeCreatureComputation';
|
||||
import clean from '../../utility/cleanProp.testFn';
|
||||
import { propsFromForest } from '/imports/api/properties/tests/propTestBuilder.testFn';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const prop = id => computation.propsById[id];
|
||||
assert.equal(prop('strengthId').value, 26);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import computeCreatureComputation from '../../computeCreatureComputation';
|
||||
import clean from '../../utility/cleanProp.testFn';
|
||||
import { applyNestedSetProperties, compareOrder } from '/imports/api/parenting/parentingFunctions';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const prop = id => computation.propsById[id];
|
||||
const scope = id => computation.scope[id].value;
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ import { assert } from 'chai';
|
||||
import computeCreatureComputation from '../../computeCreatureComputation.js';
|
||||
import { propsFromForest } from '/imports/api/properties/tests/propTestBuilder.testFn.js';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const prop = id => computation.propsById[id];
|
||||
assert.equal(prop('strengthId').value, 11, 'Point buys should apply a base value when active');
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import computeCreatureComputation from '../../computeCreatureComputation';
|
||||
import clean from '../../utility/cleanProp.testFn';
|
||||
import { applyNestedSetProperties, compareOrder } from '/imports/api/parenting/parentingFunctions';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
const hasLink = computation.dependencyGraph.hasLink;
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const prop = id => computation.propsById[id];
|
||||
assert.equal(
|
||||
prop('strengthId').value, 8,
|
||||
|
||||
@@ -3,9 +3,9 @@ import { assert } from 'chai';
|
||||
import computeCreatureComputation from '../../computeCreatureComputation';
|
||||
import clean from '../../utility/cleanProp.testFn';
|
||||
|
||||
export default function () {
|
||||
export default async function () {
|
||||
const computation = buildComputationFromProps(testProperties);
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
const prop = id => computation.propsById[id];
|
||||
|
||||
assert.equal(prop('atheleticsId').proficiency, 2, 'Inherits proficiency from ability');
|
||||
|
||||
@@ -4,7 +4,7 @@ import embedInlineCalculations from './utility/embedInlineCalculations';
|
||||
import { removeEmptyCalculations } from './buildComputation/parseCalculationFields';
|
||||
import path from 'ngraph.path';
|
||||
|
||||
export default function computeCreatureComputation(computation) {
|
||||
export default async function computeCreatureComputation(computation) {
|
||||
const stack = [];
|
||||
// Computation scope of {variableName: prop}
|
||||
const graph = computation.dependencyGraph;
|
||||
@@ -31,7 +31,7 @@ export default function computeCreatureComputation(computation) {
|
||||
top._visited = true;
|
||||
stack.pop();
|
||||
// Compute the top object of the stack
|
||||
compute(computation, top);
|
||||
await compute(computation, top);
|
||||
} else {
|
||||
top._visitedChildren = true;
|
||||
// Push dependencies to graph to be computed first
|
||||
@@ -40,14 +40,16 @@ export default function computeCreatureComputation(computation) {
|
||||
}
|
||||
|
||||
// Finish the props after the dependency graph has been traversed
|
||||
computation.props.forEach(finalizeProp);
|
||||
for (const prop of computation.props) {
|
||||
finalizeProp(prop);
|
||||
}
|
||||
}
|
||||
|
||||
function compute(computation, node) {
|
||||
async function compute(computation, node) {
|
||||
// Determine the prop's active status by its toggles
|
||||
computeToggles(computation, node);
|
||||
// Compute the property by type
|
||||
computeByType[node.data?.type || '_variable']?.(computation, node);
|
||||
await computeByType[node.data?.type || '_variable']?.(computation, node);
|
||||
}
|
||||
|
||||
function pushDependenciesToStack(nodeId, graph, stack, computation) {
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
import { get } from 'lodash';
|
||||
|
||||
export default function applyFnToKey(doc, key, fn){
|
||||
if (key.includes('.$')){
|
||||
export default function applyFnToKey(doc, key, fn) {
|
||||
if (key.includes('.$')) {
|
||||
applyToArrayKey(doc, key, fn);
|
||||
} else {
|
||||
applyToSingleKey(doc, key, fn);
|
||||
}
|
||||
}
|
||||
|
||||
function applyToSingleKey(doc, key, fn){
|
||||
export async function applyFnToKeyAsync(doc, key, fn) {
|
||||
if (key.includes('.$')) {
|
||||
await applyToArrayKeyAsync(doc, key, fn);
|
||||
} else {
|
||||
await applyToSingleKeyAsync(doc, key, fn);
|
||||
}
|
||||
}
|
||||
|
||||
function applyToSingleKey(doc, key, fn) {
|
||||
// call the function with the current value and document for context
|
||||
fn(doc, key);
|
||||
return fn(doc, key);
|
||||
}
|
||||
|
||||
async function applyToSingleKeyAsync(doc, key, fn) {
|
||||
// call the function with the current value and document for context
|
||||
return await fn(doc, key);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -19,7 +32,7 @@ function applyToSingleKey(doc, key, fn){
|
||||
* Warning: Order might be confusing, it will traverse the deepest array in order
|
||||
* but the shallower arrays in reverse order
|
||||
*/
|
||||
function applyToArrayKey(doc, key, fn){
|
||||
function applyToArrayKey(doc, key, fn) {
|
||||
const keySplit = key.split('.$');
|
||||
// Stack based depth first traversal of arrays
|
||||
const array = get(doc, keySplit[0]);
|
||||
@@ -30,11 +43,12 @@ function applyToArrayKey(doc, key, fn){
|
||||
currentPath: keySplit[0],
|
||||
indices: [],
|
||||
}];
|
||||
while(stack.length){
|
||||
while (stack.length) {
|
||||
const state = stack.pop();
|
||||
for (let index in state.array){
|
||||
if (!state) break;
|
||||
for (let index in state.array) {
|
||||
const currentPath = `${state.currentPath}[${index}]${state.paths[0]}`
|
||||
if (state.paths.length == 1){
|
||||
if (state.paths.length == 1) {
|
||||
applyToSingleKey(doc, currentPath, fn);
|
||||
} else {
|
||||
const array = get(doc, currentPath);
|
||||
@@ -49,3 +63,35 @@ function applyToArrayKey(doc, key, fn){
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function applyToArrayKeyAsync(doc, key, fn) {
|
||||
const keySplit = key.split('.$');
|
||||
// Stack based depth first traversal of arrays
|
||||
const array = get(doc, keySplit[0]);
|
||||
if (!array) return;
|
||||
const stack = [{
|
||||
array,
|
||||
paths: keySplit.slice(1),
|
||||
currentPath: keySplit[0],
|
||||
indices: [],
|
||||
}];
|
||||
while (stack.length) {
|
||||
const state = stack.pop();
|
||||
if (!state) break;
|
||||
for (let index in state.array) {
|
||||
const currentPath = `${state.currentPath}[${index}]${state.paths[0]}`
|
||||
if (state.paths.length == 1) {
|
||||
await applyToSingleKey(doc, currentPath, fn);
|
||||
} else {
|
||||
const array = get(doc, currentPath);
|
||||
if (!array) return;
|
||||
stack.push({
|
||||
array,
|
||||
paths: state.paths.slice(1),
|
||||
currentPath,
|
||||
indices: [...state.indices, index],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,16 @@ import writeAlteredProperties from './computation/writeComputation/writeAlteredP
|
||||
import writeScope from './computation/writeComputation/writeScope';
|
||||
import writeErrors from './computation/writeComputation/writeErrors';
|
||||
|
||||
export default function computeCreature(creatureId) {
|
||||
export default async function computeCreature(creatureId) {
|
||||
if (Meteor.isClient) return;
|
||||
// console.log('compute ' + creatureId);
|
||||
const computation = buildCreatureComputation(creatureId);
|
||||
computeComputation(computation, creatureId);
|
||||
await computeComputation(computation, creatureId);
|
||||
}
|
||||
|
||||
function computeComputation(computation, creatureId) {
|
||||
async function computeComputation(computation, creatureId) {
|
||||
try {
|
||||
computeCreatureComputation(computation);
|
||||
await computeCreatureComputation(computation);
|
||||
writeAlteredProperties(computation);
|
||||
writeScope(creatureId, computation);
|
||||
} catch (e) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import getUserLibraryIds from './getUserLibraryIds';
|
||||
import { intersection, union } from 'lodash';
|
||||
|
||||
export default function getCreatureLibraryIds(creature, userId) {
|
||||
if (!userId) console.log('no userId, returning empty array');
|
||||
if (!userId) return [];
|
||||
|
||||
// Get the ids of libraries the user is permitted to view
|
||||
|
||||
@@ -6,7 +6,9 @@ import {
|
||||
prettifyParseError,
|
||||
} from '/imports/parser/parser';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
|
||||
import resolve, { Context, traverse } from '/imports/parser/resolve';
|
||||
import resolve from '/imports/parser/resolve';
|
||||
import Context from '../../parser/types/Context';
|
||||
import traverse from '/imports/parser/traverse';
|
||||
|
||||
/*
|
||||
* Constants are primitive values that can be used elsewhere in computations
|
||||
|
||||
@@ -17,7 +17,6 @@ export function propsFromForest(
|
||||
const children = prop.children;
|
||||
// Check the property has a type
|
||||
if (!prop.type) {
|
||||
console.log(prop);
|
||||
throw 'Type is required on every property, not found on above doc';
|
||||
}
|
||||
// Create the clean doc
|
||||
|
||||
19
app/imports/api/utility/asyncMap.ts
Normal file
19
app/imports/api/utility/asyncMap.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Async compatible map that processes all items in parallel
|
||||
*/
|
||||
export async function parallelMap(array: any[], fn: (doc: any) => Promise<any>): Promise<any[]> {
|
||||
return await Promise.all(array.map(fn));
|
||||
}
|
||||
|
||||
/**
|
||||
* Async compatible map that processes all items in series
|
||||
*/
|
||||
export async function serialMap(array: any[], fn: (doc: any) => Promise<any>): Promise<any[]> {
|
||||
const results: any[] = [];
|
||||
for (const doc of array) {
|
||||
const result = await fn(doc);
|
||||
results.push(result);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -476,3 +476,4 @@ export default {
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
resolveimport { toString } from '/imports/parser/toString';
|
||||
|
||||
@@ -558,3 +558,4 @@ export default {
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
resolveimport { toString } from '/imports/parser/toString';
|
||||
|
||||
@@ -183,3 +183,4 @@ export default {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
resolveimport { toString } from '/imports/parser/toString';
|
||||
|
||||
@@ -114,13 +114,13 @@ export default {
|
||||
min(row) {
|
||||
return row.min ? row.min && row.min.value : this.model.min && this.model.min.value;
|
||||
},
|
||||
dragSlider(row, value) {
|
||||
async dragSlider(row, value) {
|
||||
const currentSpent = this.model.spent;
|
||||
let newSpent = currentSpent - row.spent;
|
||||
const costFunction = EJSON.clone(row.cost || this.model.cost);
|
||||
if (!costFunction?.parseNode) return;
|
||||
if (costFunction) costFunction.parseLevel = 'reduce';
|
||||
resolveCalculationNode(costFunction, costFunction.parseNode, { value });
|
||||
await resolveCalculationNode(costFunction, costFunction.parseNode, { value });
|
||||
if (Number.isFinite(costFunction.value)) {
|
||||
newSpent += costFunction.value;
|
||||
if (this.useEstimate) this.estimatedCost = newSpent;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// http://github.com/Hardmath123/nearley
|
||||
function id(x) { return x[0]; }
|
||||
|
||||
import node from './parseTree/_index';
|
||||
import node from '/imports/parser/parseTree/';
|
||||
|
||||
import moo from 'moo';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@preprocessor esmodule
|
||||
@{%
|
||||
import node from './parseTree/_index';
|
||||
import node from '/imports/parser/parseTree/_index';
|
||||
|
||||
import moo from 'moo';
|
||||
|
||||
|
||||
15
app/imports/parser/map.ts
Normal file
15
app/imports/parser/map.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import nodeTypeIndex from './parseTree';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
|
||||
export default async function map(node: ParseNode, fn: (ParseNode) => Promise<any>): Promise<any> {
|
||||
if (!node) return;
|
||||
const type = nodeTypeIndex[node.parseType];
|
||||
if (!type) {
|
||||
console.error(node);
|
||||
throw new Meteor.Error('Not valid parse node');
|
||||
}
|
||||
if ('map' in type) {
|
||||
return type.map(node as any, fn, map);
|
||||
}
|
||||
return fn(node);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { ParseNode } from '/imports/parser/parser';
|
||||
import { ResolvedResult, Context } from '/imports/parser/resolve';
|
||||
|
||||
export type ResolveLevel = 'compile' | 'roll' | 'reduce';
|
||||
|
||||
export default interface NodeFactory {
|
||||
create(node: Partial<ParseNode>): ParseNode;
|
||||
|
||||
compile?(
|
||||
node: ParseNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
|
||||
roll?(
|
||||
node: ParseNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
|
||||
reduce?(
|
||||
node: ParseNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
|
||||
resolve?(
|
||||
fn: ResolveLevel, node: ParseNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
|
||||
toString(node: ParseNode): string;
|
||||
traverse?(node: ParseNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
map?(node: ParseNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import accessor from './accessor';
|
||||
import array from './array';
|
||||
import call from './call';
|
||||
import constant from './constant';
|
||||
import error from './error';
|
||||
import ifNode from './if';
|
||||
import index from './indexNode';
|
||||
import not from './not';
|
||||
import operator from './operator';
|
||||
import parenthesis from './parenthesis';
|
||||
import roll from './roll';
|
||||
import rollArray from './rollArray';
|
||||
import unaryOperator from './unaryOperator';
|
||||
import NodeFactory from '/imports/parser/parseTree/NodeFactory';
|
||||
|
||||
const factories: Record<string, NodeFactory> = {
|
||||
accessor,
|
||||
array,
|
||||
call,
|
||||
constant,
|
||||
error,
|
||||
if: ifNode,
|
||||
index,
|
||||
not,
|
||||
operator,
|
||||
parenthesis,
|
||||
roll,
|
||||
rollArray,
|
||||
// What used to be symbols are now just treated as accessors without a path
|
||||
symbol: accessor,
|
||||
unaryOperator,
|
||||
};
|
||||
|
||||
export default factories;
|
||||
@@ -1,28 +1,20 @@
|
||||
import constant from './constant';
|
||||
import array from './array';
|
||||
import resolve, { Context, ResolvedResult } from '/imports/parser/resolve';
|
||||
import constant from '/imports/parser/parseTree/constant';
|
||||
import array from '/imports/parser/parseTree/array';
|
||||
import ResolvedResult from '/imports/parser/types/ResolvedResult';
|
||||
import { getFromScope } from '/imports/api/creature/creatures/CreatureVariables';
|
||||
import NodeFactory from '/imports/parser/parseTree/NodeFactory';
|
||||
import ResolveLevelFunction from '/imports/parser/types/ResolveLevelFunction';
|
||||
|
||||
export type AccessorNode = {
|
||||
parseType: 'accessor';
|
||||
parseType: 'accessor' | 'symbol';
|
||||
path?: string[];
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface AccessorFactory extends NodeFactory {
|
||||
type AccessorFactory = {
|
||||
create(node: Partial<AccessorNode>): AccessorNode;
|
||||
compile(
|
||||
node: AccessorNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
roll?: undefined;
|
||||
resolve?: undefined;
|
||||
reduce(
|
||||
node: AccessorNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
compile: ResolveLevelFunction<AccessorNode>;
|
||||
reduce: ResolveLevelFunction<AccessorNode>;
|
||||
toString(node: AccessorNode): string;
|
||||
traverse?: undefined;
|
||||
map?: undefined;
|
||||
}
|
||||
|
||||
const accessor: AccessorFactory = {
|
||||
@@ -33,9 +25,7 @@ const accessor: AccessorFactory = {
|
||||
name,
|
||||
};
|
||||
},
|
||||
compile(
|
||||
node: AccessorNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult {
|
||||
async compile(node, scope, context) {
|
||||
let value = getFromScope(node.name, scope);
|
||||
// Get the value from the given path
|
||||
node.path?.forEach(name => {
|
||||
@@ -102,9 +92,9 @@ const accessor: AccessorFactory = {
|
||||
context,
|
||||
};
|
||||
},
|
||||
reduce(node, scope, context): ResolvedResult {
|
||||
let { result } = accessor.compile(node, scope, context);
|
||||
({ result } = resolve('reduce', result, scope, context));
|
||||
async reduce(node, scope, context, inputProvider, resolveOthers): Promise<ResolvedResult> {
|
||||
let { result } = await accessor.compile(node, scope, context, inputProvider, resolveOthers);
|
||||
({ result } = await resolveOthers('reduce', result, scope, context, inputProvider));
|
||||
if (result.parseType === 'accessor') {
|
||||
return {
|
||||
result: constant.create({
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
import resolve, { toString, traverse, map, Context, ResolvedResult } from '/imports/parser/resolve';
|
||||
import constant from './constant';
|
||||
import NodeFactory, { ResolveLevel } from '/imports/parser/parseTree/NodeFactory';
|
||||
import { serialMap } from '/imports/api/utility/asyncMap';
|
||||
import constant from '/imports/parser/parseTree/constant';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import ResolveFunction from '/imports/parser/types/ResolveFunction';
|
||||
import MapFunction from '/imports/parser/types/MapFunction';
|
||||
import TraverseFunction from '/imports/parser/types/TraverseFunction';
|
||||
import ToStringFunction from '/imports/parser/types/ToStringFunction';
|
||||
|
||||
export type ArrayNode = {
|
||||
parseType: 'array';
|
||||
values: ParseNode[];
|
||||
}
|
||||
|
||||
interface ArrayFactory extends NodeFactory {
|
||||
type ArrayFactory = {
|
||||
create(node: Partial<ArrayNode>): ArrayNode;
|
||||
fromConstantArray(array: (string | number | boolean | undefined)[]): ArrayNode;
|
||||
compile?: undefined;
|
||||
roll?: undefined;
|
||||
reduce?: undefined;
|
||||
resolve(
|
||||
fn: ResolveLevel, node: ArrayNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
toString(node: ArrayNode): string;
|
||||
traverse(node: ArrayNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
map(node: ArrayNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
resolve: ResolveFunction<ArrayNode>;
|
||||
toString: ToStringFunction<ArrayNode>;
|
||||
traverse: TraverseFunction<ArrayNode>;
|
||||
map: MapFunction<ArrayNode>;
|
||||
}
|
||||
|
||||
const array: ArrayFactory = {
|
||||
const arrayFactory: ArrayFactory = {
|
||||
create({ values }: { values: ParseNode[] }) {
|
||||
return {
|
||||
parseType: 'array',
|
||||
@@ -44,32 +42,33 @@ const array: ArrayFactory = {
|
||||
return constant.create({ value: undefined });
|
||||
}
|
||||
});
|
||||
return array.create({ values });
|
||||
return arrayFactory.create({ values });
|
||||
},
|
||||
resolve(fn, node, scope, context): ResolvedResult {
|
||||
const values = node.values.map(node => {
|
||||
const { result } = resolve(fn, node, scope, context);
|
||||
return result;
|
||||
});
|
||||
async resolve(fn, node, scope, context, inputProvider, resolveOthers) {
|
||||
const values: ParseNode[] = [];
|
||||
for (const val of node.values) {
|
||||
const { result } = await resolveOthers(fn, val, scope, context, inputProvider);
|
||||
values.push(result);
|
||||
}
|
||||
return {
|
||||
result: array.create({ values }),
|
||||
result: arrayFactory.create({ values }),
|
||||
context,
|
||||
};
|
||||
},
|
||||
toString(node) {
|
||||
return `[${node.values.map(value => toString(value)).join(', ')}]`;
|
||||
toString(node, toStringOthers) {
|
||||
return `[${node.values.map(value => toStringOthers(value)).join(', ')}]`;
|
||||
},
|
||||
traverse(node, fn) {
|
||||
traverse(node, fn, traverseOthers) {
|
||||
fn(node);
|
||||
node.values.forEach(value => traverse(value, fn));
|
||||
node.values.forEach(value => traverseOthers(value, fn));
|
||||
},
|
||||
map(node, fn) {
|
||||
const resultingNode = fn(node);
|
||||
async map(node, fn, mapOthers) {
|
||||
const resultingNode = await fn(node);
|
||||
if (resultingNode === node) {
|
||||
node.values = node.values.map(value => map(value, fn));
|
||||
node.values = await serialMap(node.values, async value => await mapOthers(value, fn));
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
}
|
||||
|
||||
export default array;
|
||||
export default arrayFactory;
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import error from './error';
|
||||
import constant from './constant';
|
||||
console.log('call.ts imports')
|
||||
import error from '/imports/parser/parseTree/error';
|
||||
import constant from '/imports/parser/parseTree/constant';
|
||||
import functions, { ParserFunction } from '/imports/parser/functions';
|
||||
import resolve, { toString, traverse, map, Context, ResolvedResult } from '/imports/parser/resolve';
|
||||
import Context from '../types/Context';
|
||||
import ResolvedResult from '../types/ResolvedResult';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import NodeFactory, { ResolveLevel } from '/imports/parser/parseTree/NodeFactory';
|
||||
import { serialMap } from '/imports/api/utility/asyncMap';
|
||||
import ResolveFunction from '/imports/parser/types/ResolveFunction';
|
||||
import ResolveLevel from '/imports/parser/types/ResolveLevel';
|
||||
import TraverseFunction from '/imports/parser/types/TraverseFunction';
|
||||
import MapFunction from '/imports/parser/types/MapFunction';
|
||||
import ToStringFunction from '/imports/parser/types/ToStringFunction';
|
||||
|
||||
export type CallNode = {
|
||||
parseType: 'call';
|
||||
@@ -11,14 +18,12 @@ export type CallNode = {
|
||||
args: ParseNode[];
|
||||
}
|
||||
|
||||
interface CallFactory extends NodeFactory {
|
||||
type CallFactory = {
|
||||
create(node: Partial<CallNode>): CallNode;
|
||||
resolve(
|
||||
fn: ResolveLevel, node: CallNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
toString(node: CallNode): string;
|
||||
traverse(node: CallNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
map(node: CallNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
resolve: ResolveFunction<CallNode>;
|
||||
toString: ToStringFunction<CallNode>;
|
||||
traverse: TraverseFunction<CallNode>;
|
||||
map: MapFunction<CallNode>;
|
||||
checkArguments(node: CallNode, fn: ResolveLevel, func: ParserFunction,
|
||||
resolvedArgs: ParseNode[], context: Context): boolean;
|
||||
}
|
||||
@@ -31,7 +36,7 @@ const call: CallFactory = {
|
||||
args,
|
||||
}
|
||||
},
|
||||
resolve(fn, node, scope, context): ResolvedResult {
|
||||
async resolve(fn, node, scope, context, inputProvider, resolveOthers): Promise<ResolvedResult> {
|
||||
const func = functions[node.functionName];
|
||||
// Check that the function exists
|
||||
if (!func) {
|
||||
@@ -46,7 +51,7 @@ const call: CallFactory = {
|
||||
}
|
||||
|
||||
// Resolve a given node to a maximum depth of resolution
|
||||
const resolveToLevel = (node, maxResolveFn = 'reduce'): ResolvedResult => {
|
||||
const resolveToLevel = (node, maxResolveFn = 'reduce'): Promise<ResolvedResult> => {
|
||||
// Determine the actual depth to resolve to
|
||||
let resolveFn: ResolveLevel = 'reduce';
|
||||
if (fn === 'compile' || maxResolveFn === 'compile') {
|
||||
@@ -55,14 +60,15 @@ const call: CallFactory = {
|
||||
resolveFn = 'roll';
|
||||
}
|
||||
// Resolve
|
||||
return resolve(resolveFn, node, scope, context);
|
||||
return resolveOthers(resolveFn, node, scope, context, inputProvider);
|
||||
}
|
||||
|
||||
// Resolve the arguments
|
||||
const resolvedArgs = node.args.map((arg, i) => {
|
||||
const { result } = resolveToLevel(arg, func.maxResolveLevels?.[i]);
|
||||
return result;
|
||||
});
|
||||
const resolvedArgs: ParseNode[] = [];
|
||||
for (const [i, arg] of node.args.entries()) {
|
||||
const { result } = await resolveToLevel(arg, func.maxResolveLevels?.[i]);
|
||||
resolvedArgs.push(result);
|
||||
}
|
||||
|
||||
// Check that the arguments match what is expected
|
||||
const checkFailed = call.checkArguments(node, fn, func, resolvedArgs, context);
|
||||
@@ -116,7 +122,7 @@ const call: CallFactory = {
|
||||
};
|
||||
} else {
|
||||
// Resolve the return value
|
||||
return resolve(fn, value, scope, context);
|
||||
return resolveOthers(fn, value, scope, context, inputProvider);
|
||||
}
|
||||
} catch (err) {
|
||||
context.error(`Internal error: ${err.message || err}`);
|
||||
@@ -129,22 +135,22 @@ const call: CallFactory = {
|
||||
}
|
||||
}
|
||||
},
|
||||
toString(node) {
|
||||
return `${node.functionName}(${node.args.map(arg => toString(arg)).join(', ')})`;
|
||||
toString(node, toStringOthers) {
|
||||
return `${node.functionName}(${node.args.map(arg => toStringOthers(arg)).join(', ')})`;
|
||||
},
|
||||
traverse(node, fn) {
|
||||
traverse(node, fn, traverseOthers) {
|
||||
fn(node);
|
||||
node.args.forEach(arg => traverse(arg, fn));
|
||||
node.args.forEach(arg => traverseOthers(arg, fn));
|
||||
},
|
||||
map(node, fn) {
|
||||
const resultingNode = fn(node);
|
||||
async map(node, fn, mapOthers) {
|
||||
const resultingNode = await fn(node);
|
||||
if (resultingNode === node) {
|
||||
node.args = node.args.map(arg => map(arg, fn));
|
||||
node.args = await serialMap(node.args, async arg => mapOthers(arg, fn));
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
checkArguments(node, fn, func, resolvedArgs, context) {
|
||||
const argumentsExpected = func.arguments;
|
||||
checkArguments(callNode, fn, func, resolvedArgs, context) {
|
||||
const argumentsExpected = func.arguments as any;
|
||||
// Check that the number of arguments matches the number expected
|
||||
if (
|
||||
!argumentsExpected.anyLength &&
|
||||
@@ -152,7 +158,7 @@ const call: CallFactory = {
|
||||
resolvedArgs.length < (func.minArguments ?? argumentsExpected.length)
|
||||
) {
|
||||
context.error('Incorrect number of arguments ' +
|
||||
`to ${node.functionName} function, ` +
|
||||
`to ${callNode.functionName} function, ` +
|
||||
`expected ${argumentsExpected.length} got ${resolvedArgs.length}`);
|
||||
return true;
|
||||
}
|
||||
@@ -160,18 +166,22 @@ const call: CallFactory = {
|
||||
let failed = false;
|
||||
// Check that each argument is of the correct type
|
||||
resolvedArgs.forEach((node, index) => {
|
||||
let type: string;
|
||||
let type;
|
||||
if (argumentsExpected.anyLength) {
|
||||
type = argumentsExpected[0];
|
||||
} else {
|
||||
type = argumentsExpected[index];
|
||||
}
|
||||
if (type === 'parseNode') return;
|
||||
if (node.parseType !== type && node.valueType !== type) failed = true;
|
||||
if (
|
||||
node.parseType !== type
|
||||
&& node.parseType === 'constant'
|
||||
&& node.valueType !== type
|
||||
) failed = true;
|
||||
if (failed && fn === 'reduce') {
|
||||
const typeName = typeof type === 'string' ? type : type.constructor.name;
|
||||
const nodeName = node.parseType;
|
||||
context.error(`Incorrect arguments to ${node.functionName} function` +
|
||||
context.error(`Incorrect arguments to ${callNode.functionName} function` +
|
||||
`expected ${typeName} got ${nodeName}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import NodeFactory from '/imports/parser/parseTree/NodeFactory';
|
||||
import { Context, ResolvedResult } from '/imports/parser/resolve';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import ResolveLevelFunction from '/imports/parser/types/ResolveLevelFunction';
|
||||
|
||||
export type ConstantValueType = number | string | boolean | undefined
|
||||
|
||||
@@ -10,17 +10,17 @@ export type ConstantNode = {
|
||||
valueType: 'number' | 'string' | 'boolean' | 'undefined';
|
||||
}
|
||||
|
||||
interface ConstantFactory extends NodeFactory {
|
||||
export type FiniteNumberConstantNode = {
|
||||
parseType: 'constant';
|
||||
value: number;
|
||||
// TODO replace all `constantNode.valueType` with `typeof constantNode.value`
|
||||
valueType: 'number';
|
||||
}
|
||||
|
||||
type ConstantFactory = {
|
||||
create({ value }: { value: ConstantValueType }): ConstantNode;
|
||||
compile(
|
||||
node: ConstantNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
roll?: undefined;
|
||||
reduce?: undefined;
|
||||
resolve?: undefined;
|
||||
compile: ResolveLevelFunction<ConstantNode>;
|
||||
toString(node: ConstantNode): string;
|
||||
traverse?: undefined;
|
||||
map?: undefined;
|
||||
}
|
||||
|
||||
const constant: ConstantFactory = {
|
||||
@@ -31,7 +31,7 @@ const constant: ConstantFactory = {
|
||||
value,
|
||||
}
|
||||
},
|
||||
compile(node, scope, context) {
|
||||
async compile(node, scope, context) {
|
||||
return { result: node, context };
|
||||
},
|
||||
toString(node) {
|
||||
@@ -39,4 +39,11 @@ const constant: ConstantFactory = {
|
||||
},
|
||||
}
|
||||
|
||||
export function isFiniteNode(node: ParseNode): node is FiniteNumberConstantNode {
|
||||
return node.parseType === 'constant'
|
||||
&& node.valueType === 'number'
|
||||
&& typeof node.value === 'number'
|
||||
&& isFinite(node.value);
|
||||
}
|
||||
|
||||
export default constant;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import NodeFactory from '/imports/parser/parseTree/NodeFactory';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import { Context, ResolvedResult } from '/imports/parser/resolve';
|
||||
import ResolveLevelFunction from '/imports/parser/types/ResolveLevelFunction';
|
||||
import ToStringFunction from '/imports/parser/types/ToStringFunction';
|
||||
|
||||
export type ErrorNode = {
|
||||
parseType: 'error';
|
||||
@@ -8,17 +8,10 @@ export type ErrorNode = {
|
||||
error: string;
|
||||
}
|
||||
|
||||
interface ErrorFactory extends NodeFactory {
|
||||
interface ErrorFactory {
|
||||
create(node: Partial<ErrorNode>): ErrorNode;
|
||||
compile(
|
||||
node: ErrorNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
roll?: undefined;
|
||||
reduce?: undefined;
|
||||
resolve?: undefined;
|
||||
toString(node: ErrorNode): string;
|
||||
traverse?: undefined;
|
||||
map?: undefined;
|
||||
compile: ResolveLevelFunction<ErrorNode>;
|
||||
toString: ToStringFunction<ErrorNode>;
|
||||
}
|
||||
|
||||
const error: ErrorFactory = {
|
||||
@@ -29,7 +22,7 @@ const error: ErrorFactory = {
|
||||
error,
|
||||
}
|
||||
},
|
||||
compile(node, scope, context) {
|
||||
async compile(node, scope, context) {
|
||||
return { result: node, context };
|
||||
},
|
||||
toString(node) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import resolve, { traverse, toString, map } from '../resolve';
|
||||
import NodeFactory, { ResolveLevel } from '/imports/parser/parseTree/NodeFactory';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import { Context, ResolvedResult } from '/imports/parser/resolve';
|
||||
import ResolvedResult from '../types/ResolvedResult';
|
||||
import TraverseFunction from '/imports/parser/types/TraverseFunction';
|
||||
import MapFunction from '/imports/parser/types/MapFunction';
|
||||
import ResolveFunction from '/imports/parser/types/ResolveFunction';
|
||||
import ToStringFunction from '/imports/parser/types/ToStringFunction';
|
||||
|
||||
export type IfNode = {
|
||||
parseType: 'if';
|
||||
@@ -10,17 +12,12 @@ export type IfNode = {
|
||||
alternative: ParseNode;
|
||||
}
|
||||
|
||||
interface IfFactory extends NodeFactory {
|
||||
type IfFactory = {
|
||||
create(node: Partial<IfNode>): IfNode;
|
||||
compile?: undefined;
|
||||
roll?: undefined;
|
||||
reduce?: undefined;
|
||||
resolve(
|
||||
fn: ResolveLevel, node: IfNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
toString(node: IfNode): string;
|
||||
traverse(node: IfNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
map(node: IfNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
resolve: ResolveFunction<IfNode>;
|
||||
toString: ToStringFunction<IfNode>;
|
||||
traverse: TraverseFunction<IfNode>;
|
||||
map: MapFunction<IfNode>;
|
||||
}
|
||||
|
||||
const ifNode: IfFactory = {
|
||||
@@ -35,18 +32,18 @@ const ifNode: IfFactory = {
|
||||
alternative,
|
||||
};
|
||||
},
|
||||
toString(node) {
|
||||
toString(node, stringOthers) {
|
||||
const { condition, consequent, alternative } = node;
|
||||
condition.parseType
|
||||
return `${toString(condition)} ? ${toString(consequent)} : ${toString(alternative)}`
|
||||
return `${stringOthers(condition)} ? ${stringOthers(consequent)} : ${stringOthers(alternative)}`
|
||||
},
|
||||
resolve(fn, node, scope, context): ResolvedResult {
|
||||
const { result: condition } = resolve(fn, node.condition, scope, context);
|
||||
async resolve(fn, node, scope, context, inputProvider, resolveOthers): Promise<ResolvedResult> {
|
||||
const { result: condition } = await resolveOthers(fn, node.condition, scope, context, inputProvider);
|
||||
if (condition.parseType === 'constant') {
|
||||
if (condition.value) {
|
||||
return resolve(fn, node.consequent, scope, context);
|
||||
return resolveOthers(fn, node.consequent, scope, context, inputProvider);
|
||||
} else {
|
||||
return resolve(fn, node.alternative, scope, context);
|
||||
return resolveOthers(fn, node.alternative, scope, context, inputProvider);
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
@@ -59,18 +56,18 @@ const ifNode: IfFactory = {
|
||||
};
|
||||
}
|
||||
},
|
||||
traverse(node, fn) {
|
||||
traverse(node, fn, traverseOthers) {
|
||||
fn(node);
|
||||
traverse(node.condition, fn);
|
||||
traverse(node.consequent, fn);
|
||||
traverse(node.alternative, fn);
|
||||
traverseOthers(node.condition, fn);
|
||||
traverseOthers(node.consequent, fn);
|
||||
traverseOthers(node.alternative, fn);
|
||||
},
|
||||
map(node, fn) {
|
||||
const resultingNode = fn(node);
|
||||
async map(node, fn, mapOthers) {
|
||||
const resultingNode = await fn(node);
|
||||
if (resultingNode === node) {
|
||||
node.condition = map(node.condition, fn);
|
||||
node.consequent = map(node.consequent, fn);
|
||||
node.alternative = map(node.alternative, fn);
|
||||
node.condition = await mapOthers(node.condition, fn);
|
||||
node.consequent = await mapOthers(node.consequent, fn);
|
||||
node.alternative = await mapOthers(node.alternative, fn);
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
|
||||
45
app/imports/parser/parseTree/index.ts
Normal file
45
app/imports/parser/parseTree/index.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
console.log('index.ts imports')
|
||||
|
||||
import accessor from '/imports/parser/parseTree/accessor';
|
||||
import array from '/imports/parser/parseTree/array';
|
||||
import call from '/imports/parser/parseTree/call';
|
||||
import constant from '/imports/parser/parseTree/constant';
|
||||
import error from '/imports/parser/parseTree/error';
|
||||
import ifNode from '/imports/parser/parseTree/if';
|
||||
import index from '/imports/parser/parseTree/indexNode';
|
||||
import not from '/imports/parser/parseTree/not';
|
||||
import operator from '/imports/parser/parseTree/operator';
|
||||
import parenthesis from '/imports/parser/parseTree/parenthesis';
|
||||
import roll from '/imports/parser/parseTree/roll';
|
||||
import rollArray from '/imports/parser/parseTree/rollArray';
|
||||
import unaryOperator from '/imports/parser/parseTree/unaryOperator';
|
||||
|
||||
console.log('index.ts')
|
||||
|
||||
const factories = {
|
||||
accessor,
|
||||
array,
|
||||
call,
|
||||
constant,
|
||||
error,
|
||||
if: ifNode,
|
||||
index,
|
||||
not,
|
||||
operator,
|
||||
parenthesis,
|
||||
roll,
|
||||
rollArray,
|
||||
// What used to be symbols are now just treated as accessors without a path
|
||||
symbol: accessor,
|
||||
unaryOperator,
|
||||
};
|
||||
|
||||
console.log('---------------------');
|
||||
console.log('---------------------');
|
||||
console.log('---------------------');
|
||||
console.log(factories.array);
|
||||
console.log('---------------------');
|
||||
console.log('---------------------');
|
||||
console.log('---------------------');
|
||||
|
||||
export default factories;
|
||||
@@ -1,7 +1,11 @@
|
||||
import resolve, { traverse, toString, map, ResolvedResult, Context } from '/imports/parser/resolve';
|
||||
import error from './error';
|
||||
import NodeFactory, { ResolveLevel } from '/imports/parser/parseTree/NodeFactory';
|
||||
import error from '/imports/parser/parseTree/error';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import toString from '/imports/parser/toString';
|
||||
import { isFiniteNode } from '/imports/parser/parseTree/constant';
|
||||
import ResolveFunction from '/imports/parser/types/ResolveFunction';
|
||||
import TraverseFunction from '/imports/parser/types/TraverseFunction';
|
||||
import MapFunction from '/imports/parser/types/MapFunction';
|
||||
import ToStringFunction from '/imports/parser/types/ToStringFunction';
|
||||
|
||||
export type IndexNode = {
|
||||
parseType: 'index';
|
||||
@@ -9,17 +13,12 @@ export type IndexNode = {
|
||||
index: ParseNode;
|
||||
}
|
||||
|
||||
interface IndexFactory extends NodeFactory {
|
||||
type IndexFactory = {
|
||||
create(node: Partial<IndexNode>): IndexNode;
|
||||
compile?: undefined;
|
||||
roll?: undefined;
|
||||
reduce?: undefined;
|
||||
resolve(
|
||||
fn: ResolveLevel, node: IndexNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
toString(node: IndexNode): string;
|
||||
traverse(node: IndexNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
map(node: IndexNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
resolve: ResolveFunction<IndexNode>;
|
||||
toString: ToStringFunction<IndexNode>;
|
||||
traverse: TraverseFunction<IndexNode>;
|
||||
map: MapFunction<IndexNode>;
|
||||
}
|
||||
|
||||
const indexNode: IndexFactory = {
|
||||
@@ -30,25 +29,22 @@ const indexNode: IndexFactory = {
|
||||
index,
|
||||
}
|
||||
},
|
||||
resolve(fn, node, scope, context) {
|
||||
const { result: index } = resolve(fn, node.index, scope, context);
|
||||
const { result: array } = resolve(fn, node.array, scope, context);
|
||||
async resolve(fn, node, scope, context, inputProvider, resolveOthers) {
|
||||
const { result: index } = await resolveOthers(fn, node.index, scope, context, inputProvider);
|
||||
const { result: array } = await resolveOthers(fn, node.array, scope, context, inputProvider);
|
||||
|
||||
if (
|
||||
index.valueType === 'number' &&
|
||||
isFiniteNode(index) &&
|
||||
Number.isInteger(index.value) &&
|
||||
array.parseType === 'array'
|
||||
) {
|
||||
if (index.value < 1 || index.value > array.values.length) {
|
||||
context.error({
|
||||
type: 'warning',
|
||||
message: `Index of ${index.value} is out of range for an array` +
|
||||
` of length ${array.values.length}`,
|
||||
});
|
||||
context.error(`Index of ${index.value} is out of range for an array` +
|
||||
` of length ${array.values.length}`);
|
||||
}
|
||||
const selection = array.values[index.value - 1];
|
||||
if (selection) {
|
||||
return resolve(fn, selection, scope, context);
|
||||
return resolveOthers(fn, selection, scope, context, inputProvider);
|
||||
}
|
||||
} else if (fn === 'reduce') {
|
||||
if (array.parseType !== 'array') {
|
||||
@@ -61,7 +57,7 @@ const indexNode: IndexFactory = {
|
||||
}),
|
||||
context,
|
||||
};
|
||||
} else if (!index.isInteger) {
|
||||
} else if (!isFiniteNode(index) || !Number.isInteger(index.value)) {
|
||||
const message = `${toString(array)} is not an integer index of the array`
|
||||
context.error(message);
|
||||
return {
|
||||
@@ -81,19 +77,19 @@ const indexNode: IndexFactory = {
|
||||
context,
|
||||
};
|
||||
},
|
||||
toString(node) {
|
||||
return `${toString(node.array)}[${toString(node.index)}]`;
|
||||
toString(node, stringOthers) {
|
||||
return `${stringOthers(node.array)}[${stringOthers(node.index)}]`;
|
||||
},
|
||||
traverse(node, fn: (node: ParseNode) => any) {
|
||||
traverse(node, fn, traverseOthers) {
|
||||
fn(node);
|
||||
traverse(node.array, fn);
|
||||
traverse(node.index, fn);
|
||||
traverseOthers(node.array, fn);
|
||||
traverseOthers(node.index, fn);
|
||||
},
|
||||
map(node, fn: (node: ParseNode) => any) {
|
||||
const resultingNode = fn(node);
|
||||
async map(node, fn, mapOthers) {
|
||||
const resultingNode = await fn(node);
|
||||
if (resultingNode === node) {
|
||||
node.array = map(node.array, fn);
|
||||
node.index = map(node.index, fn);
|
||||
node.array = await mapOthers(node.array, fn);
|
||||
node.index = await mapOthers(node.index, fn);
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
import resolve, { toString, traverse, map, Context, ResolvedResult } from '/imports/parser/resolve';
|
||||
import constant from './constant';
|
||||
import NodeFactory, { ResolveLevel } from '/imports/parser/parseTree/NodeFactory';
|
||||
import constant from '/imports/parser/parseTree/constant';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import ResolveFunction from '/imports/parser/types/ResolveFunction';
|
||||
import TraverseFunction from '/imports/parser/types/TraverseFunction';
|
||||
import MapFunction from '/imports/parser/types/MapFunction';
|
||||
import ToStringFunction from '/imports/parser/types/ToStringFunction';
|
||||
|
||||
export type NotNode = {
|
||||
parseType: 'not';
|
||||
right: ParseNode;
|
||||
}
|
||||
|
||||
interface NotFactory extends NodeFactory {
|
||||
type NotFactory = {
|
||||
create(node: Partial<NotNode>): NotNode;
|
||||
compile?: undefined;
|
||||
roll?: undefined;
|
||||
reduce?: undefined;
|
||||
resolve(
|
||||
fn: ResolveLevel, node: NotNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
toString(node: NotNode): string;
|
||||
traverse(node: NotNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
map(node: NotNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
resolve: ResolveFunction<NotNode>;
|
||||
toString: ToStringFunction<NotNode>;
|
||||
traverse: TraverseFunction<NotNode>;
|
||||
map: MapFunction<NotNode>;
|
||||
}
|
||||
|
||||
const not: NotFactory = {
|
||||
@@ -28,8 +25,8 @@ const not: NotFactory = {
|
||||
right,
|
||||
}
|
||||
},
|
||||
resolve(fn, node, scope, context) {
|
||||
const { result: right } = resolve(fn, node.right, scope, context);
|
||||
async resolve(fn, node, scope, context, inputProvider, resolveOthers) {
|
||||
const { result: right } = await resolveOthers(fn, node.right, scope, context, inputProvider);
|
||||
if (right.parseType !== 'constant') {
|
||||
return {
|
||||
result: not.create({
|
||||
@@ -45,17 +42,17 @@ const not: NotFactory = {
|
||||
context,
|
||||
};
|
||||
},
|
||||
toString(node) {
|
||||
return `!${toString(node.right)}`;
|
||||
toString(node, stringOthers) {
|
||||
return `!${stringOthers(node.right)}`;
|
||||
},
|
||||
traverse(node, fn) {
|
||||
traverse(node, fn, traverseOthers) {
|
||||
fn(node);
|
||||
traverse(node.right, fn);
|
||||
traverseOthers(node.right, fn);
|
||||
},
|
||||
map(node, fn) {
|
||||
const resultingNode = fn(node);
|
||||
async map(node, fn, mapOthers) {
|
||||
const resultingNode = await fn(node);
|
||||
if (resultingNode === node) {
|
||||
node.right = map(node.right, fn);
|
||||
node.right = await mapOthers(node.right, fn);
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import resolve, { toString, traverse, map, ResolvedResult, Context } from '/imports/parser/resolve';
|
||||
import constant from './constant';
|
||||
import constant, { ConstantValueType, isFiniteNode } from '/imports/parser/parseTree/constant';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import NodeFactory, { ResolveLevel } from '/imports/parser/parseTree/NodeFactory';
|
||||
import ResolveFunction from '/imports/parser/types/ResolveFunction';
|
||||
import TraverseFunction from '/imports/parser/types/TraverseFunction';
|
||||
import MapFunction from '/imports/parser/types/MapFunction';
|
||||
import ToStringFunction from '/imports/parser/types/ToStringFunction';
|
||||
|
||||
type OperatorSymbol = '*' | '/' | '^' | '+' | '-' | '%' | '&' | '&&' | '|' | '||' | '=' |
|
||||
'==' | '===' | '!=' | '!==' | '>' | '<' | '>=' | '<=';
|
||||
@@ -13,17 +15,12 @@ export type OperatorNode = {
|
||||
operator: OperatorSymbol;
|
||||
}
|
||||
|
||||
interface OperatorFactory extends NodeFactory {
|
||||
type OperatorFactory = {
|
||||
create(node: Partial<OperatorNode>): OperatorNode;
|
||||
compile?: undefined;
|
||||
roll?: undefined;
|
||||
reduce?: undefined;
|
||||
resolve(
|
||||
fn: ResolveLevel, node: OperatorNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
toString(node: OperatorNode): string;
|
||||
traverse(node: OperatorNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
map(node: OperatorNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
resolve: ResolveFunction<OperatorNode>;
|
||||
toString: ToStringFunction<OperatorNode>;
|
||||
traverse: TraverseFunction<OperatorNode>;
|
||||
map: MapFunction<OperatorNode>;
|
||||
}
|
||||
|
||||
// Which operators can be considered commutative by the parser
|
||||
@@ -43,10 +40,10 @@ const operator: OperatorFactory = {
|
||||
operator,
|
||||
};
|
||||
},
|
||||
resolve(fn, node, scope, context) {
|
||||
const { result: leftNode } = resolve(fn, node.left, scope, context);
|
||||
const { result: rightNode } = resolve(fn, node.right, scope, context);
|
||||
let left, right;
|
||||
async resolve(fn, node, scope, context, inputProvider, resolveOthers) {
|
||||
const { result: leftNode } = await resolveOthers(fn, node.left, scope, context, inputProvider);
|
||||
const { result: rightNode } = await resolveOthers(fn, node.right, scope, context, inputProvider);
|
||||
let left: ConstantValueType, right: ConstantValueType;
|
||||
|
||||
// If commutation is possible, do it and return that result
|
||||
const commutatedResult = reorderCommutativeOperations(node, leftNode, rightNode);
|
||||
@@ -73,32 +70,39 @@ const operator: OperatorFactory = {
|
||||
context,
|
||||
};
|
||||
},
|
||||
toString(node) {
|
||||
toString(node, stringOthers) {
|
||||
const { left, right, operator } = node;
|
||||
// special case of adding a negative number
|
||||
if (operator === '+' && right.valueType === 'number' && right.value < 0) {
|
||||
return `${toString(left)} - ${-right.value}`
|
||||
if (operator === '+' && isFiniteNode(right) && right.value < 0) {
|
||||
return `${stringOthers(left)} - ${-right.value}`
|
||||
}
|
||||
return `${toString(left)} ${operator} ${toString(right)}`;
|
||||
return `${stringOthers(left)} ${operator} ${stringOthers(right)}`;
|
||||
},
|
||||
traverse(node, fn) {
|
||||
traverse(node, fn, traverseOthers) {
|
||||
fn(node);
|
||||
traverse(node.left, fn);
|
||||
traverse(node.right, fn);
|
||||
traverseOthers(node.left, fn);
|
||||
traverseOthers(node.right, fn);
|
||||
},
|
||||
map(node, fn) {
|
||||
const resultingNode = fn(node);
|
||||
async map(node, fn, mapOthers) {
|
||||
const resultingNode = await fn(node);
|
||||
if (resultingNode === node) {
|
||||
node.left = map(node.left, fn);
|
||||
node.right = map(node.right, fn);
|
||||
node.left = await mapOthers(node.left, fn);
|
||||
node.right = await mapOthers(node.right, fn);
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
}
|
||||
|
||||
function applyOperator(operator: OperatorSymbol, left: ParseNode, right: ParseNode) {
|
||||
function applyOperator(operator: OperatorSymbol, left: ConstantValueType, right: ConstantValueType) {
|
||||
let result;
|
||||
if (left === undefined) {
|
||||
left = 0;
|
||||
}
|
||||
if (right === undefined) {
|
||||
right = 0;
|
||||
}
|
||||
switch (operator) {
|
||||
// Typescript might complain about these, but they return NaN as expected
|
||||
case '+': result = left + right; break;
|
||||
case '-': result = left - right; break;
|
||||
case '*': result = left * right; break;
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import NodeFactory, { ResolveLevel } from '/imports/parser/parseTree/NodeFactory';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import resolve, { toString, traverse, map, Context, ResolvedResult } from '/imports/parser/resolve';
|
||||
import ResolveFunction from '/imports/parser/types/ResolveFunction';
|
||||
import TraverseFunction from '/imports/parser/types/TraverseFunction';
|
||||
import MapFunction from '/imports/parser/types/MapFunction';
|
||||
import ToStringFunction from '/imports/parser/types/ToStringFunction';
|
||||
|
||||
export type ParenthesisNode = {
|
||||
parseType: 'parenthesis';
|
||||
content: ParseNode;
|
||||
}
|
||||
|
||||
interface ParenthesisFactory extends NodeFactory {
|
||||
type ParenthesisFactory = {
|
||||
create(node: Partial<ParenthesisNode>): ParenthesisNode;
|
||||
compile?: undefined;
|
||||
roll?: undefined;
|
||||
reduce?: undefined;
|
||||
resolve(
|
||||
fn: ResolveLevel, node: ParenthesisNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
toString(node: ParenthesisNode): string;
|
||||
traverse(node: ParenthesisNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
map(node: ParenthesisNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
resolve: ResolveFunction<ParenthesisNode>;
|
||||
toString: ToStringFunction<ParenthesisNode>;
|
||||
traverse: TraverseFunction<ParenthesisNode>;
|
||||
map: MapFunction<ParenthesisNode>;
|
||||
}
|
||||
|
||||
const parenthesis: ParenthesisFactory = {
|
||||
@@ -27,8 +24,8 @@ const parenthesis: ParenthesisFactory = {
|
||||
content,
|
||||
};
|
||||
},
|
||||
resolve(fn, node, scope, context) {
|
||||
const { result: content } = resolve(fn, node.content, scope, context);
|
||||
async resolve(fn, node, scope, context, inputProvider, resolveOthers) {
|
||||
const { result: content } = await resolveOthers(fn, node.content, scope, context, inputProvider);
|
||||
if (
|
||||
fn === 'reduce' ||
|
||||
content.parseType === 'constant' ||
|
||||
@@ -42,17 +39,17 @@ const parenthesis: ParenthesisFactory = {
|
||||
};
|
||||
}
|
||||
},
|
||||
toString(node) {
|
||||
return `(${toString(node.content)})`;
|
||||
toString(node, stringOthers) {
|
||||
return `(${stringOthers(node.content)})`;
|
||||
},
|
||||
traverse(node, fn: (node: ParseNode) => any) {
|
||||
traverse(node, fn, traverseOthers) {
|
||||
fn(node);
|
||||
traverse(node.content, fn);
|
||||
traverseOthers(node.content, fn);
|
||||
},
|
||||
map(node, fn: (node: ParseNode) => any) {
|
||||
const resultingNode = fn(node);
|
||||
async map(node, fn, mapOthers) {
|
||||
const resultingNode = await fn(node);
|
||||
if (resultingNode === node) {
|
||||
node.content = map(node.content, fn);
|
||||
node.content = await mapOthers(node.content, fn);
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import resolve, { toString, traverse, map, ResolvedResult, Context } from '../resolve';
|
||||
import error from './error';
|
||||
import rollArray from './rollArray';
|
||||
import rollDice from '/imports/parser/rollDice';
|
||||
import error from '/imports/parser/parseTree/error';
|
||||
import rollArray from '/imports/parser/parseTree/rollArray';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import NodeFactory from '/imports/parser/parseTree/NodeFactory';
|
||||
import ResolveLevelFunction from '/imports/parser/types/ResolveLevelFunction';
|
||||
import TraverseFunction from '/imports/parser/types/TraverseFunction';
|
||||
import MapFunction from '/imports/parser/types/MapFunction';
|
||||
import ToStringFunction from '/imports/parser/types/ToStringFunction';
|
||||
import Context from '/imports/parser/types/Context';
|
||||
|
||||
export type RollNode = {
|
||||
parseType: 'roll';
|
||||
@@ -12,21 +14,14 @@ export type RollNode = {
|
||||
right: ParseNode;
|
||||
}
|
||||
|
||||
interface RollNodeFactory extends NodeFactory {
|
||||
type RollNodeFactory = {
|
||||
create(node: Partial<RollNode>): RollNode;
|
||||
compile(
|
||||
node: RollNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
roll(
|
||||
node: RollNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
reduce(
|
||||
node: RollNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
resolve?: undefined;
|
||||
toString(node: RollNode): string;
|
||||
traverse(node: RollNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
map(node: RollNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
compile: ResolveLevelFunction<RollNode>;
|
||||
roll: ResolveLevelFunction<RollNode>;
|
||||
reduce: ResolveLevelFunction<RollNode>;
|
||||
toString: ToStringFunction<RollNode>;
|
||||
traverse: TraverseFunction<RollNode>;
|
||||
map: MapFunction<RollNode>;
|
||||
}
|
||||
|
||||
const rollNode: RollNodeFactory = {
|
||||
@@ -37,28 +32,28 @@ const rollNode: RollNodeFactory = {
|
||||
right,
|
||||
};
|
||||
},
|
||||
compile(node, scope, context) {
|
||||
const { result: left } = resolve('compile', node.left, scope, context);
|
||||
const { result: right } = resolve('compile', node.right, scope, context);
|
||||
async compile(node, scope, context, inputProvider, resolveOthers) {
|
||||
const { result: left } = await resolveOthers('compile', node.left, scope, context, inputProvider);
|
||||
const { result: right } = await resolveOthers('compile', node.right, scope, context, inputProvider);
|
||||
return {
|
||||
result: rollNode.create({ left, right }),
|
||||
context,
|
||||
};
|
||||
},
|
||||
toString(node) {
|
||||
toString(node, stringOthers) {
|
||||
if (
|
||||
node.left.parseType === 'constant'
|
||||
&& typeof node.left.value === 'number'
|
||||
&& node.left.value === 1
|
||||
) {
|
||||
return `d${toString(node.right)}`;
|
||||
return `d${stringOthers(node.right)}`;
|
||||
} else {
|
||||
return `${toString(node.left)}d${toString(node.right)}`;
|
||||
return `${stringOthers(node.left)}d${stringOthers(node.right)}`;
|
||||
}
|
||||
},
|
||||
roll(node, scope, context) {
|
||||
const { result: left } = resolve('reduce', node.left, scope, context);
|
||||
const { result: right } = resolve('reduce', node.right, scope, context);
|
||||
async roll(node, scope, context, inputProvider, resolveOthers) {
|
||||
const { result: left } = await resolveOthers('reduce', node.left, scope, context, inputProvider);
|
||||
const { result: right } = await resolveOthers('reduce', node.right, scope, context, inputProvider);
|
||||
if (
|
||||
left.parseType !== 'constant'
|
||||
|| typeof left.value !== 'number'
|
||||
@@ -82,7 +77,7 @@ const rollNode: RollNodeFactory = {
|
||||
return errorResult(message, node, context);
|
||||
}
|
||||
const diceSize = right.value;
|
||||
const values = rollDice(number, diceSize);
|
||||
const [values] = await inputProvider.rollDice([{ number, diceSize }]);
|
||||
if (context) {
|
||||
context.rolls.push({ number, diceSize, values });
|
||||
}
|
||||
@@ -95,20 +90,20 @@ const rollNode: RollNodeFactory = {
|
||||
context
|
||||
};
|
||||
},
|
||||
reduce(node, scope, context) {
|
||||
const { result } = rollNode.roll(node, scope, context);
|
||||
return resolve('reduce', result, scope, context);
|
||||
async reduce(node, scope, context, inputProvider, resolveOthers) {
|
||||
const { result } = await rollNode.roll(node, scope, context, inputProvider, resolveOthers);
|
||||
return resolveOthers('reduce', result, scope, context, inputProvider);
|
||||
},
|
||||
traverse(node, fn) {
|
||||
traverse(node, fn, traverseOthers) {
|
||||
fn(node);
|
||||
traverse(node.left, fn);
|
||||
traverse(node.right, fn);
|
||||
traverseOthers(node.left, fn);
|
||||
traverseOthers(node.right, fn);
|
||||
},
|
||||
map(node, fn) {
|
||||
const resultingNode = fn(node);
|
||||
async map(node, fn, mapOthers) {
|
||||
const resultingNode = await fn(node);
|
||||
if (resultingNode === node) {
|
||||
node.left = map(node.left, fn);
|
||||
node.right = map(node.right, fn);
|
||||
node.left = await mapOthers(node.left, fn);
|
||||
node.right = await mapOthers(node.right, fn);
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import constant from './constant';
|
||||
import NodeFactory from '/imports/parser/parseTree/NodeFactory';
|
||||
import { Context, ResolvedResult } from '/imports/parser/resolve';
|
||||
import constant from '/imports/parser/parseTree/constant';
|
||||
import ResolveLevelFunction from '/imports/parser/types/ResolveLevelFunction';
|
||||
import ToStringFunction from '/imports/parser/types/ToStringFunction';
|
||||
|
||||
type RollValue = {
|
||||
value: number,
|
||||
@@ -17,19 +17,11 @@ export type RollArrayNode = {
|
||||
diceNum: number,
|
||||
}
|
||||
|
||||
interface RollArrayFactory extends NodeFactory {
|
||||
type RollArrayFactory = {
|
||||
create(input: { values: number[], diceSize: number, diceNum: number }): RollArrayNode;
|
||||
compile(
|
||||
node: RollArrayNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
roll?: undefined;
|
||||
reduce(
|
||||
node: RollArrayNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
resolve?: undefined;
|
||||
toString(node: RollArrayNode): string;
|
||||
traverse?: undefined;
|
||||
map?: undefined;
|
||||
compile: ResolveLevelFunction<RollArrayNode>;
|
||||
reduce: ResolveLevelFunction<RollArrayNode>;
|
||||
toString: ToStringFunction<RollArrayNode>;
|
||||
}
|
||||
|
||||
const rollArray: RollArrayFactory = {
|
||||
@@ -43,7 +35,7 @@ const rollArray: RollArrayFactory = {
|
||||
diceNum,
|
||||
};
|
||||
},
|
||||
compile(node, scope, context) {
|
||||
async compile(node, scope, context) {
|
||||
return {
|
||||
result: node,
|
||||
context
|
||||
@@ -52,7 +44,7 @@ const rollArray: RollArrayFactory = {
|
||||
toString(node) {
|
||||
return `${node.diceNum || ''}d${node.diceSize} [${valuesToString(node.values)}]`;
|
||||
},
|
||||
reduce(node, scope, context) {
|
||||
async reduce(node, scope, context) {
|
||||
const total = node.values.reduce((a, b) => {
|
||||
if (b.disabled) return a;
|
||||
return a + b.value;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import resolve, { toString, traverse, map, Context, ResolvedResult } from '/imports/parser/resolve';
|
||||
import constant from './constant';
|
||||
import NodeFactory, { ResolveLevel } from '/imports/parser/parseTree/NodeFactory';
|
||||
import constant from '/imports/parser/parseTree/constant';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import ResolveFunction from '/imports/parser/types/ResolveFunction';
|
||||
import TraverseFunction from '/imports/parser/types/TraverseFunction';
|
||||
import MapFunction from '/imports/parser/types/MapFunction';
|
||||
import ToStringFunction from '/imports/parser/types/ToStringFunction';
|
||||
|
||||
type UnaryOperatorSymbol = '+' | '-';
|
||||
|
||||
@@ -11,17 +13,12 @@ export type UnaryOperatorNode = {
|
||||
right: ParseNode;
|
||||
}
|
||||
|
||||
interface UnaryOperatorFactory extends NodeFactory {
|
||||
type UnaryOperatorFactory = {
|
||||
create(node: Partial<UnaryOperatorNode>): UnaryOperatorNode;
|
||||
compile?: undefined;
|
||||
roll?: undefined;
|
||||
reduce?: undefined;
|
||||
resolve(
|
||||
fn: ResolveLevel, node: UnaryOperatorNode, scope: Record<string, any>, context: Context
|
||||
): ResolvedResult;
|
||||
toString(node: UnaryOperatorNode): string;
|
||||
traverse(node: UnaryOperatorNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
map(node: UnaryOperatorNode, fn: (node: ParseNode) => any): ReturnType<typeof fn>;
|
||||
resolve: ResolveFunction<UnaryOperatorNode>;
|
||||
toString: ToStringFunction<UnaryOperatorNode>;
|
||||
traverse: TraverseFunction<UnaryOperatorNode>;
|
||||
map: MapFunction<UnaryOperatorNode>;
|
||||
}
|
||||
|
||||
const unaryOperator: UnaryOperatorFactory = {
|
||||
@@ -32,8 +29,8 @@ const unaryOperator: UnaryOperatorFactory = {
|
||||
right,
|
||||
};
|
||||
},
|
||||
resolve(fn, node, scope, context) {
|
||||
const { result: rightNode } = resolve(fn, node.right, scope, context);
|
||||
async resolve(fn, node, scope, context, inputProvider, resolveOthers) {
|
||||
const { result: rightNode } = await resolveOthers(fn, node.right, scope, context, inputProvider);
|
||||
if (
|
||||
rightNode.parseType !== 'constant'
|
||||
|| typeof rightNode.value !== 'number'
|
||||
@@ -59,17 +56,17 @@ const unaryOperator: UnaryOperatorFactory = {
|
||||
context,
|
||||
};
|
||||
},
|
||||
toString(node) {
|
||||
return `${node.operator}${toString(node.right)}`;
|
||||
toString(node, stringOthers) {
|
||||
return `${node.operator}${stringOthers(node.right)}`;
|
||||
},
|
||||
traverse(node, fn) {
|
||||
traverse(node, fn, traverseOthers) {
|
||||
fn(node);
|
||||
traverse(node.right, fn);
|
||||
traverseOthers(node.right, fn);
|
||||
},
|
||||
map(node, fn) {
|
||||
const resultingNode = fn(node);
|
||||
async map(node, fn, mapOthers) {
|
||||
const resultingNode = await fn(node);
|
||||
if (resultingNode === node) {
|
||||
node.right = map(node.right, fn);
|
||||
node.right = await mapOthers(node.right, fn);
|
||||
}
|
||||
return resultingNode;
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { parse } from './parser';
|
||||
import resolve, { toString } from './resolve';
|
||||
import { parse } from '/imports/parser/parser';
|
||||
import resolve from '/imports/parser/resolve';
|
||||
import toString from './toString';
|
||||
import { assert } from 'chai';
|
||||
|
||||
describe('Parser', function () {
|
||||
@@ -7,19 +8,19 @@ describe('Parser', function () {
|
||||
assert.typeOf(parse('1'), 'object');
|
||||
});
|
||||
it('parses various operations', function () {
|
||||
assert.typeOf(parse('1 + 2 * 3 / 4 * 1d8'), 'object');
|
||||
assert.typeOf(parse('[1,2,3][2] + 1 + 2 * 3 / 4 * 1d8'), 'object');
|
||||
});
|
||||
it('simplifies basic addition and multiplication', function () {
|
||||
it('simplifies basic addition and multiplication', async function () {
|
||||
let add = parse('1 + 3 + 3 + 4');
|
||||
({ result: add } = resolve('compile', add));
|
||||
({ result: add } = await resolve('compile', add));
|
||||
assert.equal(toString(add), '11');
|
||||
|
||||
let mul = parse('2 * 3 * 4');
|
||||
({ result: mul } = resolve('compile', mul));
|
||||
({ result: mul } = await resolve('compile', mul));
|
||||
assert.equal(toString(mul), '24');
|
||||
});
|
||||
it('simplifies addition when possible, even if a roll is in the way', function () {
|
||||
let { result } = resolve('compile', parse('1 + 3 + d12 + 3 + 4'));
|
||||
it('simplifies addition when possible, even if a roll is in the way', async function () {
|
||||
let { result } = await resolve('compile', parse('1 + 3 + d12 + 3 + 4'));
|
||||
assert.equal(toString(result), 'd12 + 11');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,105 +1,57 @@
|
||||
import nodeTypeIndex from './parseTree/_index';
|
||||
import '/imports/parser/parseTree/array';
|
||||
import factories from '/imports/parser/parseTree';
|
||||
import InputProvider from '/imports/api/engine/action/functions/InputProvider';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import { ConstantValueType } from '/imports/parser/parseTree/constant';
|
||||
import rollDice from '/imports/parser/rollDice';
|
||||
import ResolveLevel from './types/ResolveLevel';
|
||||
import ResolvedResult from './types/ResolvedResult';
|
||||
import Context from './types/Context';
|
||||
import ResolveLevelFunction from '/imports/parser/types/ResolveLevelFunction';
|
||||
|
||||
// Takes a parse node and computes it to a set detail level
|
||||
// returns {result, context}
|
||||
export default function resolve(
|
||||
fn: 'roll' | 'reduce' | 'compile',
|
||||
export default async function resolve(
|
||||
fn: ResolveLevel,
|
||||
node: ParseNode,
|
||||
scope: Record<string, any>,
|
||||
context = new Context()
|
||||
): ResolvedResult {
|
||||
if (!node) throw 'Node must be supplied';
|
||||
const factory = nodeTypeIndex[node.parseType];
|
||||
const handlerFunction = factory[fn];
|
||||
scope: Record<string, any> = {},
|
||||
context = new Context(),
|
||||
inputProvider = computationInputProvider,
|
||||
): Promise<ResolvedResult> {
|
||||
if (!node) throw new Error('Node must be supplied');
|
||||
const factory = factories[node.parseType];
|
||||
const handlerFunction: ResolveLevelFunction<ParseNode> = factory[fn];
|
||||
if (!factory) {
|
||||
throw new Meteor.Error(`Parse node type: ${node.parseType} not implemented`);
|
||||
}
|
||||
if (factory.resolve) {
|
||||
return factory.resolve(fn, node, scope, context);
|
||||
} else if (handlerFunction) {
|
||||
return handlerFunction(node, scope, context);
|
||||
} else if (fn === 'reduce' && factory.roll) {
|
||||
return factory.roll(node, scope, context)
|
||||
if ('resolve' in factory) {
|
||||
return factory.resolve(fn, node as any, scope, context, inputProvider, resolve);
|
||||
} else if (fn in factory) {
|
||||
return handlerFunction(node, scope, context, inputProvider, resolve);
|
||||
} else if (fn === 'reduce' && 'roll' in factory) {
|
||||
return factory.roll(node as any, scope, context, inputProvider, resolve)
|
||||
} else if (factory.compile) {
|
||||
return factory.compile(node, scope, context)
|
||||
return factory.compile(node as any, scope, context, inputProvider, resolve)
|
||||
} else {
|
||||
throw new Meteor.Error('Compile not implemented on ' + node.parseType);
|
||||
}
|
||||
}
|
||||
|
||||
export function toString(node: ParseNode) {
|
||||
if (!node) return '';
|
||||
const type = nodeTypeIndex[node.parseType];
|
||||
if (!type?.toString) {
|
||||
throw new Meteor.Error('toString not implemented on ' + node.parseType);
|
||||
}
|
||||
return type.toString(node);
|
||||
}
|
||||
|
||||
export function toPrimitiveOrString(node: ParseNode): ConstantValueType {
|
||||
if (!node) return '';
|
||||
if (node.parseType === 'constant') return node.value;
|
||||
if (node.parseType === 'error') return undefined;
|
||||
return toString(node);
|
||||
}
|
||||
|
||||
export function traverse(node: ParseNode, fn: (ParseNode) => any): ReturnType<typeof fn> {
|
||||
if (!node) return;
|
||||
const type = nodeTypeIndex[node.parseType];
|
||||
if (!type) {
|
||||
console.error(node);
|
||||
throw new Meteor.Error('Not valid parse node');
|
||||
}
|
||||
if (type.traverse) {
|
||||
return type.traverse(node, fn);
|
||||
}
|
||||
return fn(node);
|
||||
}
|
||||
|
||||
export function map(node: ParseNode, fn: (ParseNode) => any): ReturnType<typeof fn> {
|
||||
if (!node) return;
|
||||
const type = nodeTypeIndex[node.parseType];
|
||||
if (!type) {
|
||||
console.error(node);
|
||||
throw new Meteor.Error('Not valid parse node');
|
||||
}
|
||||
if (type.map) {
|
||||
return type.map(node, fn);
|
||||
}
|
||||
return fn(node);
|
||||
}
|
||||
|
||||
export type ResolvedResult = {
|
||||
result: ParseNode,
|
||||
context: Context
|
||||
}
|
||||
|
||||
export class Context {
|
||||
errors: (Error | { type: string, message: string })[];
|
||||
rolls: { number: number, diceSize: number, values: number[] }[];
|
||||
options: { [key: string]: any };
|
||||
|
||||
constructor({ errors = [], rolls = [], options = {} } = {}) {
|
||||
this.errors = errors;
|
||||
this.rolls = rolls;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
error(e: Error | string) {
|
||||
if (!e) return;
|
||||
if (typeof e === 'string') {
|
||||
this.errors.push({
|
||||
type: 'error',
|
||||
message: e,
|
||||
});
|
||||
} else {
|
||||
this.errors.push(e);
|
||||
const computationInputProvider: InputProvider = {
|
||||
/**
|
||||
* By default, just roll the dice as usual
|
||||
*/
|
||||
async rollDice(dice) {
|
||||
return dice.map(d => rollDice(d.number, d.diceSize));
|
||||
},
|
||||
/**
|
||||
* By default just choose the minimum number of options from the front of the list
|
||||
*/
|
||||
async choose(choices, quantity = [1, 1]) {
|
||||
const chosen: string[] = [];
|
||||
const choiceQuantity = quantity[0] <= 0 ? 1 : quantity[0];
|
||||
for (let i = 0; i < choiceQuantity && i < choices.length; i += 1) {
|
||||
chosen.push(choices[i]._id);
|
||||
}
|
||||
}
|
||||
|
||||
roll(r) {
|
||||
this.rolls.push(r);
|
||||
return chosen;
|
||||
}
|
||||
}
|
||||
|
||||
11
app/imports/parser/toPrimitiveOrString.ts
Normal file
11
app/imports/parser/toPrimitiveOrString.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import { ConstantValueType } from '/imports/parser/parseTree/constant';
|
||||
import toString from './toString';
|
||||
|
||||
|
||||
export default function toPrimitiveOrString(node: ParseNode): ConstantValueType {
|
||||
if (!node) return '';
|
||||
if (node.parseType === 'constant') return node.value;
|
||||
if (node.parseType === 'error') return undefined;
|
||||
return toString(node);
|
||||
}
|
||||
7
app/imports/parser/toString.ts
Normal file
7
app/imports/parser/toString.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import factories from './parseTree';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
|
||||
export default function toString(node: ParseNode) {
|
||||
if (!node) return '';
|
||||
return factories[node.parseType].toString(node as any, toString);
|
||||
}
|
||||
12
app/imports/parser/traverse.ts
Normal file
12
app/imports/parser/traverse.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/* eslint-disable no-fallthrough */
|
||||
import factories from './parseTree';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
|
||||
export default function traverse(node: ParseNode, fn: (ParseNode) => any): ReturnType<typeof fn> {
|
||||
if (!node) return;
|
||||
const factory = factories[node.parseType];
|
||||
if ('traverse' in factory) {
|
||||
return factory.traverse(node as any, fn, traverse);
|
||||
}
|
||||
return fn(node);
|
||||
}
|
||||
28
app/imports/parser/types/Context.ts
Normal file
28
app/imports/parser/types/Context.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
export default class Context {
|
||||
errors: (Error | { type: string; message: string; })[];
|
||||
rolls: { number: number; diceSize: number; values: number[]; }[];
|
||||
options: { [key: string]: any; };
|
||||
|
||||
constructor({ errors = [], rolls = [], options = {} } = {}) {
|
||||
this.errors = errors;
|
||||
this.rolls = rolls;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
error(e: Error | string) {
|
||||
if (!e) return;
|
||||
if (typeof e === 'string') {
|
||||
this.errors.push({
|
||||
type: 'error',
|
||||
message: e,
|
||||
});
|
||||
} else {
|
||||
this.errors.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
roll(r) {
|
||||
this.rolls.push(r);
|
||||
}
|
||||
}
|
||||
11
app/imports/parser/types/MapFunction.ts
Normal file
11
app/imports/parser/types/MapFunction.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
|
||||
type MapFunction<T extends ParseNode> = {
|
||||
(node: T, fn: (node: ParseNode) => Promise<ParseNode>, mapOthers: MapOthersFunction): Promise<ParseNode>;
|
||||
}
|
||||
|
||||
export default MapFunction;
|
||||
|
||||
type MapOthersFunction = {
|
||||
(node: ParseNode, fn: (node: ParseNode) => Promise<ParseNode>): Promise<ParseNode>
|
||||
}
|
||||
18
app/imports/parser/types/ResolveFunction.ts
Normal file
18
app/imports/parser/types/ResolveFunction.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import InputProvider from '/imports/api/engine/action/functions/InputProvider';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import Context from './Context';
|
||||
import ResolvedResult from './ResolvedResult';
|
||||
import ResolveLevel from './ResolveLevel';
|
||||
import ResolveOthersFunction from './ResolveOthersFunction';
|
||||
|
||||
type ResolveFunction<T extends ParseNode> = (
|
||||
fn: ResolveLevel,
|
||||
node: T,
|
||||
scope: Record<string, any>,
|
||||
context: Context,
|
||||
input: InputProvider,
|
||||
resolveOthers: ResolveOthersFunction,
|
||||
) => Promise<ResolvedResult>;
|
||||
|
||||
export default ResolveFunction;
|
||||
|
||||
3
app/imports/parser/types/ResolveLevel.ts
Normal file
3
app/imports/parser/types/ResolveLevel.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
type ResolveLevel = 'compile' | 'roll' | 'reduce';
|
||||
|
||||
export default ResolveLevel;
|
||||
15
app/imports/parser/types/ResolveLevelFunction.ts
Normal file
15
app/imports/parser/types/ResolveLevelFunction.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import InputProvider from '/imports/api/engine/action/functions/InputProvider';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import Context from './Context';
|
||||
import ResolvedResult from './ResolvedResult';
|
||||
import ResolveOthersFunction from './ResolveOthersFunction';
|
||||
|
||||
type ResolveLevelFunction<T extends ParseNode> = (
|
||||
node: T,
|
||||
scope: Record<string, any>,
|
||||
context: Context,
|
||||
input: InputProvider,
|
||||
resolveOthers: ResolveOthersFunction,
|
||||
) => Promise<ResolvedResult>;
|
||||
|
||||
export default ResolveLevelFunction;
|
||||
15
app/imports/parser/types/ResolveOthersFunction.ts
Normal file
15
app/imports/parser/types/ResolveOthersFunction.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import InputProvider from '/imports/api/engine/action/functions/InputProvider';
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import Context from '/imports/parser/types/Context';
|
||||
import ResolveLevel from '/imports/parser/types/ResolveLevel';
|
||||
import ResolvedResult from '/imports/parser/types/ResolvedResult';
|
||||
|
||||
type ResolveOthersFunction = (
|
||||
fn: ResolveLevel,
|
||||
node: ParseNode,
|
||||
scope: Record<string, any>,
|
||||
context: Context,
|
||||
inputProvider: InputProvider,
|
||||
) => Promise<ResolvedResult>
|
||||
|
||||
export default ResolveOthersFunction;
|
||||
9
app/imports/parser/types/ResolvedResult.ts
Normal file
9
app/imports/parser/types/ResolvedResult.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import Context from './Context';
|
||||
|
||||
type ResolvedResult = {
|
||||
result: ParseNode;
|
||||
context: Context;
|
||||
};
|
||||
|
||||
export default ResolvedResult;
|
||||
11
app/imports/parser/types/ToStringFunction.ts
Normal file
11
app/imports/parser/types/ToStringFunction.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
|
||||
type ToStringFunction<T extends ParseNode> = {
|
||||
(node: T, stringOthers: ToStringOthersFunction): string;
|
||||
}
|
||||
|
||||
export default ToStringFunction;
|
||||
|
||||
type ToStringOthersFunction = {
|
||||
(node: ParseNode): string;
|
||||
}
|
||||
15
app/imports/parser/types/TraverseFunction.ts
Normal file
15
app/imports/parser/types/TraverseFunction.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
|
||||
type TraverseFunction<T extends ParseNode> = {
|
||||
(
|
||||
node: T,
|
||||
fn: (node: ParseNode) => any,
|
||||
traverseOthers: TraverseOthersFunction
|
||||
): any;
|
||||
}
|
||||
|
||||
export default TraverseFunction;
|
||||
|
||||
type TraverseOthersFunction = {
|
||||
(node: ParseNode, fn: (node: ParseNode) => any): any
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures';
|
||||
import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables';
|
||||
import Actions from '/imports/api/engine/actions/ActionEngine';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||
import CreatureLogs from '/imports/api/creature/log/CreatureLogs';
|
||||
import { assertViewPermission } from '/imports/api/creature/creatures/creaturePermissions';
|
||||
@@ -9,6 +8,7 @@ import computeCreature from '/imports/api/engine/computeCreature';
|
||||
import VERSION from '/imports/constants/VERSION';
|
||||
import { loadCreature } from '/imports/api/engine/loadCreatures';
|
||||
import { rebuildCreatureNestedSets } from '/imports/api/parenting/parentingFunctions';
|
||||
import EngineActions from '/imports/api/engine/action/EngineActions';
|
||||
|
||||
let schema = new SimpleSchema({
|
||||
creatureId: {
|
||||
@@ -62,7 +62,7 @@ Meteor.publish('singleCharacter', function (creatureId) {
|
||||
limit: 20,
|
||||
sort: { date: -1 },
|
||||
}),
|
||||
Actions.find({
|
||||
EngineActions.find({
|
||||
creatureId,
|
||||
}),
|
||||
// Also publish the owner's username
|
||||
|
||||
28
app/package-lock.json
generated
28
app/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dicecloud",
|
||||
"version": "2.0.59",
|
||||
"version": "2.1.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -1875,14 +1875,17 @@
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||
},
|
||||
"available-typed-arrays": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz",
|
||||
"integrity": "sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg=="
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||
"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
|
||||
"requires": {
|
||||
"possible-typed-array-names": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"aws-sdk": {
|
||||
"version": "2.1559.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1559.0.tgz",
|
||||
"integrity": "sha512-Lz/VgIhMtvDF1kP0xgXckrR5B2w7FD4IwLZfQ//t1Fu5YZVmYz6esHFba2q6X/Tw66q/Aig/rzQeYg0idSn9Kw==",
|
||||
"version": "2.1561.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1561.0.tgz",
|
||||
"integrity": "sha512-YbYQOyvy9mfEGRI4JDZjw6J0zW6bjyV7H3WMWeq69qETvZlkq8koy5CTPMCjnL8i7boDjyW9FuhQzICBbeNgLg==",
|
||||
"requires": {
|
||||
"buffer": "4.9.2",
|
||||
"events": "1.1.1",
|
||||
@@ -2968,9 +2971,9 @@
|
||||
}
|
||||
},
|
||||
"has-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg=="
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q=="
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.0.3",
|
||||
@@ -4520,6 +4523,11 @@
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true
|
||||
},
|
||||
"possible-typed-array-names": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
|
||||
"integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q=="
|
||||
},
|
||||
"prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dicecloud",
|
||||
"version": "2.0.59",
|
||||
"version": "2.1.0",
|
||||
"description": "Unofficial Online Realtime D&D 5e App",
|
||||
"license": "GPL-3.0",
|
||||
"repository": {
|
||||
@@ -25,7 +25,7 @@
|
||||
"@tozd/vue-observer-utils": "^0.5.0",
|
||||
"@types/meteor": "^2.9.8",
|
||||
"alea": "^1.0.1",
|
||||
"aws-sdk": "^2.1559.0",
|
||||
"aws-sdk": "^2.1561.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"chroma-js": "^2.4.2",
|
||||
"css-box-shadow": "^1.0.0-3",
|
||||
|
||||
@@ -10,7 +10,6 @@ import '/imports/server/publications/index';
|
||||
import '/imports/server/cron/deleteSoftRemovedDocuments';
|
||||
import '/imports/api/parenting/organizeMethods';
|
||||
import '/imports/api/users/patreon/updatePatreonOnLogin';
|
||||
import '/imports/api/engine/actions/index';
|
||||
import '/imports/migrations/server/index';
|
||||
import '/imports/migrations/methods/index'
|
||||
import '/imports/constants/MAINTENANCE_MODE';
|
||||
|
||||
Reference in New Issue
Block a user