Typescript all the parser things

This commit is contained in:
Thaum Rystra
2024-02-20 23:21:12 +02:00
parent 3ea492ee78
commit ac15512bc5
86 changed files with 926 additions and 718 deletions

View File

@@ -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 => {

View File

@@ -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 });
}
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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 || [];

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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');

View File

@@ -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');

View File

@@ -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 = [

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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');
}

View File

@@ -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,

View File

@@ -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');

View File

@@ -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) {

View File

@@ -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],
});
}
}
}
}