Fixed failing tests, tested parser more
This commit is contained in:
@@ -40,8 +40,6 @@ export async function resolveCalculationNode(calculation, parseNode, scope, give
|
||||
const fn = calculation._parseLevel;
|
||||
const calculationScope = { ...calculation._localScope, ...scope };
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ var testProperties = [
|
||||
}],
|
||||
},
|
||||
uses: {
|
||||
calculation: 'nonExistantProperty + 7',
|
||||
calculation: 'nonExistentProperty + 7',
|
||||
},
|
||||
usesUsed: 5,
|
||||
left: 1,
|
||||
|
||||
@@ -8,7 +8,6 @@ export default async function () {
|
||||
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');
|
||||
|
||||
@@ -235,6 +235,7 @@ const ComputedOnlyActionSchema = createPropertySchema({
|
||||
optional: true,
|
||||
},
|
||||
uses: {
|
||||
parseLevel: 'reduce',
|
||||
type: 'computedOnlyField',
|
||||
optional: true,
|
||||
},
|
||||
|
||||
@@ -132,8 +132,8 @@ const parserFunctions: { [name: string]: ParserFunction } = {
|
||||
],
|
||||
arguments: ['parseNode'],
|
||||
resultType: 'parseNode',
|
||||
fn: function resolveFn(node) {
|
||||
const { result } = resolve('reduce', node, this.scope, this.context);
|
||||
fn: async function resolveFn(node) {
|
||||
const { result } = await resolve('reduce', node, this.scope, this.context);
|
||||
return result;
|
||||
}
|
||||
},
|
||||
@@ -268,7 +268,7 @@ const parserFunctions: { [name: string]: ParserFunction } = {
|
||||
}
|
||||
|
||||
function anyNumberOf(type) {
|
||||
const argumentArray = [type];
|
||||
const argumentArray: any = [type];
|
||||
argumentArray.anyLength = true;
|
||||
return argumentArray;
|
||||
}
|
||||
|
||||
139
app/imports/parser/parseTree/accessor.test.ts
Normal file
139
app/imports/parser/parseTree/accessor.test.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import inputProviderForTests from '/imports/api/engine/action/functions/inputProviderForTests.testFn';
|
||||
import { parse } from '/imports/parser/parser';
|
||||
import resolve from '/imports/parser/resolve';
|
||||
import toString from '/imports/parser/toString';
|
||||
import { assert } from 'chai';
|
||||
|
||||
describe('Accessor Node', function () {
|
||||
it('compiles', async function () {
|
||||
const callNode = parse('unknownVariable + knownVariable + 1');
|
||||
const scope = {
|
||||
knownVariable: { value: 7 },
|
||||
};
|
||||
const { result, context } = await resolve(
|
||||
'compile', callNode, scope, undefined, inputProviderForTests
|
||||
);
|
||||
assert.isEmpty(context.errors);
|
||||
assert.equal(
|
||||
toString(result),
|
||||
'unknownVariable + 8',
|
||||
'Only known variables should be substituted during compilation step'
|
||||
);
|
||||
});
|
||||
it('reduces', async function () {
|
||||
const callNode = parse('unknownVariable + knownVariable + 1');
|
||||
const scope = {
|
||||
knownVariable: { value: 7 },
|
||||
};
|
||||
const { result, context } = await resolve(
|
||||
'reduce', callNode, scope, undefined, inputProviderForTests
|
||||
);
|
||||
assert.isEmpty(context.errors);
|
||||
assert.equal(
|
||||
toString(result),
|
||||
'8',
|
||||
'All variables should be substituted during reduce step'
|
||||
);
|
||||
});
|
||||
it('marks undefined variables', async function () {
|
||||
const callNode = parse('unknownVariable');
|
||||
|
||||
// At compile step
|
||||
const { result: compileResult, context: compileContext } = await resolve(
|
||||
'compile', callNode, undefined, undefined, inputProviderForTests
|
||||
);
|
||||
assert.isEmpty(compileContext.errors, 'compiling unknown variables should not have errors');
|
||||
assert.deepEqual(
|
||||
compileResult, { parseType: 'accessor', name: 'unknownVariable', isUndefined: true },
|
||||
'Unknown variables should be marked as inUndefined in compile step'
|
||||
);
|
||||
|
||||
// At reduce step
|
||||
const { result: reduceResult, context: reduceContext } = await resolve(
|
||||
'reduce', callNode, undefined, undefined, inputProviderForTests
|
||||
);
|
||||
assert.isEmpty(reduceContext.errors, 'reducing unknown variables should not have errors');
|
||||
assert.deepEqual(
|
||||
reduceResult, { parseType: 'constant', value: 0, valueType: 'number', isUndefined: true },
|
||||
'Unknown variables should be marked as inUndefined in compile step'
|
||||
);
|
||||
});
|
||||
it('Does not mark isUndefined on known variables', async function () {
|
||||
const callNode = parse('knownVariable');
|
||||
const scope = {
|
||||
knownVariable: { value: 0 },
|
||||
};
|
||||
|
||||
// At compile step
|
||||
const { result: compileResult, context: compileContext } = await resolve(
|
||||
'compile', callNode, scope, undefined, inputProviderForTests
|
||||
);
|
||||
assert.isEmpty(compileContext.errors, 'compiling known variables should not have errors');
|
||||
assert.deepEqual(
|
||||
compileResult, { parseType: 'constant', value: 0, valueType: 'number' },
|
||||
'Known variables should not be marked as inUndefined in compile step'
|
||||
);
|
||||
|
||||
// At reduce step
|
||||
const { result: reduceResult, context: reduceContext } = await resolve(
|
||||
'reduce', callNode, scope, undefined, inputProviderForTests
|
||||
);
|
||||
assert.isEmpty(reduceContext.errors, 'reducing known variables should not have errors');
|
||||
assert.deepEqual(
|
||||
reduceResult, { parseType: 'constant', value: 0, valueType: 'number' },
|
||||
'Known variables should not be marked as inUndefined in compile step'
|
||||
);
|
||||
});
|
||||
it('handles .isUndefined on unknown variables', async function () {
|
||||
const callNode = parse('unknownVariable.isUndefined');
|
||||
const scope = {
|
||||
knownVariable: { value: 7 },
|
||||
};
|
||||
|
||||
// At compile step
|
||||
const { result: compileResult, context: compileContext } = await resolve(
|
||||
'compile', callNode, scope, undefined, inputProviderForTests
|
||||
);
|
||||
assert.isEmpty(compileContext.errors, 'compiling unknown variables should not have errors');
|
||||
assert.equal(
|
||||
toString(compileResult), 'true',
|
||||
'Unknown variables should be marked as inUndefined in compile step'
|
||||
);
|
||||
|
||||
// At reduce step
|
||||
const { result: reduceResult, context: reduceContext } = await resolve(
|
||||
'reduce', callNode, scope, undefined, inputProviderForTests
|
||||
);
|
||||
assert.isEmpty(reduceContext.errors, 'reducing unknown variables should not have errors');
|
||||
assert.equal(
|
||||
toString(reduceResult), 'true',
|
||||
'Unknown variables should be marked as inUndefined in reduce step'
|
||||
);
|
||||
});
|
||||
it('handles .isUndefined on known variables', async function () {
|
||||
const callNode = parse('knownVariable.isUndefined');
|
||||
const scope = {
|
||||
knownVariable: { value: 7 },
|
||||
};
|
||||
|
||||
// At compile step
|
||||
const { result: compileResult, context: compileContext } = await resolve(
|
||||
'compile', callNode, scope, undefined, inputProviderForTests
|
||||
);
|
||||
assert.isEmpty(compileContext.errors, 'compiling unknown variables should not have errors');
|
||||
assert.equal(
|
||||
toString(compileResult), 'false',
|
||||
'Unknown variables should not be marked as inUndefined in compile step'
|
||||
);
|
||||
|
||||
// At reduce step
|
||||
const { result: reduceResult, context: reduceContext } = await resolve(
|
||||
'reduce', callNode, scope, undefined, inputProviderForTests
|
||||
);
|
||||
assert.isEmpty(reduceContext.errors, 'reducing unknown variables should not have errors');
|
||||
assert.equal(
|
||||
toString(reduceResult), 'false',
|
||||
'Known variables should not be marked as inUndefined in reduce step'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -8,6 +8,7 @@ export type AccessorNode = {
|
||||
parseType: 'accessor' | 'symbol';
|
||||
path?: string[];
|
||||
name: string;
|
||||
isUndefined?: true,
|
||||
}
|
||||
|
||||
type AccessorFactory = {
|
||||
@@ -18,18 +19,29 @@ type AccessorFactory = {
|
||||
}
|
||||
|
||||
const accessor: AccessorFactory = {
|
||||
create({ name, path }: { name: string, path?: string[] }): AccessorNode {
|
||||
create({
|
||||
name, path, isUndefined
|
||||
}: {
|
||||
name: string, path?: string[], isUndefined?: true
|
||||
}): AccessorNode {
|
||||
return {
|
||||
parseType: 'accessor',
|
||||
path,
|
||||
name,
|
||||
...path && { path },
|
||||
...isUndefined && { isUndefined: true },
|
||||
};
|
||||
},
|
||||
async compile(node, scope, context) {
|
||||
let value = getFromScope(node.name, scope);
|
||||
// Get the value from the given path
|
||||
node.path?.forEach(name => {
|
||||
if (value === undefined) return;
|
||||
if (name === 'isUndefined') {
|
||||
value = value === undefined;
|
||||
return;
|
||||
}
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
value = value[name];
|
||||
});
|
||||
let valueType = getType(value);
|
||||
@@ -75,14 +87,17 @@ const accessor: AccessorFactory = {
|
||||
};
|
||||
}
|
||||
if (valueType === 'undefined') {
|
||||
// Undefined defaults to zero
|
||||
// We are only at compile, if it isn't defined in the scope, return a copy of the accessor
|
||||
return {
|
||||
result: constant.create({
|
||||
value: 0,
|
||||
result: accessor.create({
|
||||
name: node.name,
|
||||
path: node.path,
|
||||
isUndefined: true,
|
||||
}),
|
||||
context
|
||||
context,
|
||||
};
|
||||
}
|
||||
// The type being accessed isn't supported above, make an error and return a copy of the node
|
||||
context.error(`Accessing ${accessor.toString(node)} is not supported yet`);
|
||||
return {
|
||||
result: accessor.create({
|
||||
@@ -93,18 +108,19 @@ const accessor: AccessorFactory = {
|
||||
};
|
||||
},
|
||||
async reduce(node, scope, context, inputProvider, resolveOthers): Promise<ResolvedResult> {
|
||||
let { result } = await accessor.compile(node, scope, context, inputProvider, resolveOthers);
|
||||
({ result } = await resolveOthers('reduce', result, scope, context, inputProvider));
|
||||
if (result.parseType === 'accessor') {
|
||||
// First compile the accessor
|
||||
const { result } = await accessor.compile(node, scope, context, inputProvider, resolveOthers);
|
||||
// If compilation didn't find a suitable replacement, return 0
|
||||
if (result.parseType === 'accessor' && result.isUndefined) {
|
||||
return {
|
||||
result: constant.create({
|
||||
value: 0,
|
||||
isUndefined: true,
|
||||
}),
|
||||
context
|
||||
};
|
||||
} else {
|
||||
return { result, context };
|
||||
}
|
||||
return { result, context };
|
||||
},
|
||||
toString(node) {
|
||||
if (!node.path?.length) return `${node.name}`;
|
||||
|
||||
14
app/imports/parser/parseTree/call.test.ts
Normal file
14
app/imports/parser/parseTree/call.test.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import inputProviderForTests from '/imports/api/engine/action/functions/inputProviderForTests.testFn';
|
||||
import { parse } from '/imports/parser/parser';
|
||||
import resolve from '/imports/parser/resolve';
|
||||
import toString from '/imports/parser/toString';
|
||||
import { assert } from 'chai';
|
||||
|
||||
describe('Call Node', function () {
|
||||
it('compiles', async function () {
|
||||
const callNode = parse('min( unknownVariable, 1 + 2, 3d30 )');
|
||||
const { result, context } = await resolve('compile', callNode, undefined, undefined, inputProviderForTests);
|
||||
assert.isEmpty(context.errors)
|
||||
assert.equal(toString(result), 'min(unknownVariable, 3, 3d30)');
|
||||
});
|
||||
});
|
||||
@@ -108,7 +108,7 @@ const call: CallFactory = {
|
||||
|
||||
try {
|
||||
// Run the function
|
||||
const value = func.fn.apply({
|
||||
const value = await func.fn.apply({
|
||||
scope,
|
||||
context,
|
||||
}, mappedArgs);
|
||||
@@ -166,20 +166,22 @@ const call: CallFactory = {
|
||||
let failed = false;
|
||||
// Check that each argument is of the correct type
|
||||
resolvedArgs.forEach((node, index) => {
|
||||
let type;
|
||||
let expectedType;
|
||||
if (argumentsExpected.anyLength) {
|
||||
type = argumentsExpected[0];
|
||||
expectedType = argumentsExpected[0];
|
||||
} else {
|
||||
type = argumentsExpected[index];
|
||||
expectedType = argumentsExpected[index];
|
||||
}
|
||||
if (type === 'parseNode') return;
|
||||
if (expectedType === 'parseNode') return;
|
||||
if (
|
||||
node.parseType !== type
|
||||
&& node.parseType === 'constant'
|
||||
&& node.valueType !== type
|
||||
node.parseType !== expectedType
|
||||
|| (
|
||||
node.parseType === 'constant'
|
||||
&& node.valueType !== expectedType
|
||||
)
|
||||
) failed = true;
|
||||
if (failed && fn === 'reduce') {
|
||||
const typeName = typeof type === 'string' ? type : type.constructor.name;
|
||||
const typeName = typeof expectedType === 'string' ? expectedType : expectedType.constructor.name;
|
||||
const nodeName = node.parseType;
|
||||
context.error(`Incorrect arguments to ${callNode.functionName} function` +
|
||||
`expected ${typeName} got ${nodeName}`);
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||
import ResolveLevelFunction from '/imports/parser/types/ResolveLevelFunction';
|
||||
|
||||
export type ConstantValueType = number | string | boolean | undefined
|
||||
export type ConstantValueType = number | string | boolean
|
||||
|
||||
export type ConstantNode = {
|
||||
parseType: 'constant';
|
||||
value: ConstantValueType;
|
||||
// TODO replace all `constantNode.valueType` with `typeof constantNode.value`
|
||||
valueType: 'number' | 'string' | 'boolean' | 'undefined';
|
||||
valueType: 'number' | 'string' | 'boolean';
|
||||
isUndefined?: true;
|
||||
}
|
||||
|
||||
export type FiniteNumberConstantNode = {
|
||||
@@ -18,17 +19,18 @@ export type FiniteNumberConstantNode = {
|
||||
}
|
||||
|
||||
type ConstantFactory = {
|
||||
create({ value }: { value: ConstantValueType }): ConstantNode;
|
||||
create({ value, isUndefined }: { value: ConstantValueType, isUndefined?: true }): ConstantNode;
|
||||
compile: ResolveLevelFunction<ConstantNode>;
|
||||
toString(node: ConstantNode): string;
|
||||
}
|
||||
|
||||
const constant: ConstantFactory = {
|
||||
create({ value }): ConstantNode {
|
||||
create({ value, isUndefined }): ConstantNode {
|
||||
return {
|
||||
parseType: 'constant',
|
||||
valueType: typeof value as 'number' | 'string' | 'boolean' | 'undefined',
|
||||
valueType: typeof value as 'number' | 'string' | 'boolean',
|
||||
value,
|
||||
...isUndefined && { isUndefined: true }
|
||||
}
|
||||
},
|
||||
async compile(node, scope, context) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
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';
|
||||
@@ -14,8 +12,6 @@ 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,
|
||||
@@ -34,12 +30,4 @@ const factories = {
|
||||
unaryOperator,
|
||||
};
|
||||
|
||||
console.log('---------------------');
|
||||
console.log('---------------------');
|
||||
console.log('---------------------');
|
||||
console.log(factories.array);
|
||||
console.log('---------------------');
|
||||
console.log('---------------------');
|
||||
console.log('---------------------');
|
||||
|
||||
export default factories;
|
||||
|
||||
6
app/package-lock.json
generated
6
app/package-lock.json
generated
@@ -1422,6 +1422,12 @@
|
||||
"resolved": "https://registry.npmjs.org/@tozd/vue-observer-utils/-/vue-observer-utils-0.5.0.tgz",
|
||||
"integrity": "sha512-HeRxWFJB7FXcQigH2LvauiR0l7hA4qqBC6hK9rBeKf076Ew08C4lx3eo7/YmvADt3b8ZP1j+TN0pGCEhKYOhEA=="
|
||||
},
|
||||
"@types/chai": {
|
||||
"version": "4.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz",
|
||||
"integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/connect": {
|
||||
"version": "3.4.38",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"vuex": "^3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.11",
|
||||
"@types/mocha": "^10.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
|
||||
Reference in New Issue
Block a user