diff --git a/.vscode/settings.json b/.vscode/settings.json index dfc0d6d0..f5b11207 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,7 +6,10 @@ "Crits", "cyrb", "EJSON", + "healthbar", + "healthbars", "nearley", - "uncomputed" + "uncomputed", + "walkdown" ] } diff --git a/app/.meteor/packages b/app/.meteor/packages index 4f678f39..3d996c5d 100644 --- a/app/.meteor/packages +++ b/app/.meteor/packages @@ -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 diff --git a/app/.meteor/versions b/app/.meteor/versions index 7983e6cc..0e4403f3 100644 --- a/app/.meteor/versions +++ b/app/.meteor/versions @@ -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 diff --git a/app/imports/api/creature/creatures/CreatureVariables.js b/app/imports/api/creature/creatures/CreatureVariables.ts similarity index 84% rename from app/imports/api/creature/creatures/CreatureVariables.js rename to app/imports/api/creature/creatures/CreatureVariables.ts index 3dc88aae..8a307198 100644 --- a/app/imports/api/creature/creatures/CreatureVariables.js +++ b/app/imports/api/creature/creatures/CreatureVariables.ts @@ -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); diff --git a/app/imports/api/creature/log/CreatureLogs.js b/app/imports/api/creature/log/CreatureLogs.js index 158c28d4..e4f03301 100644 --- a/app/imports/api/creature/log/CreatureLogs.js +++ b/app/imports/api/creature/log/CreatureLogs.js @@ -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 diff --git a/app/imports/api/engine/action/applyProperties/applyActionProperty.test.ts b/app/imports/api/engine/action/applyProperties/applyActionProperty.test.ts index a6785ef0..b1829d66 100644 --- a/app/imports/api/engine/action/applyProperties/applyActionProperty.test.ts +++ b/app/imports/api/engine/action/applyProperties/applyActionProperty.test.ts @@ -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, diff --git a/app/imports/api/engine/action/applyProperties/applyActionProperty.ts b/app/imports/api/engine/action/applyProperties/applyActionProperty.ts index 93ca5e09..67ddcb64 100644 --- a/app/imports/api/engine/action/applyProperties/applyActionProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyActionProperty.ts @@ -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; diff --git a/app/imports/api/engine/action/applyProperties/applyBranchProperty.ts b/app/imports/api/engine/action/applyProperties/applyBranchProperty.ts index b12c93bb..e828fa44 100644 --- a/app/imports/api/engine/action/applyProperties/applyBranchProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyBranchProperty.ts @@ -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) { diff --git a/app/imports/api/engine/action/applyProperties/applyBuffProperty.ts b/app/imports/api/engine/action/applyProperties/applyBuffProperty.ts index 6dc5589f..3c21c894 100644 --- a/app/imports/api/engine/action/applyProperties/applyBuffProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyBuffProperty.ts @@ -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; }); - }); - }); + } + } } diff --git a/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts b/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts index b36830e7..ef6d48d0 100644 --- a/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyDamageProperty.ts @@ -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; } diff --git a/app/imports/api/engine/action/applyProperties/applyRollProperty.ts b/app/imports/api/engine/action/applyProperties/applyRollProperty.ts index 193cb88d..b683c454 100644 --- a/app/imports/api/engine/action/applyProperties/applyRollProperty.ts +++ b/app/imports/api/engine/action/applyProperties/applyRollProperty.ts @@ -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 diff --git a/app/imports/api/engine/action/functions/InputProvider.ts b/app/imports/api/engine/action/functions/InputProvider.ts index f9c1933d..aab27246 100644 --- a/app/imports/api/engine/action/functions/InputProvider.ts +++ b/app/imports/api/engine/action/functions/InputProvider.ts @@ -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; /** * 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)[], quantity?: [min: number, max: number], ): Promise; diff --git a/app/imports/api/engine/action/functions/inputProviderForTests.testFn.ts b/app/imports/api/engine/action/functions/inputProviderForTests.testFn.ts index c162f010..c76daded 100644 --- a/app/imports/api/engine/action/functions/inputProviderForTests.testFn.ts +++ b/app/imports/api/engine/action/functions/inputProviderForTests.testFn.ts @@ -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) { diff --git a/app/imports/api/engine/action/functions/recalculateCalculation.ts b/app/imports/api/engine/action/functions/recalculateCalculation.ts index 02588bcb..15a217e5 100644 --- a/app/imports/api/engine/action/functions/recalculateCalculation.ts +++ b/app/imports/api/engine/action/functions/recalculateCalculation.ts @@ -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 }; diff --git a/app/imports/api/engine/action/functions/recalculateInlineCalculations.ts b/app/imports/api/engine/action/functions/recalculateInlineCalculations.ts index 2722c395..41230fe5 100644 --- a/app/imports/api/engine/action/functions/recalculateInlineCalculations.ts +++ b/app/imports/api/engine/action/functions/recalculateInlineCalculations.ts @@ -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); } diff --git a/app/imports/api/engine/action/functions/spendResources.ts b/app/imports/api/engine/action/functions/spendResources.ts index f5a26f8c..66f6310a 100644 --- a/app/imports/api/engine/action/functions/spendResources.ts +++ b/app/imports/api/engine/action/functions/spendResources.ts @@ -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'; } diff --git a/app/imports/api/engine/action/tasks/TaskResult.ts b/app/imports/api/engine/action/tasks/TaskResult.ts index 5551f9cc..ca52a11b 100644 --- a/app/imports/api/engine/action/tasks/TaskResult.ts +++ b/app/imports/api/engine/action/tasks/TaskResult.ts @@ -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 = { diff --git a/app/imports/api/engine/action/tasks/applyDamagePropTask.ts b/app/imports/api/engine/action/tasks/applyDamagePropTask.ts index 25bbb5dc..ea5b3487 100644 --- a/app/imports/api/engine/action/tasks/applyDamagePropTask.ts +++ b/app/imports/api/engine/action/tasks/applyDamagePropTask.ts @@ -8,7 +8,7 @@ import { getSingleProperty } from '/imports/api/engine/loadCreatures'; export default async function applyDamagePropTask( task: DamagePropTask, action: EngineAction, result: TaskResult, userInput -): Promise { +): Promise { 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; } \ No newline at end of file diff --git a/app/imports/api/engine/action/tasks/applyTask.ts b/app/imports/api/engine/action/tasks/applyTask.ts index 796d19e2..4fb5e766 100644 --- a/app/imports/api/engine/action/tasks/applyTask.ts +++ b/app/imports/api/engine/action/tasks/applyTask.ts @@ -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 + +// Other tasks promise nothing +export default async function applyTask( + action: EngineAction, task: PropTask | ItemAsAmmoTask, userInput: InputProvider +): Promise + export default async function applyTask( action: EngineAction, task: Task, userInput: InputProvider -): Promise { +): Promise { action.taskCount += 1; if (action.taskCount > 100) throw 'Only 100 properties can be applied at once'; diff --git a/app/imports/api/engine/computation/buildComputation/linkCalculationDependencies.js b/app/imports/api/engine/computation/buildComputation/linkCalculationDependencies.js index 5e3601e7..d07c13a6 100644 --- a/app/imports/api/engine/computation/buildComputation/linkCalculationDependencies.js +++ b/app/imports/api/engine/computation/buildComputation/linkCalculationDependencies.js @@ -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 => { diff --git a/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js b/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js index 5558f062..9e8a2d68 100644 --- a/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js +++ b/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js @@ -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 }); } } diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.ts similarity index 86% rename from app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js rename to app/imports/api/engine/computation/computeComputation/computeByType/computeAction.ts index 5c6d469d..6087f646 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.ts @@ -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); diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeCalculation.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeCalculation.js index d49f0cd4..78c2b86c 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeCalculation.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeCalculation.js @@ -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; } diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computePointBuy.js b/app/imports/api/engine/computation/computeComputation/computeByType/computePointBuy.js index c2e3da59..47f83925 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computePointBuy.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computePointBuy.js @@ -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 || []; diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsAttribute.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsAttribute.js index 0252490a..b2b5902a 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsAttribute.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsAttribute.js @@ -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 diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsToggle.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsToggle.js index f546426a..f38ecc60 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsToggle.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsToggle.js @@ -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; } diff --git a/app/imports/api/engine/computation/computeComputation/tests/computeAction.testFn.js b/app/imports/api/engine/computation/computeComputation/tests/computeAction.testFn.js index a915d5cd..bf4087c1 100644 --- a/app/imports/api/engine/computation/computeComputation/tests/computeAction.testFn.js +++ b/app/imports/api/engine/computation/computeComputation/tests/computeAction.testFn.js @@ -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'); diff --git a/app/imports/api/engine/computation/computeComputation/tests/computeAttribute.testFn.js b/app/imports/api/engine/computation/computeComputation/tests/computeAttribute.testFn.js index 51d717de..6ea411a0 100644 --- a/app/imports/api/engine/computation/computeComputation/tests/computeAttribute.testFn.js +++ b/app/imports/api/engine/computation/computeComputation/tests/computeAttribute.testFn.js @@ -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'); diff --git a/app/imports/api/engine/computation/computeComputation/tests/computeCalculations.testFn.js b/app/imports/api/engine/computation/computeComputation/tests/computeCalculations.testFn.js index f7471b23..bdbe0b65 100644 --- a/app/imports/api/engine/computation/computeComputation/tests/computeCalculations.testFn.js +++ b/app/imports/api/engine/computation/computeComputation/tests/computeCalculations.testFn.js @@ -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 = [ diff --git a/app/imports/api/engine/computation/computeComputation/tests/computeClasses.testFn.js b/app/imports/api/engine/computation/computeComputation/tests/computeClasses.testFn.js index e1151fa1..a1d50ed3 100644 --- a/app/imports/api/engine/computation/computeComputation/tests/computeClasses.testFn.js +++ b/app/imports/api/engine/computation/computeComputation/tests/computeClasses.testFn.js @@ -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); diff --git a/app/imports/api/engine/computation/computeComputation/tests/computeConstants.testFn.js b/app/imports/api/engine/computation/computeComputation/tests/computeConstants.testFn.js index 13b8f77e..793924b7 100644 --- a/app/imports/api/engine/computation/computeComputation/tests/computeConstants.testFn.js +++ b/app/imports/api/engine/computation/computeComputation/tests/computeConstants.testFn.js @@ -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); } diff --git a/app/imports/api/engine/computation/computeComputation/tests/computeDamageMultipliers.testFn.js b/app/imports/api/engine/computation/computeComputation/tests/computeDamageMultipliers.testFn.js index ed71ad98..86d984b7 100644 --- a/app/imports/api/engine/computation/computeComputation/tests/computeDamageMultipliers.testFn.js +++ b/app/imports/api/engine/computation/computeComputation/tests/computeDamageMultipliers.testFn.js @@ -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); diff --git a/app/imports/api/engine/computation/computeComputation/tests/computeEffects.testFn.js b/app/imports/api/engine/computation/computeComputation/tests/computeEffects.testFn.js index 733ede0d..9ef3d6ef 100644 --- a/app/imports/api/engine/computation/computeComputation/tests/computeEffects.testFn.js +++ b/app/imports/api/engine/computation/computeComputation/tests/computeEffects.testFn.js @@ -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); } diff --git a/app/imports/api/engine/computation/computeComputation/tests/computeInventory.testFn.js b/app/imports/api/engine/computation/computeComputation/tests/computeInventory.testFn.js index e1893fe5..98878cb2 100644 --- a/app/imports/api/engine/computation/computeComputation/tests/computeInventory.testFn.js +++ b/app/imports/api/engine/computation/computeComputation/tests/computeInventory.testFn.js @@ -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; diff --git a/app/imports/api/engine/computation/computeComputation/tests/computePointBuys.testFn.js b/app/imports/api/engine/computation/computeComputation/tests/computePointBuys.testFn.js index f70efa87..ac19f8da 100644 --- a/app/imports/api/engine/computation/computeComputation/tests/computePointBuys.testFn.js +++ b/app/imports/api/engine/computation/computeComputation/tests/computePointBuys.testFn.js @@ -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'); } diff --git a/app/imports/api/engine/computation/computeComputation/tests/computeProficiencies.testFn.js b/app/imports/api/engine/computation/computeComputation/tests/computeProficiencies.testFn.js index 7f427160..805e9eaf 100644 --- a/app/imports/api/engine/computation/computeComputation/tests/computeProficiencies.testFn.js +++ b/app/imports/api/engine/computation/computeComputation/tests/computeProficiencies.testFn.js @@ -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, diff --git a/app/imports/api/engine/computation/computeComputation/tests/computeSkills.testFn.js b/app/imports/api/engine/computation/computeComputation/tests/computeSkills.testFn.js index a8d3e325..e1b1a049 100644 --- a/app/imports/api/engine/computation/computeComputation/tests/computeSkills.testFn.js +++ b/app/imports/api/engine/computation/computeComputation/tests/computeSkills.testFn.js @@ -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'); diff --git a/app/imports/api/engine/computation/computeCreatureComputation.js b/app/imports/api/engine/computation/computeCreatureComputation.js index 15369790..f74b4368 100644 --- a/app/imports/api/engine/computation/computeCreatureComputation.js +++ b/app/imports/api/engine/computation/computeCreatureComputation.js @@ -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) { diff --git a/app/imports/api/engine/computation/utility/applyFnToKey.js b/app/imports/api/engine/computation/utility/applyFnToKey.js index 0d4005af..d0dd90bd 100644 --- a/app/imports/api/engine/computation/utility/applyFnToKey.js +++ b/app/imports/api/engine/computation/utility/applyFnToKey.js @@ -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], + }); + } + } + } +} diff --git a/app/imports/api/engine/computeCreature.js b/app/imports/api/engine/computeCreature.js index ca3e7cc3..86793165 100644 --- a/app/imports/api/engine/computeCreature.js +++ b/app/imports/api/engine/computeCreature.js @@ -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) { diff --git a/app/imports/api/library/getCreatureLibraryIds.js b/app/imports/api/library/getCreatureLibraryIds.js index 14b6c5e7..c5c4938e 100644 --- a/app/imports/api/library/getCreatureLibraryIds.js +++ b/app/imports/api/library/getCreatureLibraryIds.js @@ -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 diff --git a/app/imports/api/properties/Constants.js b/app/imports/api/properties/Constants.js index 9b70bfca..2ef18326 100644 --- a/app/imports/api/properties/Constants.js +++ b/app/imports/api/properties/Constants.js @@ -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 diff --git a/app/imports/api/properties/tests/propTestBuilder.testFn.js b/app/imports/api/properties/tests/propTestBuilder.testFn.js index 59ddfb6f..deddfd79 100644 --- a/app/imports/api/properties/tests/propTestBuilder.testFn.js +++ b/app/imports/api/properties/tests/propTestBuilder.testFn.js @@ -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 diff --git a/app/imports/api/utility/asyncMap.ts b/app/imports/api/utility/asyncMap.ts new file mode 100644 index 00000000..b216a336 --- /dev/null +++ b/app/imports/api/utility/asyncMap.ts @@ -0,0 +1,19 @@ +/** + * Async compatible map that processes all items in parallel + */ +export async function parallelMap(array: any[], fn: (doc: any) => Promise): Promise { + 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): Promise { + const results: any[] = []; + for (const doc of array) { + const result = await fn(doc); + results.push(result); + } + return results; +} + diff --git a/app/imports/client/ui/creature/slots/LevelUpDialog.vue b/app/imports/client/ui/creature/slots/LevelUpDialog.vue index a33d3520..73d6cb4a 100644 --- a/app/imports/client/ui/creature/slots/LevelUpDialog.vue +++ b/app/imports/client/ui/creature/slots/LevelUpDialog.vue @@ -476,3 +476,4 @@ export default { opacity: 0.7; } +resolveimport { toString } from '/imports/parser/toString'; diff --git a/app/imports/client/ui/creature/slots/SlotFillDialog.vue b/app/imports/client/ui/creature/slots/SlotFillDialog.vue index dbfa4a3b..7bf14551 100644 --- a/app/imports/client/ui/creature/slots/SlotFillDialog.vue +++ b/app/imports/client/ui/creature/slots/SlotFillDialog.vue @@ -558,3 +558,4 @@ export default { opacity: 0.7; } +resolveimport { toString } from '/imports/parser/toString'; diff --git a/app/imports/client/ui/log/CharacterLog.vue b/app/imports/client/ui/log/CharacterLog.vue index db09f9fb..87ee5eee 100644 --- a/app/imports/client/ui/log/CharacterLog.vue +++ b/app/imports/client/ui/log/CharacterLog.vue @@ -183,3 +183,4 @@ export default { margin-bottom: 0; } +resolveimport { toString } from '/imports/parser/toString'; diff --git a/app/imports/client/ui/properties/forms/PointBuySpendForm.vue b/app/imports/client/ui/properties/forms/PointBuySpendForm.vue index cf0b8d04..7e5f8282 100644 --- a/app/imports/client/ui/properties/forms/PointBuySpendForm.vue +++ b/app/imports/client/ui/properties/forms/PointBuySpendForm.vue @@ -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; diff --git a/app/imports/parser/grammar.js b/app/imports/parser/grammar.js index bb42dbf7..5c502440 100644 --- a/app/imports/parser/grammar.js +++ b/app/imports/parser/grammar.js @@ -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'; diff --git a/app/imports/parser/grammar.ne b/app/imports/parser/grammar.ne index c1883357..5901e75d 100644 --- a/app/imports/parser/grammar.ne +++ b/app/imports/parser/grammar.ne @@ -1,6 +1,6 @@ @preprocessor esmodule @{% - import node from './parseTree/_index'; + import node from '/imports/parser/parseTree/_index'; import moo from 'moo'; diff --git a/app/imports/parser/map.ts b/app/imports/parser/map.ts new file mode 100644 index 00000000..98a3fa42 --- /dev/null +++ b/app/imports/parser/map.ts @@ -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): Promise { + 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); +} diff --git a/app/imports/parser/parseTree/NodeFactory.ts b/app/imports/parser/parseTree/NodeFactory.ts deleted file mode 100644 index fb3d1821..00000000 --- a/app/imports/parser/parseTree/NodeFactory.ts +++ /dev/null @@ -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; - - compile?( - node: ParseNode, scope: Record, context: Context - ): ResolvedResult; - - roll?( - node: ParseNode, scope: Record, context: Context - ): ResolvedResult; - - reduce?( - node: ParseNode, scope: Record, context: Context - ): ResolvedResult; - - resolve?( - fn: ResolveLevel, node: ParseNode, scope: Record, context: Context - ): ResolvedResult; - - toString(node: ParseNode): string; - traverse?(node: ParseNode, fn: (node: ParseNode) => any): ReturnType; - map?(node: ParseNode, fn: (node: ParseNode) => any): ReturnType; -} diff --git a/app/imports/parser/parseTree/_index.ts b/app/imports/parser/parseTree/_index.ts deleted file mode 100644 index 89dbfa96..00000000 --- a/app/imports/parser/parseTree/_index.ts +++ /dev/null @@ -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 = { - 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; diff --git a/app/imports/parser/parseTree/accessor.ts b/app/imports/parser/parseTree/accessor.ts index ee72779e..0d7cb2d3 100644 --- a/app/imports/parser/parseTree/accessor.ts +++ b/app/imports/parser/parseTree/accessor.ts @@ -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; - compile( - node: AccessorNode, scope: Record, context: Context - ): ResolvedResult; - roll?: undefined; - resolve?: undefined; - reduce( - node: AccessorNode, scope: Record, context: Context - ): ResolvedResult; + compile: ResolveLevelFunction; + reduce: ResolveLevelFunction; toString(node: AccessorNode): string; - traverse?: undefined; - map?: undefined; } const accessor: AccessorFactory = { @@ -33,9 +25,7 @@ const accessor: AccessorFactory = { name, }; }, - compile( - node: AccessorNode, scope: Record, 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 { + 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({ diff --git a/app/imports/parser/parseTree/array.ts b/app/imports/parser/parseTree/array.ts index c37b5d1a..6462a12d 100644 --- a/app/imports/parser/parseTree/array.ts +++ b/app/imports/parser/parseTree/array.ts @@ -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; fromConstantArray(array: (string | number | boolean | undefined)[]): ArrayNode; - compile?: undefined; - roll?: undefined; - reduce?: undefined; - resolve( - fn: ResolveLevel, node: ArrayNode, scope: Record, context: Context - ): ResolvedResult; - toString(node: ArrayNode): string; - traverse(node: ArrayNode, fn: (node: ParseNode) => any): ReturnType; - map(node: ArrayNode, fn: (node: ParseNode) => any): ReturnType; + resolve: ResolveFunction; + toString: ToStringFunction; + traverse: TraverseFunction; + map: MapFunction; } -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; diff --git a/app/imports/parser/parseTree/call.ts b/app/imports/parser/parseTree/call.ts index 9712c9ad..ed9f8020 100644 --- a/app/imports/parser/parseTree/call.ts +++ b/app/imports/parser/parseTree/call.ts @@ -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; - resolve( - fn: ResolveLevel, node: CallNode, scope: Record, context: Context - ): ResolvedResult; - toString(node: CallNode): string; - traverse(node: CallNode, fn: (node: ParseNode) => any): ReturnType; - map(node: CallNode, fn: (node: ParseNode) => any): ReturnType; + resolve: ResolveFunction; + toString: ToStringFunction; + traverse: TraverseFunction; + map: MapFunction; 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 { 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 => { // 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}`); } }); diff --git a/app/imports/parser/parseTree/constant.ts b/app/imports/parser/parseTree/constant.ts index e74a6397..43467ffd 100644 --- a/app/imports/parser/parseTree/constant.ts +++ b/app/imports/parser/parseTree/constant.ts @@ -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, context: Context - ): ResolvedResult; - roll?: undefined; - reduce?: undefined; - resolve?: undefined; + compile: ResolveLevelFunction; 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; diff --git a/app/imports/parser/parseTree/error.ts b/app/imports/parser/parseTree/error.ts index 484d8b50..9647b7d2 100644 --- a/app/imports/parser/parseTree/error.ts +++ b/app/imports/parser/parseTree/error.ts @@ -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; - compile( - node: ErrorNode, scope: Record, context: Context - ): ResolvedResult; - roll?: undefined; - reduce?: undefined; - resolve?: undefined; - toString(node: ErrorNode): string; - traverse?: undefined; - map?: undefined; + compile: ResolveLevelFunction; + toString: ToStringFunction; } 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) { diff --git a/app/imports/parser/parseTree/if.ts b/app/imports/parser/parseTree/if.ts index d6a4e332..15efbb84 100644 --- a/app/imports/parser/parseTree/if.ts +++ b/app/imports/parser/parseTree/if.ts @@ -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; - compile?: undefined; - roll?: undefined; - reduce?: undefined; - resolve( - fn: ResolveLevel, node: IfNode, scope: Record, context: Context - ): ResolvedResult; - toString(node: IfNode): string; - traverse(node: IfNode, fn: (node: ParseNode) => any): ReturnType; - map(node: IfNode, fn: (node: ParseNode) => any): ReturnType; + resolve: ResolveFunction; + toString: ToStringFunction; + traverse: TraverseFunction; + map: MapFunction; } 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 { + 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; }, diff --git a/app/imports/parser/parseTree/index.ts b/app/imports/parser/parseTree/index.ts new file mode 100644 index 00000000..75df057a --- /dev/null +++ b/app/imports/parser/parseTree/index.ts @@ -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; diff --git a/app/imports/parser/parseTree/indexNode.ts b/app/imports/parser/parseTree/indexNode.ts index a28868f9..18953f45 100644 --- a/app/imports/parser/parseTree/indexNode.ts +++ b/app/imports/parser/parseTree/indexNode.ts @@ -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; - compile?: undefined; - roll?: undefined; - reduce?: undefined; - resolve( - fn: ResolveLevel, node: IndexNode, scope: Record, context: Context - ): ResolvedResult; - toString(node: IndexNode): string; - traverse(node: IndexNode, fn: (node: ParseNode) => any): ReturnType; - map(node: IndexNode, fn: (node: ParseNode) => any): ReturnType; + resolve: ResolveFunction; + toString: ToStringFunction; + traverse: TraverseFunction; + map: MapFunction; } 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; }, diff --git a/app/imports/parser/parseTree/not.ts b/app/imports/parser/parseTree/not.ts index 052e8d83..05bf7d68 100644 --- a/app/imports/parser/parseTree/not.ts +++ b/app/imports/parser/parseTree/not.ts @@ -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; - compile?: undefined; - roll?: undefined; - reduce?: undefined; - resolve( - fn: ResolveLevel, node: NotNode, scope: Record, context: Context - ): ResolvedResult; - toString(node: NotNode): string; - traverse(node: NotNode, fn: (node: ParseNode) => any): ReturnType; - map(node: NotNode, fn: (node: ParseNode) => any): ReturnType; + resolve: ResolveFunction; + toString: ToStringFunction; + traverse: TraverseFunction; + map: MapFunction; } 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; }, diff --git a/app/imports/parser/parseTree/operator.ts b/app/imports/parser/parseTree/operator.ts index 16152a9c..2deef9ec 100644 --- a/app/imports/parser/parseTree/operator.ts +++ b/app/imports/parser/parseTree/operator.ts @@ -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; - compile?: undefined; - roll?: undefined; - reduce?: undefined; - resolve( - fn: ResolveLevel, node: OperatorNode, scope: Record, context: Context - ): ResolvedResult; - toString(node: OperatorNode): string; - traverse(node: OperatorNode, fn: (node: ParseNode) => any): ReturnType; - map(node: OperatorNode, fn: (node: ParseNode) => any): ReturnType; + resolve: ResolveFunction; + toString: ToStringFunction; + traverse: TraverseFunction; + map: MapFunction; } // 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; diff --git a/app/imports/parser/parseTree/parenthesis.ts b/app/imports/parser/parseTree/parenthesis.ts index 8c0c9e6f..d54f5e65 100644 --- a/app/imports/parser/parseTree/parenthesis.ts +++ b/app/imports/parser/parseTree/parenthesis.ts @@ -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; - compile?: undefined; - roll?: undefined; - reduce?: undefined; - resolve( - fn: ResolveLevel, node: ParenthesisNode, scope: Record, context: Context - ): ResolvedResult; - toString(node: ParenthesisNode): string; - traverse(node: ParenthesisNode, fn: (node: ParseNode) => any): ReturnType; - map(node: ParenthesisNode, fn: (node: ParseNode) => any): ReturnType; + resolve: ResolveFunction; + toString: ToStringFunction; + traverse: TraverseFunction; + map: MapFunction; } 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; }, diff --git a/app/imports/parser/parseTree/roll.ts b/app/imports/parser/parseTree/roll.ts index 405f0e02..6efcff3c 100644 --- a/app/imports/parser/parseTree/roll.ts +++ b/app/imports/parser/parseTree/roll.ts @@ -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; - compile( - node: RollNode, scope: Record, context: Context - ): ResolvedResult; - roll( - node: RollNode, scope: Record, context: Context - ): ResolvedResult; - reduce( - node: RollNode, scope: Record, context: Context - ): ResolvedResult; - resolve?: undefined; - toString(node: RollNode): string; - traverse(node: RollNode, fn: (node: ParseNode) => any): ReturnType; - map(node: RollNode, fn: (node: ParseNode) => any): ReturnType; + compile: ResolveLevelFunction; + roll: ResolveLevelFunction; + reduce: ResolveLevelFunction; + toString: ToStringFunction; + traverse: TraverseFunction; + map: MapFunction; } 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; }, diff --git a/app/imports/parser/parseTree/rollArray.ts b/app/imports/parser/parseTree/rollArray.ts index da408994..66936fc9 100644 --- a/app/imports/parser/parseTree/rollArray.ts +++ b/app/imports/parser/parseTree/rollArray.ts @@ -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, context: Context - ): ResolvedResult; - roll?: undefined; - reduce( - node: RollArrayNode, scope: Record, context: Context - ): ResolvedResult; - resolve?: undefined; - toString(node: RollArrayNode): string; - traverse?: undefined; - map?: undefined; + compile: ResolveLevelFunction; + reduce: ResolveLevelFunction; + toString: ToStringFunction; } 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; diff --git a/app/imports/parser/parseTree/unaryOperator.ts b/app/imports/parser/parseTree/unaryOperator.ts index 9bea236a..bfdfd88e 100644 --- a/app/imports/parser/parseTree/unaryOperator.ts +++ b/app/imports/parser/parseTree/unaryOperator.ts @@ -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; - compile?: undefined; - roll?: undefined; - reduce?: undefined; - resolve( - fn: ResolveLevel, node: UnaryOperatorNode, scope: Record, context: Context - ): ResolvedResult; - toString(node: UnaryOperatorNode): string; - traverse(node: UnaryOperatorNode, fn: (node: ParseNode) => any): ReturnType; - map(node: UnaryOperatorNode, fn: (node: ParseNode) => any): ReturnType; + resolve: ResolveFunction; + toString: ToStringFunction; + traverse: TraverseFunction; + map: MapFunction; } 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; }, diff --git a/app/imports/parser/parser.test.js b/app/imports/parser/parser.test.js index 87daa788..96ce2aeb 100644 --- a/app/imports/parser/parser.test.js +++ b/app/imports/parser/parser.test.js @@ -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'); }); }); diff --git a/app/imports/parser/resolve.ts b/app/imports/parser/resolve.ts index a97deb72..336b4e37 100644 --- a/app/imports/parser/resolve.ts +++ b/app/imports/parser/resolve.ts @@ -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, - context = new Context() -): ResolvedResult { - if (!node) throw 'Node must be supplied'; - const factory = nodeTypeIndex[node.parseType]; - const handlerFunction = factory[fn]; + scope: Record = {}, + context = new Context(), + inputProvider = computationInputProvider, +): Promise { + if (!node) throw new Error('Node must be supplied'); + const factory = factories[node.parseType]; + const handlerFunction: ResolveLevelFunction = 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 { - 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 { - 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; } } diff --git a/app/imports/parser/toPrimitiveOrString.ts b/app/imports/parser/toPrimitiveOrString.ts new file mode 100644 index 00000000..96982f6b --- /dev/null +++ b/app/imports/parser/toPrimitiveOrString.ts @@ -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); +} diff --git a/app/imports/parser/toString.ts b/app/imports/parser/toString.ts new file mode 100644 index 00000000..d356eeb5 --- /dev/null +++ b/app/imports/parser/toString.ts @@ -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); +} diff --git a/app/imports/parser/traverse.ts b/app/imports/parser/traverse.ts new file mode 100644 index 00000000..2d954091 --- /dev/null +++ b/app/imports/parser/traverse.ts @@ -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 { + if (!node) return; + const factory = factories[node.parseType]; + if ('traverse' in factory) { + return factory.traverse(node as any, fn, traverse); + } + return fn(node); +} diff --git a/app/imports/parser/types/Context.ts b/app/imports/parser/types/Context.ts new file mode 100644 index 00000000..59e95416 --- /dev/null +++ b/app/imports/parser/types/Context.ts @@ -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); + } +} diff --git a/app/imports/parser/types/MapFunction.ts b/app/imports/parser/types/MapFunction.ts new file mode 100644 index 00000000..93ebfc20 --- /dev/null +++ b/app/imports/parser/types/MapFunction.ts @@ -0,0 +1,11 @@ +import ParseNode from '/imports/parser/parseTree/ParseNode'; + +type MapFunction = { + (node: T, fn: (node: ParseNode) => Promise, mapOthers: MapOthersFunction): Promise; +} + +export default MapFunction; + +type MapOthersFunction = { + (node: ParseNode, fn: (node: ParseNode) => Promise): Promise +} diff --git a/app/imports/parser/types/ResolveFunction.ts b/app/imports/parser/types/ResolveFunction.ts new file mode 100644 index 00000000..9690cd9d --- /dev/null +++ b/app/imports/parser/types/ResolveFunction.ts @@ -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 = ( + fn: ResolveLevel, + node: T, + scope: Record, + context: Context, + input: InputProvider, + resolveOthers: ResolveOthersFunction, +) => Promise; + +export default ResolveFunction; + diff --git a/app/imports/parser/types/ResolveLevel.ts b/app/imports/parser/types/ResolveLevel.ts new file mode 100644 index 00000000..94929265 --- /dev/null +++ b/app/imports/parser/types/ResolveLevel.ts @@ -0,0 +1,3 @@ +type ResolveLevel = 'compile' | 'roll' | 'reduce'; + +export default ResolveLevel; diff --git a/app/imports/parser/types/ResolveLevelFunction.ts b/app/imports/parser/types/ResolveLevelFunction.ts new file mode 100644 index 00000000..4924e890 --- /dev/null +++ b/app/imports/parser/types/ResolveLevelFunction.ts @@ -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 = ( + node: T, + scope: Record, + context: Context, + input: InputProvider, + resolveOthers: ResolveOthersFunction, +) => Promise; + +export default ResolveLevelFunction; diff --git a/app/imports/parser/types/ResolveOthersFunction.ts b/app/imports/parser/types/ResolveOthersFunction.ts new file mode 100644 index 00000000..36c14698 --- /dev/null +++ b/app/imports/parser/types/ResolveOthersFunction.ts @@ -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, + context: Context, + inputProvider: InputProvider, +) => Promise + +export default ResolveOthersFunction; diff --git a/app/imports/parser/types/ResolvedResult.ts b/app/imports/parser/types/ResolvedResult.ts new file mode 100644 index 00000000..a8d5cdd9 --- /dev/null +++ b/app/imports/parser/types/ResolvedResult.ts @@ -0,0 +1,9 @@ +import ParseNode from '/imports/parser/parseTree/ParseNode'; +import Context from './Context'; + +type ResolvedResult = { + result: ParseNode; + context: Context; +}; + +export default ResolvedResult; diff --git a/app/imports/parser/types/ToStringFunction.ts b/app/imports/parser/types/ToStringFunction.ts new file mode 100644 index 00000000..883c3320 --- /dev/null +++ b/app/imports/parser/types/ToStringFunction.ts @@ -0,0 +1,11 @@ +import ParseNode from '/imports/parser/parseTree/ParseNode'; + +type ToStringFunction = { + (node: T, stringOthers: ToStringOthersFunction): string; +} + +export default ToStringFunction; + +type ToStringOthersFunction = { + (node: ParseNode): string; +} diff --git a/app/imports/parser/types/TraverseFunction.ts b/app/imports/parser/types/TraverseFunction.ts new file mode 100644 index 00000000..418a5e53 --- /dev/null +++ b/app/imports/parser/types/TraverseFunction.ts @@ -0,0 +1,15 @@ +import ParseNode from '/imports/parser/parseTree/ParseNode'; + +type TraverseFunction = { + ( + node: T, + fn: (node: ParseNode) => any, + traverseOthers: TraverseOthersFunction + ): any; +} + +export default TraverseFunction; + +type TraverseOthersFunction = { + (node: ParseNode, fn: (node: ParseNode) => any): any +} diff --git a/app/imports/server/action.js b/app/imports/server/action.js deleted file mode 100644 index e69de29b..00000000 diff --git a/app/imports/server/publications/singleCharacter.js b/app/imports/server/publications/singleCharacter.js index 26221815..13db4b26 100644 --- a/app/imports/server/publications/singleCharacter.js +++ b/app/imports/server/publications/singleCharacter.js @@ -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 diff --git a/app/package-lock.json b/app/package-lock.json index 5409c8fb..46533ff3 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -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", diff --git a/app/package.json b/app/package.json index 1dcad558..d51a02ac 100644 --- a/app/package.json +++ b/app/package.json @@ -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", diff --git a/app/server/main.js b/app/server/main.js index 03265bb9..3e1f8725 100644 --- a/app/server/main.js +++ b/app/server/main.js @@ -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';