Fixed failing tests, tested parser more

This commit is contained in:
Thaum Rystra
2024-02-20 21:36:32 +02:00
parent ac15512bc5
commit b41d26b3ad
13 changed files with 211 additions and 45 deletions

View File

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

View File

@@ -57,7 +57,7 @@ var testProperties = [
}],
},
uses: {
calculation: 'nonExistantProperty + 7',
calculation: 'nonExistentProperty + 7',
},
usesUsed: 5,
left: 1,

View File

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

View File

@@ -235,6 +235,7 @@ const ComputedOnlyActionSchema = createPropertySchema({
optional: true,
},
uses: {
parseLevel: 'reduce',
type: 'computedOnlyField',
optional: true,
},

View File

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

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

View File

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

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

View File

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

View File

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

View File

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

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

View File

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