Fixed failing tests, tested parser more
This commit is contained in:
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;
|
||||
|
||||
Reference in New Issue
Block a user