Add support for more depth in function calls
It allow pipes to be used in nested functions. Before, pipes were added
to a variable before variable content was evaluated/compiled by
another function. This commit ensures that the commits are evaluted in
expected order.
The issue is solved by stopping precompiling functions. It makes code
less complex. It adds to compile time of the script file but nothing
noticable and something optimizable.
The problem was that the call trees we're not executed in expected
order. E.g. let's say we have functionA that outputs something like
"Hello {{ $name| pipe }}", and we have function B calling with "name:
dear {{ $firstName}}", and at last we have a script that's calling
function B with "firstName: undergroundwires". Before, expressions were
evaluated directly, meaning that function A would become:
"Hello Dear {{ $firstName}}", as you see the pipe in function A
is lost here after being applied to function B and not reaching
$firstTime input value. Parsing expressions in the end allows for pipes
etc. to not get lost.
The commit also does necessary name refactorings and folder refactorings
to reflect logical changes. `FunctionCompiler` is renamed to
`SharedFunctionsParser` as precompiling is removed and it just simply
parses now. `/FunctionCall/` is moved to `/Function/Call`.
Finally, it improves documentation and adds more tests.
This commit is contained in:
@@ -45,9 +45,11 @@
|
|||||||
### `Script`
|
### `Script`
|
||||||
|
|
||||||
- Script represents a single tweak.
|
- Script represents a single tweak.
|
||||||
- A script must include either:
|
- A script can be of two different types (just like [functions](#function)):
|
||||||
- A `code` and `revertCode`
|
1. Inline script; a script with an inline code
|
||||||
- Or `call` to call YAML-defined functions
|
- Must define `code` property and optionally `revertCode` but not `call`
|
||||||
|
2. Caller script; a script that calls other functions
|
||||||
|
- Must define `call` property but not `code` or `revertCode`
|
||||||
- 🙏 For any new script, please add `revertCode` and `docs` values if possible.
|
- 🙏 For any new script, please add `revertCode` and `docs` values if possible.
|
||||||
|
|
||||||
#### `Script` syntax
|
#### `Script` syntax
|
||||||
@@ -98,12 +100,18 @@
|
|||||||
appName: Microsoft.WindowsFeedbackHub
|
appName: Microsoft.WindowsFeedbackHub
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- 💡 [Expressions (templating)](./templating.md#expressions) can be used as parameter value
|
||||||
|
|
||||||
### `Function`
|
### `Function`
|
||||||
|
|
||||||
- Functions allow re-usable code throughout the defined scripts
|
- Functions allow re-usable code throughout the defined scripts.
|
||||||
- Functions are templates compiled by privacy.sexy and uses special expression expressions
|
- Functions are templates compiled by privacy.sexy and uses special expression expressions.
|
||||||
- Functions can call other functions by defining `call` property instead of `code`
|
- A function can be of two different types (just like [scripts](#script)):
|
||||||
- 👀 Read more on [Templating](./templating.md) for function expressions and [example usages](./templating.md#parameter-substitution)
|
1. Inline function: a function with an inline code.
|
||||||
|
- Must define `code` property and optionally `revertCode` but not `call`.
|
||||||
|
2. Caller function: a function that calls other functions.
|
||||||
|
- Must define `call` property but not `code` or `revertCode`.
|
||||||
|
- 👀 Read more on [Templating](./templating.md) for function expressions and [example usages](./templating.md#parameter-substitution).
|
||||||
|
|
||||||
#### `Function` syntax
|
#### `Function` syntax
|
||||||
|
|
||||||
@@ -114,18 +122,20 @@
|
|||||||
- ❗ Function names must be unique
|
- ❗ Function names must be unique
|
||||||
- `parameters`: `[` ***[`FunctionParameter`](#FunctionParameter)*** `, ... ]`
|
- `parameters`: `[` ***[`FunctionParameter`](#FunctionParameter)*** `, ... ]`
|
||||||
- List of parameters that function code refers to.
|
- List of parameters that function code refers to.
|
||||||
- ❗ Must be defined to be able use in [`FunctionCall`](#functioncall) or [expressions](./templating.md#expressions)
|
- ❗ Must be defined to be able use in [`FunctionCall`](#functioncall) or [expressions (templating)](./templating.md#expressions)
|
||||||
`code`: *`string`* (**required** if `call` is undefined)
|
`code`: *`string`* (**required** if `call` is undefined)
|
||||||
- Batch file commands that will be executed
|
- Batch file commands that will be executed
|
||||||
|
- 💡 [Expressions (templating)](./templating.md#expressions) can be used in its value
|
||||||
- 💡 If defined, best practice to also define `revertCode`
|
- 💡 If defined, best practice to also define `revertCode`
|
||||||
- ❗ If not defined `call` must be defined
|
- ❗ If not defined `call` must be defined
|
||||||
- `revertCode`: *`string`*
|
- `revertCode`: *`string`*
|
||||||
- Code that'll undo the change done by `code` property.
|
- Code that'll undo the change done by `code` property.
|
||||||
- E.g. let's say `code` sets an environment variable as `setx POWERSHELL_TELEMETRY_OPTOUT 1`
|
- E.g. let's say `code` sets an environment variable as `setx POWERSHELL_TELEMETRY_OPTOUT 1`
|
||||||
- then `revertCode` should be doing `setx POWERSHELL_TELEMETRY_OPTOUT 0`
|
- then `revertCode` should be doing `setx POWERSHELL_TELEMETRY_OPTOUT 0`
|
||||||
|
- 💡 [Expressions (templating)](./templating.md#expressions) can be used in code
|
||||||
- `call`: ***[`FunctionCall`](#FunctionCall)*** | `[` ***[`FunctionCall`](#FunctionCall)*** `, ... ]` (may be **required**)
|
- `call`: ***[`FunctionCall`](#FunctionCall)*** | `[` ***[`FunctionCall`](#FunctionCall)*** `, ... ]` (may be **required**)
|
||||||
- A shared function or sequence of functions to call (called in order)
|
- A shared function or sequence of functions to call (called in order)
|
||||||
- The parameter values that are sent can use [expressions](./templating.md#expressions)
|
- The parameter values that are sent can use [expressions (templating)](./templating.md#expressions)
|
||||||
- ❗ If not defined `code` must be defined
|
- ❗ If not defined `code` must be defined
|
||||||
|
|
||||||
### `FunctionParameter`
|
### `FunctionParameter`
|
||||||
@@ -137,7 +147,7 @@
|
|||||||
|
|
||||||
- `name`: *`string`* (**required**)
|
- `name`: *`string`* (**required**)
|
||||||
- Name of the parameters that the function has.
|
- Name of the parameters that the function has.
|
||||||
- Parameter names must be defined to be used in [expressions](./templating.md#expressions).
|
- Parameter names must be defined to be used in [expressions (templating)](./templating.md#expressions).
|
||||||
- ❗ Parameter names must be unique and include alphanumeric characters only.
|
- ❗ Parameter names must be unique and include alphanumeric characters only.
|
||||||
- `optional`: *`boolean`* (default: `false`)
|
- `optional`: *`boolean`* (default: `false`)
|
||||||
- Specifies whether the caller [Script](#script) must provide any value for the parameter.
|
- Specifies whether the caller [Script](#script) must provide any value for the parameter.
|
||||||
|
|||||||
@@ -10,8 +10,9 @@
|
|||||||
|
|
||||||
- Expressions in the language are defined inside mustaches (double brackets, `{{` and `}}`).
|
- Expressions in the language are defined inside mustaches (double brackets, `{{` and `}}`).
|
||||||
- Expression syntax is inspired mainly by [Go Templates](https://pkg.go.dev/text/template).
|
- Expression syntax is inspired mainly by [Go Templates](https://pkg.go.dev/text/template).
|
||||||
|
- Expressions are used in and enabled by functions where they can be used.
|
||||||
## Syntax
|
- In script definition parts of a function, see [`Function`](./collection-files.md#Function).
|
||||||
|
- When doing a call as argument values, see [`FunctionCall`](./collection-files.md#Function).
|
||||||
|
|
||||||
### Parameter substitution
|
### Parameter substitution
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { ExpressionPosition } from './ExpressionPosition';
|
import { ExpressionPosition } from './ExpressionPosition';
|
||||||
import { IExpression } from './IExpression';
|
import { IExpression } from './IExpression';
|
||||||
import { IReadOnlyFunctionCallArgumentCollection } from '../../FunctionCall/Argument/IFunctionCallArgumentCollection';
|
import { IReadOnlyFunctionCallArgumentCollection } from '../../Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
import { IReadOnlyFunctionParameterCollection } from '../../Function/Parameter/IFunctionParameterCollection';
|
import { IReadOnlyFunctionParameterCollection } from '../../Function/Parameter/IFunctionParameterCollection';
|
||||||
import { FunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection';
|
import { FunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection';
|
||||||
import { FunctionCallArgumentCollection } from '../../FunctionCall/Argument/FunctionCallArgumentCollection';
|
import { FunctionCallArgumentCollection } from '../../Function/Call/Argument/FunctionCallArgumentCollection';
|
||||||
import { IExpressionEvaluationContext } from './ExpressionEvaluationContext';
|
import { IExpressionEvaluationContext } from './ExpressionEvaluationContext';
|
||||||
import { ExpressionEvaluationContext } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
|
import { ExpressionEvaluationContext } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IReadOnlyFunctionCallArgumentCollection } from '../../FunctionCall/Argument/IFunctionCallArgumentCollection';
|
import { IReadOnlyFunctionCallArgumentCollection } from '../../Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
import { IPipelineCompiler } from '../Pipes/IPipelineCompiler';
|
import { IPipelineCompiler } from '../Pipes/IPipelineCompiler';
|
||||||
import { PipelineCompiler } from '../Pipes/PipelineCompiler';
|
import { PipelineCompiler } from '../Pipes/PipelineCompiler';
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { IExpressionsCompiler } from './IExpressionsCompiler';
|
|||||||
import { IExpression } from './Expression/IExpression';
|
import { IExpression } from './Expression/IExpression';
|
||||||
import { IExpressionParser } from './Parser/IExpressionParser';
|
import { IExpressionParser } from './Parser/IExpressionParser';
|
||||||
import { CompositeExpressionParser } from './Parser/CompositeExpressionParser';
|
import { CompositeExpressionParser } from './Parser/CompositeExpressionParser';
|
||||||
import { IReadOnlyFunctionCallArgumentCollection } from '../FunctionCall/Argument/IFunctionCallArgumentCollection';
|
import { IReadOnlyFunctionCallArgumentCollection } from '../Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
import { ExpressionEvaluationContext } from './Expression/ExpressionEvaluationContext';
|
import { ExpressionEvaluationContext } from './Expression/ExpressionEvaluationContext';
|
||||||
import { IExpressionEvaluationContext } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
|
import { IExpressionEvaluationContext } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IReadOnlyFunctionCallArgumentCollection } from '../FunctionCall/Argument/IFunctionCallArgumentCollection';
|
import { IReadOnlyFunctionCallArgumentCollection } from '../Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
|
|
||||||
export interface IExpressionsCompiler {
|
export interface IExpressionsCompiler {
|
||||||
compileExpressions(
|
compileExpressions(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IFunctionCallArgument } from './IFunctionCallArgument';
|
import { IFunctionCallArgument } from './IFunctionCallArgument';
|
||||||
import { ensureValidParameterName } from '../../ParameterNameValidator';
|
import { ensureValidParameterName } from '../../Shared/ParameterNameValidator';
|
||||||
|
|
||||||
export class FunctionCallArgument implements IFunctionCallArgument {
|
export class FunctionCallArgument implements IFunctionCallArgument {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { ICompiledCode } from './ICompiledCode';
|
||||||
|
import { ISharedFunctionCollection } from '../../ISharedFunctionCollection';
|
||||||
|
import { IFunctionCallCompiler } from './IFunctionCallCompiler';
|
||||||
|
import { IExpressionsCompiler } from '../../../Expressions/IExpressionsCompiler';
|
||||||
|
import { ExpressionsCompiler } from '../../../Expressions/ExpressionsCompiler';
|
||||||
|
import { ISharedFunction, IFunctionCode } from '../../ISharedFunction';
|
||||||
|
import { IFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/IFunctionCall';
|
||||||
|
import { FunctionCall } from '../FunctionCall';
|
||||||
|
import { FunctionCallArgumentCollection } from '../Argument/FunctionCallArgumentCollection';
|
||||||
|
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||||
|
|
||||||
|
export class FunctionCallCompiler implements IFunctionCallCompiler {
|
||||||
|
public static readonly instance: IFunctionCallCompiler = new FunctionCallCompiler();
|
||||||
|
|
||||||
|
protected constructor(
|
||||||
|
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public compileCall(
|
||||||
|
calls: IFunctionCall[],
|
||||||
|
functions: ISharedFunctionCollection): ICompiledCode {
|
||||||
|
if (!functions) { throw new Error('undefined functions'); }
|
||||||
|
if (!calls) { throw new Error('undefined calls'); }
|
||||||
|
if (calls.some((f) => !f)) { throw new Error('undefined function call'); }
|
||||||
|
const context: ICompilationContext = {
|
||||||
|
allFunctions: functions,
|
||||||
|
callSequence: calls,
|
||||||
|
expressionsCompiler: this.expressionsCompiler,
|
||||||
|
};
|
||||||
|
const code = compileCallSequence(context);
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ICompilationContext {
|
||||||
|
allFunctions: ISharedFunctionCollection;
|
||||||
|
callSequence: readonly IFunctionCall[];
|
||||||
|
expressionsCompiler: IExpressionsCompiler;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ICompiledFunctionCall {
|
||||||
|
readonly code: string;
|
||||||
|
readonly revertCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileCallSequence(context: ICompilationContext): ICompiledFunctionCall {
|
||||||
|
const compiledFunctions = new Array<ICompiledFunctionCall>();
|
||||||
|
for (const call of context.callSequence) {
|
||||||
|
const compiledCode = compileSingleCall(call, context);
|
||||||
|
compiledFunctions.push(...compiledCode);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
code: merge(compiledFunctions.map((f) => f.code)),
|
||||||
|
revertCode: merge(compiledFunctions.map((f) => f.revertCode)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileSingleCall(call: IFunctionCall, context: ICompilationContext): ICompiledFunctionCall[] {
|
||||||
|
const func = context.allFunctions.getFunctionByName(call.functionName);
|
||||||
|
ensureThatCallArgumentsExistInParameterDefinition(func, call.args);
|
||||||
|
if (func.body.code) { // Function with inline code
|
||||||
|
const compiledCode = compileCode(func.body.code, call.args, context.expressionsCompiler);
|
||||||
|
return [ compiledCode ];
|
||||||
|
} else { // Function with inner calls
|
||||||
|
return func.body.calls
|
||||||
|
.map((innerCall) => {
|
||||||
|
const compiledArgs = compileArgs(innerCall.args, call.args, context.expressionsCompiler);
|
||||||
|
const compiledCall = new FunctionCall(innerCall.functionName, compiledArgs);
|
||||||
|
return compileSingleCall(compiledCall, context);
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileCode(
|
||||||
|
code: IFunctionCode,
|
||||||
|
args: IReadOnlyFunctionCallArgumentCollection,
|
||||||
|
compiler: IExpressionsCompiler): ICompiledFunctionCall {
|
||||||
|
return {
|
||||||
|
code: compiler.compileExpressions(code.do, args),
|
||||||
|
revertCode: compiler.compileExpressions(code.revert, args),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileArgs(
|
||||||
|
argsToCompile: IReadOnlyFunctionCallArgumentCollection,
|
||||||
|
args: IReadOnlyFunctionCallArgumentCollection,
|
||||||
|
compiler: IExpressionsCompiler,
|
||||||
|
): IReadOnlyFunctionCallArgumentCollection {
|
||||||
|
const compiledArgs = new FunctionCallArgumentCollection();
|
||||||
|
for (const parameterName of argsToCompile.getAllParameterNames()) {
|
||||||
|
const argumentValue = argsToCompile.getArgument(parameterName).argumentValue;
|
||||||
|
const compiledValue = compiler.compileExpressions(argumentValue, args);
|
||||||
|
const newArgument = new FunctionCallArgument(parameterName, compiledValue);
|
||||||
|
compiledArgs.addArgument(newArgument);
|
||||||
|
}
|
||||||
|
return compiledArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
function merge(codeParts: readonly string[]): string {
|
||||||
|
return codeParts
|
||||||
|
.filter((part) => part?.length > 0)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureThatCallArgumentsExistInParameterDefinition(
|
||||||
|
func: ISharedFunction,
|
||||||
|
args: IReadOnlyFunctionCallArgumentCollection): void {
|
||||||
|
const callArgumentNames = args.getAllParameterNames();
|
||||||
|
const functionParameterNames = func.parameters.all.map((param) => param.name) || [];
|
||||||
|
const unexpectedParameters = findUnexpectedParameters(callArgumentNames, functionParameterNames);
|
||||||
|
throwIfNotEmpty(func.name, unexpectedParameters, functionParameterNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findUnexpectedParameters(
|
||||||
|
callArgumentNames: string[],
|
||||||
|
functionParameterNames: string[]): string[] {
|
||||||
|
if (!callArgumentNames.length && !functionParameterNames.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return callArgumentNames
|
||||||
|
.filter((callParam) => !functionParameterNames.includes(callParam));
|
||||||
|
}
|
||||||
|
|
||||||
|
function throwIfNotEmpty(
|
||||||
|
functionName: string,
|
||||||
|
unexpectedParameters: string[],
|
||||||
|
expectedParameters: string[]) {
|
||||||
|
if (!unexpectedParameters.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`Function "${functionName}" has unexpected parameter(s) provided: ` +
|
||||||
|
`"${unexpectedParameters.join('", "')}"` +
|
||||||
|
'. Expected parameter(s): ' +
|
||||||
|
(expectedParameters.length ? `"${expectedParameters.join('", "')}"` : 'none'),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { ICompiledCode } from './ICompiledCode';
|
||||||
|
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
||||||
|
import { IFunctionCall } from '../IFunctionCall';
|
||||||
|
|
||||||
|
export interface IFunctionCallCompiler {
|
||||||
|
compileCall(
|
||||||
|
calls: IFunctionCall[],
|
||||||
|
functions: ISharedFunctionCollection): ICompiledCode;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { FunctionCallData, FunctionCallsData } from 'js-yaml-loader!@/*';
|
||||||
|
import { IFunctionCall } from './IFunctionCall';
|
||||||
|
import { FunctionCallArgumentCollection } from './Argument/FunctionCallArgumentCollection';
|
||||||
|
import { FunctionCallArgument } from './Argument/FunctionCallArgument';
|
||||||
|
import { FunctionCall } from './FunctionCall';
|
||||||
|
|
||||||
|
export function parseFunctionCalls(calls: FunctionCallsData): IFunctionCall[] {
|
||||||
|
if (!calls) {
|
||||||
|
throw new Error('undefined call data');
|
||||||
|
}
|
||||||
|
const sequence = getCallSequence(calls);
|
||||||
|
return sequence.map((call) => parseFunctionCall(call));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCallSequence(calls: FunctionCallsData): FunctionCallData[] {
|
||||||
|
if (typeof calls !== 'object') {
|
||||||
|
throw new Error('called function(s) must be an object');
|
||||||
|
}
|
||||||
|
if (calls instanceof Array) {
|
||||||
|
return calls as FunctionCallData[];
|
||||||
|
}
|
||||||
|
return [ calls as FunctionCallData ];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFunctionCall(call: FunctionCallData): IFunctionCall {
|
||||||
|
if (!call) {
|
||||||
|
throw new Error(`undefined function call`);
|
||||||
|
}
|
||||||
|
const args = new FunctionCallArgumentCollection();
|
||||||
|
for (const parameterName of Object.keys(call.parameters || {})) {
|
||||||
|
const arg = new FunctionCallArgument(parameterName, call.parameters[parameterName]);
|
||||||
|
args.addArgument(arg);
|
||||||
|
}
|
||||||
|
return new FunctionCall(call.function, args);
|
||||||
|
}
|
||||||
@@ -1,8 +1,24 @@
|
|||||||
import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
||||||
|
import { IFunctionCall } from '../Function/Call/IFunctionCall';
|
||||||
|
|
||||||
export interface ISharedFunction {
|
export interface ISharedFunction {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly parameters: IReadOnlyFunctionParameterCollection;
|
readonly parameters: IReadOnlyFunctionParameterCollection;
|
||||||
readonly code: string;
|
readonly body: ISharedFunctionBody;
|
||||||
readonly revertCode?: string;
|
}
|
||||||
|
|
||||||
|
export interface ISharedFunctionBody {
|
||||||
|
readonly type: FunctionBodyType;
|
||||||
|
readonly code: IFunctionCode;
|
||||||
|
readonly calls: readonly IFunctionCall[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum FunctionBodyType {
|
||||||
|
Code,
|
||||||
|
Calls,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFunctionCode {
|
||||||
|
readonly do: string;
|
||||||
|
readonly revert?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { FunctionData } from 'js-yaml-loader!@/*';
|
import { FunctionData } from 'js-yaml-loader!@/*';
|
||||||
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
||||||
|
|
||||||
export interface IFunctionCompiler {
|
export interface ISharedFunctionsParser {
|
||||||
compileFunctions(functions: readonly FunctionData[]): ISharedFunctionCollection;
|
parseFunctions(functions: readonly FunctionData[]): ISharedFunctionCollection;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IFunctionParameter } from './IFunctionParameter';
|
import { IFunctionParameter } from './IFunctionParameter';
|
||||||
import { ensureValidParameterName } from '../../ParameterNameValidator';
|
import { ensureValidParameterName } from '../Shared/ParameterNameValidator';
|
||||||
|
|
||||||
export class FunctionParameter implements IFunctionParameter {
|
export class FunctionParameter implements IFunctionParameter {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -1,15 +1,49 @@
|
|||||||
import { ISharedFunction } from './ISharedFunction';
|
import { IFunctionCall } from '../Function/Call/IFunctionCall';
|
||||||
|
import { FunctionBodyType, IFunctionCode, ISharedFunction, ISharedFunctionBody } from './ISharedFunction';
|
||||||
import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
||||||
|
|
||||||
export class SharedFunction implements ISharedFunction {
|
export function createCallerFunction(
|
||||||
|
name: string,
|
||||||
|
parameters: IReadOnlyFunctionParameterCollection,
|
||||||
|
callSequence: readonly IFunctionCall[]): ISharedFunction {
|
||||||
|
if (!callSequence) {
|
||||||
|
throw new Error(`undefined call sequence in function "${name}"`);
|
||||||
|
}
|
||||||
|
if (!callSequence.length) {
|
||||||
|
throw new Error(`empty call sequence in function "${name}"`);
|
||||||
|
}
|
||||||
|
return new SharedFunction(name, parameters, callSequence, FunctionBodyType.Calls);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createFunctionWithInlineCode(
|
||||||
|
name: string,
|
||||||
|
parameters: IReadOnlyFunctionParameterCollection,
|
||||||
|
code: string,
|
||||||
|
revertCode?: string): ISharedFunction {
|
||||||
|
if (!code) {
|
||||||
|
throw new Error(`undefined code in function "${name}"`);
|
||||||
|
}
|
||||||
|
const content: IFunctionCode = {
|
||||||
|
do: code,
|
||||||
|
revert: revertCode,
|
||||||
|
};
|
||||||
|
return new SharedFunction(name, parameters, content, FunctionBodyType.Code);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SharedFunction implements ISharedFunction {
|
||||||
|
public readonly body: ISharedFunctionBody;
|
||||||
constructor(
|
constructor(
|
||||||
public readonly name: string,
|
public readonly name: string,
|
||||||
public readonly parameters: IReadOnlyFunctionParameterCollection,
|
public readonly parameters: IReadOnlyFunctionParameterCollection,
|
||||||
public readonly code: string,
|
content: IFunctionCode | readonly IFunctionCall[],
|
||||||
public readonly revertCode?: string,
|
bodyType: FunctionBodyType,
|
||||||
) {
|
) {
|
||||||
if (!name) { throw new Error('undefined function name'); }
|
if (!name) { throw new Error('undefined function name'); }
|
||||||
if (!code) { throw new Error(`undefined function ("${name}") code`); }
|
|
||||||
if (!parameters) { throw new Error(`undefined parameters`); }
|
if (!parameters) { throw new Error(`undefined parameters`); }
|
||||||
|
this.body = {
|
||||||
|
type: bodyType,
|
||||||
|
code: bodyType === FunctionBodyType.Code ? content as IFunctionCode : undefined,
|
||||||
|
calls: bodyType === FunctionBodyType.Calls ? content as readonly IFunctionCall[] : undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export class SharedFunctionCollection implements ISharedFunctionCollection {
|
|||||||
|
|
||||||
public addFunction(func: ISharedFunction): void {
|
public addFunction(func: ISharedFunction): void {
|
||||||
if (!func) { throw new Error('undefined function'); }
|
if (!func) { throw new Error('undefined function'); }
|
||||||
if (this.functionsByName.has(func.name)) {
|
if (this.has(func.name)) {
|
||||||
throw new Error(`function with name ${func.name} already exists`);
|
throw new Error(`function with name ${func.name} already exists`);
|
||||||
}
|
}
|
||||||
this.functionsByName.set(func.name, func);
|
this.functionsByName.set(func.name, func);
|
||||||
@@ -20,4 +20,8 @@ export class SharedFunctionCollection implements ISharedFunctionCollection {
|
|||||||
}
|
}
|
||||||
return func;
|
return func;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private has(functionName: string) {
|
||||||
|
return this.functionsByName.has(functionName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,42 @@
|
|||||||
import { FunctionData, InstructionHolder } from 'js-yaml-loader!@/*';
|
import { FunctionData, InstructionHolder } from 'js-yaml-loader!@/*';
|
||||||
import { SharedFunction } from './SharedFunction';
|
import { createFunctionWithInlineCode, createCallerFunction } from './SharedFunction';
|
||||||
import { SharedFunctionCollection } from './SharedFunctionCollection';
|
import { SharedFunctionCollection } from './SharedFunctionCollection';
|
||||||
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
||||||
import { IFunctionCompiler } from './IFunctionCompiler';
|
import { ISharedFunctionsParser } from './ISharedFunctionsParser';
|
||||||
import { IFunctionCallCompiler } from '../FunctionCall/IFunctionCallCompiler';
|
|
||||||
import { FunctionCallCompiler } from '../FunctionCall/FunctionCallCompiler';
|
|
||||||
import { FunctionParameter } from './Parameter/FunctionParameter';
|
import { FunctionParameter } from './Parameter/FunctionParameter';
|
||||||
import { FunctionParameterCollection } from './Parameter/FunctionParameterCollection';
|
import { FunctionParameterCollection } from './Parameter/FunctionParameterCollection';
|
||||||
import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
||||||
|
import { ISharedFunction } from './ISharedFunction';
|
||||||
|
import { parseFunctionCalls } from './Call/FunctionCallParser';
|
||||||
|
|
||||||
export class FunctionCompiler implements IFunctionCompiler {
|
export class SharedFunctionsParser implements ISharedFunctionsParser {
|
||||||
public static readonly instance: IFunctionCompiler = new FunctionCompiler();
|
public static readonly instance: ISharedFunctionsParser = new SharedFunctionsParser();
|
||||||
protected constructor(
|
public parseFunctions(
|
||||||
private readonly functionCallCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance) {
|
functions: readonly FunctionData[]): ISharedFunctionCollection {
|
||||||
}
|
|
||||||
public compileFunctions(functions: readonly FunctionData[]): ISharedFunctionCollection {
|
|
||||||
const collection = new SharedFunctionCollection();
|
const collection = new SharedFunctionCollection();
|
||||||
if (!functions || !functions.length) {
|
if (!functions || !functions.length) {
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
ensureValidFunctions(functions);
|
ensureValidFunctions(functions);
|
||||||
functions
|
for (const func of functions) {
|
||||||
.filter((func) => hasCode(func))
|
const sharedFunction = parseFunction(func);
|
||||||
.forEach((func) => {
|
collection.addFunction(sharedFunction);
|
||||||
const parameters = parseParameters(func);
|
}
|
||||||
const shared = new SharedFunction(func.name, parameters, func.code, func.revertCode);
|
|
||||||
collection.addFunction(shared);
|
|
||||||
});
|
|
||||||
functions
|
|
||||||
.filter((func) => hasCall(func))
|
|
||||||
.forEach((func) => {
|
|
||||||
const parameters = parseParameters(func);
|
|
||||||
const code = this.functionCallCompiler.compileCall(func.call, collection);
|
|
||||||
const shared = new SharedFunction(func.name, parameters, code.code, code.revertCode);
|
|
||||||
collection.addFunction(shared);
|
|
||||||
});
|
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseFunction(data: FunctionData): ISharedFunction {
|
||||||
|
const name = data.name;
|
||||||
|
const parameters = parseParameters(data);
|
||||||
|
if (hasCode(data)) {
|
||||||
|
return createFunctionWithInlineCode(name, parameters, data.code, data.revertCode);
|
||||||
|
} else { // has call
|
||||||
|
const calls = parseFunctionCalls(data.call);
|
||||||
|
return createCallerFunction(name, parameters, calls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function parseParameters(data: FunctionData): IReadOnlyFunctionParameterCollection {
|
function parseParameters(data: FunctionData): IReadOnlyFunctionParameterCollection {
|
||||||
const parameters = new FunctionParameterCollection();
|
const parameters = new FunctionParameterCollection();
|
||||||
if (!data.parameters) {
|
if (!data.parameters) {
|
||||||
@@ -64,7 +62,6 @@ function hasCall(data: FunctionData): boolean {
|
|||||||
return Boolean(data.call);
|
return Boolean(data.call);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function ensureValidFunctions(functions: readonly FunctionData[]) {
|
function ensureValidFunctions(functions: readonly FunctionData[]) {
|
||||||
ensureNoUndefinedItem(functions);
|
ensureNoUndefinedItem(functions);
|
||||||
ensureNoDuplicatesInFunctionNames(functions);
|
ensureNoDuplicatesInFunctionNames(functions);
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
import { FunctionCallData, ScriptFunctionCallData } from 'js-yaml-loader!@/*';
|
|
||||||
import { ICompiledCode } from './ICompiledCode';
|
|
||||||
import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection';
|
|
||||||
import { IFunctionCallCompiler } from './IFunctionCallCompiler';
|
|
||||||
import { IExpressionsCompiler } from '../Expressions/IExpressionsCompiler';
|
|
||||||
import { ExpressionsCompiler } from '../Expressions/ExpressionsCompiler';
|
|
||||||
import { ISharedFunction } from '../Function/ISharedFunction';
|
|
||||||
import { IFunctionCall } from './IFunctionCall';
|
|
||||||
import { FunctionCall } from './FunctionCall';
|
|
||||||
import { FunctionCallArgument } from './Argument/FunctionCallArgument';
|
|
||||||
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
|
|
||||||
import { FunctionCallArgumentCollection } from './Argument/FunctionCallArgumentCollection';
|
|
||||||
|
|
||||||
export class FunctionCallCompiler implements IFunctionCallCompiler {
|
|
||||||
public static readonly instance: IFunctionCallCompiler = new FunctionCallCompiler();
|
|
||||||
|
|
||||||
protected constructor(
|
|
||||||
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler()) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public compileCall(
|
|
||||||
call: ScriptFunctionCallData,
|
|
||||||
functions: ISharedFunctionCollection): ICompiledCode {
|
|
||||||
if (!functions) { throw new Error('undefined functions'); }
|
|
||||||
if (!call) { throw new Error('undefined call'); }
|
|
||||||
const compiledFunctions = new Array<ICompiledFunction>();
|
|
||||||
const callSequence = getCallSequence(call);
|
|
||||||
for (const currentCall of callSequence) {
|
|
||||||
const functionCall = parseFunctionCall(currentCall);
|
|
||||||
const sharedFunction = functions.getFunctionByName(functionCall.functionName);
|
|
||||||
ensureThatCallArgumentsExistInParameterDefinition(sharedFunction, functionCall.args);
|
|
||||||
const compiledFunction = compileCode(sharedFunction, functionCall.args, this.expressionsCompiler);
|
|
||||||
compiledFunctions.push(compiledFunction);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
code: merge(compiledFunctions.map((f) => f.code)),
|
|
||||||
revertCode: merge(compiledFunctions.map((f) => f.revertCode)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function merge(codeParts: readonly string[]): string {
|
|
||||||
return codeParts
|
|
||||||
.filter((part) => part?.length > 0)
|
|
||||||
.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ICompiledFunction {
|
|
||||||
readonly code: string;
|
|
||||||
readonly revertCode: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function compileCode(
|
|
||||||
func: ISharedFunction,
|
|
||||||
args: IReadOnlyFunctionCallArgumentCollection,
|
|
||||||
compiler: IExpressionsCompiler): ICompiledFunction {
|
|
||||||
return {
|
|
||||||
code: compiler.compileExpressions(func.code, args),
|
|
||||||
revertCode: compiler.compileExpressions(func.revertCode, args),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCallSequence(call: ScriptFunctionCallData): FunctionCallData[] {
|
|
||||||
if (typeof call !== 'object') {
|
|
||||||
throw new Error('called function(s) must be an object');
|
|
||||||
}
|
|
||||||
if (call instanceof Array) {
|
|
||||||
return call as FunctionCallData[];
|
|
||||||
}
|
|
||||||
return [ call as FunctionCallData ];
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseFunctionCall(call: FunctionCallData): IFunctionCall {
|
|
||||||
if (!call) {
|
|
||||||
throw new Error(`undefined function call`);
|
|
||||||
}
|
|
||||||
const args = new FunctionCallArgumentCollection();
|
|
||||||
for (const parameterName of Object.keys(call.parameters || {})) {
|
|
||||||
const arg = new FunctionCallArgument(parameterName, call.parameters[parameterName]);
|
|
||||||
args.addArgument(arg);
|
|
||||||
}
|
|
||||||
return new FunctionCall(call.function, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureThatCallArgumentsExistInParameterDefinition(
|
|
||||||
func: ISharedFunction,
|
|
||||||
args: IReadOnlyFunctionCallArgumentCollection): void {
|
|
||||||
const callArgumentNames = args.getAllParameterNames();
|
|
||||||
const functionParameterNames = func.parameters.all.map((param) => param.name) || [];
|
|
||||||
if (!callArgumentNames.length && !functionParameterNames.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const parametersOutsideFunction = callArgumentNames
|
|
||||||
.filter((callParam) => !functionParameterNames.includes(callParam));
|
|
||||||
if (parametersOutsideFunction.length) {
|
|
||||||
throw new Error(
|
|
||||||
`function "${func.name}" has unexpected parameter(s) provided:` +
|
|
||||||
`"${parametersOutsideFunction.join('", "')}"`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { ScriptFunctionCallData } from 'js-yaml-loader!@/*';
|
|
||||||
import { ICompiledCode } from './ICompiledCode';
|
|
||||||
import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection';
|
|
||||||
|
|
||||||
export interface IFunctionCallCompiler {
|
|
||||||
compileCall(
|
|
||||||
call: ScriptFunctionCallData,
|
|
||||||
functions: ISharedFunctionCollection): ICompiledCode;
|
|
||||||
}
|
|
||||||
@@ -1,24 +1,25 @@
|
|||||||
import { IScriptCode } from '@/domain/IScriptCode';
|
import { IScriptCode } from '@/domain/IScriptCode';
|
||||||
import { ScriptCode } from '@/domain/ScriptCode';
|
import { ScriptCode } from '@/domain/ScriptCode';
|
||||||
|
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||||
import { FunctionData, ScriptData } from 'js-yaml-loader!@/*';
|
import { FunctionData, ScriptData } from 'js-yaml-loader!@/*';
|
||||||
import { IScriptCompiler } from './IScriptCompiler';
|
import { IScriptCompiler } from './IScriptCompiler';
|
||||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
|
||||||
import { ISharedFunctionCollection } from './Function/ISharedFunctionCollection';
|
import { ISharedFunctionCollection } from './Function/ISharedFunctionCollection';
|
||||||
import { IFunctionCallCompiler } from './FunctionCall/IFunctionCallCompiler';
|
import { IFunctionCallCompiler } from './Function/Call/Compiler/IFunctionCallCompiler';
|
||||||
import { FunctionCallCompiler } from './FunctionCall/FunctionCallCompiler';
|
import { FunctionCallCompiler } from './Function/Call/Compiler/FunctionCallCompiler';
|
||||||
import { IFunctionCompiler } from './Function/IFunctionCompiler';
|
import { ISharedFunctionsParser } from './Function/ISharedFunctionsParser';
|
||||||
import { FunctionCompiler } from './Function/FunctionCompiler';
|
import { SharedFunctionsParser } from './Function/SharedFunctionsParser';
|
||||||
|
import { parseFunctionCalls } from './Function/Call/FunctionCallParser';
|
||||||
|
|
||||||
export class ScriptCompiler implements IScriptCompiler {
|
export class ScriptCompiler implements IScriptCompiler {
|
||||||
private readonly functions: ISharedFunctionCollection;
|
private readonly functions: ISharedFunctionCollection;
|
||||||
constructor(
|
constructor(
|
||||||
functions: readonly FunctionData[] | undefined,
|
functions: readonly FunctionData[] | undefined,
|
||||||
private readonly syntax: ILanguageSyntax,
|
private readonly syntax: ILanguageSyntax,
|
||||||
functionCompiler: IFunctionCompiler = FunctionCompiler.instance,
|
sharedFunctionsParser: ISharedFunctionsParser = SharedFunctionsParser.instance,
|
||||||
private readonly callCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance,
|
private readonly callCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance,
|
||||||
) {
|
) {
|
||||||
if (!syntax) { throw new Error('undefined syntax'); }
|
if (!syntax) { throw new Error('undefined syntax'); }
|
||||||
this.functions = functionCompiler.compileFunctions(functions);
|
this.functions = sharedFunctionsParser.parseFunctions(functions);
|
||||||
}
|
}
|
||||||
public canCompile(script: ScriptData): boolean {
|
public canCompile(script: ScriptData): boolean {
|
||||||
if (!script) { throw new Error('undefined script'); }
|
if (!script) { throw new Error('undefined script'); }
|
||||||
@@ -30,7 +31,8 @@ export class ScriptCompiler implements IScriptCompiler {
|
|||||||
public compile(script: ScriptData): IScriptCode {
|
public compile(script: ScriptData): IScriptCode {
|
||||||
if (!script) { throw new Error('undefined script'); }
|
if (!script) { throw new Error('undefined script'); }
|
||||||
try {
|
try {
|
||||||
const compiledCode = this.callCompiler.compileCall(script.call, this.functions);
|
const calls = parseFunctionCalls(script.call);
|
||||||
|
const compiledCode = this.callCompiler.compileCall(calls, this.functions);
|
||||||
return new ScriptCode(
|
return new ScriptCode(
|
||||||
compiledCode.code,
|
compiledCode.code,
|
||||||
compiledCode.revertCode,
|
compiledCode.revertCode,
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { CompositeExpressionParser } from '@/application/Parser/Script/Compiler/
|
|||||||
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
|
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
|
||||||
import { IProjectInformation } from '@/domain/IProjectInformation';
|
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||||
import { ICodeSubstituter } from './ICodeSubstituter';
|
import { ICodeSubstituter } from './ICodeSubstituter';
|
||||||
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/FunctionCallArgumentCollection';
|
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
|
||||||
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/FunctionCallArgument';
|
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||||
|
|
||||||
export class CodeSubstituter implements ICodeSubstituter {
|
export class CodeSubstituter implements ICodeSubstituter {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ declare module 'js-yaml-loader!@/*' {
|
|||||||
readonly code?: string;
|
readonly code?: string;
|
||||||
readonly revertCode?: string;
|
readonly revertCode?: string;
|
||||||
|
|
||||||
readonly call?: ScriptFunctionCallData;
|
readonly call?: FunctionCallsData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ParameterDefinitionData {
|
export interface ParameterDefinitionData {
|
||||||
@@ -45,7 +45,7 @@ declare module 'js-yaml-loader!@/*' {
|
|||||||
readonly parameters?: FunctionCallParametersData;
|
readonly parameters?: FunctionCallParametersData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ScriptFunctionCallData = readonly FunctionCallData[] | FunctionCallData | undefined;
|
export type FunctionCallsData = readonly FunctionCallData[] | FunctionCallData | undefined;
|
||||||
|
|
||||||
export interface ScriptData extends InstructionHolder, DocumentableData {
|
export interface ScriptData extends InstructionHolder, DocumentableData {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { CategoryCollectionParseContextStub } from '@tests/unit/stubs/CategoryCo
|
|||||||
import { CategoryDataStub } from '@tests/unit/stubs/CategoryDataStub';
|
import { CategoryDataStub } from '@tests/unit/stubs/CategoryDataStub';
|
||||||
import { ScriptDataStub } from '@tests/unit/stubs/ScriptDataStub';
|
import { ScriptDataStub } from '@tests/unit/stubs/ScriptDataStub';
|
||||||
import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
|
import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
|
||||||
|
import { FunctionCallDataStub } from '@tests/unit/stubs/FunctionCallDataStub';
|
||||||
|
|
||||||
describe('CategoryCollectionParser', () => {
|
describe('CategoryCollectionParser', () => {
|
||||||
describe('parseCategoryCollection', () => {
|
describe('parseCategoryCollection', () => {
|
||||||
@@ -105,9 +106,10 @@ describe('CategoryCollectionParser', () => {
|
|||||||
const expectedCode = 'code-from-the-function';
|
const expectedCode = 'code-from-the-function';
|
||||||
const functionName = 'function-name';
|
const functionName = 'function-name';
|
||||||
const scriptName = 'script-name';
|
const scriptName = 'script-name';
|
||||||
const script = ScriptDataStub.createWithCall({ function: functionName })
|
const script = ScriptDataStub.createWithCall()
|
||||||
|
.withCall(new FunctionCallDataStub().withName(functionName).withParameters({}))
|
||||||
.withName(scriptName);
|
.withName(scriptName);
|
||||||
const func = FunctionDataStub.createWithCode()
|
const func = FunctionDataStub.createWithCode().withParametersObject([])
|
||||||
.withName(functionName)
|
.withName(functionName)
|
||||||
.withCode(expectedCode);
|
.withCode(expectedCode);
|
||||||
const category = new CategoryDataStub()
|
const category = new CategoryDataStub()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { expect } from 'chai';
|
|||||||
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
||||||
// tslint:disable-next-line:max-line-length
|
// tslint:disable-next-line:max-line-length
|
||||||
import { ExpressionEvaluator, Expression } from '@/application/Parser/Script/Compiler/Expressions/Expression/Expression';
|
import { ExpressionEvaluator, Expression } from '@/application/Parser/Script/Compiler/Expressions/Expression/Expression';
|
||||||
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/IFunctionCallArgumentCollection';
|
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||||
import { FunctionParameterCollectionStub } from '@tests/unit/stubs/FunctionParameterCollectionStub';
|
import { FunctionParameterCollectionStub } from '@tests/unit/stubs/FunctionParameterCollectionStub';
|
||||||
import { FunctionCallArgumentStub } from '@tests/unit/stubs/FunctionCallArgumentStub';
|
import { FunctionCallArgumentStub } from '@tests/unit/stubs/FunctionCallArgumentStub';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'mocha';
|
import 'mocha';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { ExpressionEvaluationContext, IExpressionEvaluationContext } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
|
import { ExpressionEvaluationContext, IExpressionEvaluationContext } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
|
||||||
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/IFunctionCallArgumentCollection';
|
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
import { IPipelineCompiler } from '@/application/Parser/Script/Compiler/Expressions/Pipes/IPipelineCompiler';
|
import { IPipelineCompiler } from '@/application/Parser/Script/Compiler/Expressions/Pipes/IPipelineCompiler';
|
||||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||||
import { PipelineCompilerStub } from '@tests/unit/stubs/PipelineCompilerStub';
|
import { PipelineCompilerStub } from '@tests/unit/stubs/PipelineCompilerStub';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'mocha';
|
import 'mocha';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/FunctionCallArgument';
|
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||||
import { testParameterName } from '../../ParameterNameTestRunner';
|
import { testParameterName } from '../../../ParameterNameTestRunner';
|
||||||
|
|
||||||
describe('FunctionCallArgument', () => {
|
describe('FunctionCallArgument', () => {
|
||||||
describe('ctor', () => {
|
describe('ctor', () => {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'mocha';
|
import 'mocha';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/FunctionCallArgumentCollection';
|
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
|
||||||
import { FunctionCallArgumentStub } from '@tests/unit/stubs/FunctionCallArgumentStub';
|
import { FunctionCallArgumentStub } from '@tests/unit/stubs/FunctionCallArgumentStub';
|
||||||
|
|
||||||
describe('FunctionCallArgumentCollection', () => {
|
describe('FunctionCallArgumentCollection', () => {
|
||||||
@@ -0,0 +1,505 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { FunctionCallParametersData } from 'js-yaml-loader!@/*';
|
||||||
|
import { FunctionCallCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompiler';
|
||||||
|
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
||||||
|
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
|
||||||
|
import { SharedFunctionCollectionStub } from '@tests/unit/stubs/SharedFunctionCollectionStub';
|
||||||
|
import { FunctionBodyType } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||||
|
import { SharedFunctionStub } from '@tests/unit/stubs/SharedFunctionStub';
|
||||||
|
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||||
|
import { FunctionCallStub } from '@tests/unit/stubs/FunctionCallStub';
|
||||||
|
import { ExpressionsCompilerStub } from '@tests/unit/stubs/ExpressionsCompilerStub';
|
||||||
|
|
||||||
|
describe('FunctionCallCompiler', () => {
|
||||||
|
describe('compileCall', () => {
|
||||||
|
describe('parameter validation', () => {
|
||||||
|
describe('call', () => {
|
||||||
|
it('throws with undefined call', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'undefined calls';
|
||||||
|
const call = undefined;
|
||||||
|
const functions = new SharedFunctionCollectionStub();
|
||||||
|
const sut = new MockableFunctionCallCompiler();
|
||||||
|
// act
|
||||||
|
const act = () => sut.compileCall(call, functions);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
it('throws if call sequence has undefined call', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'undefined function call';
|
||||||
|
const call = [
|
||||||
|
new FunctionCallStub(),
|
||||||
|
undefined,
|
||||||
|
];
|
||||||
|
const functions = new SharedFunctionCollectionStub();
|
||||||
|
const sut = new MockableFunctionCallCompiler();
|
||||||
|
// act
|
||||||
|
const act = () => sut.compileCall(call, functions);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
describe('throws if call parameters does not match function parameters', () => {
|
||||||
|
// arrange
|
||||||
|
const functionName = 'test-function-name';
|
||||||
|
const testCases = [
|
||||||
|
{
|
||||||
|
name: 'provided: single unexpected parameter, when: another expected',
|
||||||
|
functionParameters: [ 'expected-parameter' ],
|
||||||
|
callParameters: [ 'unexpected-parameter' ],
|
||||||
|
expectedError: `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"` +
|
||||||
|
`. Expected parameter(s): "expected-parameter"`
|
||||||
|
,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'provided: multiple unexpected parameters, when: different one is expected',
|
||||||
|
functionParameters: [ 'expected-parameter' ],
|
||||||
|
callParameters: [ 'unexpected-parameter1', 'unexpected-parameter2' ],
|
||||||
|
expectedError: `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter1", "unexpected-parameter2"` +
|
||||||
|
`. Expected parameter(s): "expected-parameter"`
|
||||||
|
,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'provided: an unexpected parameter, when: multiple parameters are expected',
|
||||||
|
functionParameters: [ 'expected-parameter1', 'expected-parameter2' ],
|
||||||
|
callParameters: [ 'expected-parameter1', 'expected-parameter2', 'unexpected-parameter' ],
|
||||||
|
expectedError: `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"` +
|
||||||
|
`. Expected parameter(s): "expected-parameter1", "expected-parameter2"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'provided: an unexpected parameter, when: none required',
|
||||||
|
functionParameters: undefined,
|
||||||
|
callParameters: [ 'unexpected-call-parameter' ],
|
||||||
|
expectedError: `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-call-parameter"` +
|
||||||
|
`. Expected parameter(s): none`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'provided: expected and unexpected parameter, when: one of them is expected',
|
||||||
|
functionParameters: [ 'expected-parameter' ],
|
||||||
|
callParameters: [ 'expected-parameter', 'unexpected-parameter' ],
|
||||||
|
expectedError: `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"` +
|
||||||
|
`. Expected parameter(s): "expected-parameter"`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
for (const testCase of testCases) {
|
||||||
|
it(testCase.name, () => {
|
||||||
|
const func = new SharedFunctionStub(FunctionBodyType.Code)
|
||||||
|
.withName('test-function-name')
|
||||||
|
.withParameterNames(...testCase.functionParameters);
|
||||||
|
let params: FunctionCallParametersData = {};
|
||||||
|
for (const parameter of testCase.callParameters) {
|
||||||
|
params = {...params, [parameter]: 'defined-parameter-value '};
|
||||||
|
}
|
||||||
|
const call = new FunctionCallStub()
|
||||||
|
.withFunctionName(func.name)
|
||||||
|
.withArguments(params);
|
||||||
|
const functions = new SharedFunctionCollectionStub()
|
||||||
|
.withFunction(func);
|
||||||
|
const sut = new MockableFunctionCallCompiler();
|
||||||
|
// act
|
||||||
|
const act = () => sut.compileCall([call], functions);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(testCase.expectedError);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('functions', () => {
|
||||||
|
it('throws with undefined functions', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'undefined functions';
|
||||||
|
const call = new FunctionCallStub();
|
||||||
|
const functions = undefined;
|
||||||
|
const sut = new MockableFunctionCallCompiler();
|
||||||
|
// act
|
||||||
|
const act = () => sut.compileCall([call], functions);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
it('throws if function does not exist', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'function does not exist';
|
||||||
|
const call = new FunctionCallStub();
|
||||||
|
const functions: ISharedFunctionCollection = {
|
||||||
|
getFunctionByName: () => { throw new Error(expectedError); },
|
||||||
|
};
|
||||||
|
const sut = new MockableFunctionCallCompiler();
|
||||||
|
// act
|
||||||
|
const act = () => sut.compileCall([call], functions);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
describe('builds code as expected', () => {
|
||||||
|
describe('builds single call as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const parametersTestCases = [
|
||||||
|
{
|
||||||
|
name: 'empty parameters',
|
||||||
|
parameters: [],
|
||||||
|
callArgs: { },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'non-empty parameters',
|
||||||
|
parameters: [ 'param1', 'param2' ],
|
||||||
|
callArgs: { param1: 'value1', param2: 'value2' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
for (const testCase of parametersTestCases) {
|
||||||
|
it(testCase.name, () => {
|
||||||
|
const expected = {
|
||||||
|
execute: 'expected code (execute)',
|
||||||
|
revert: 'expected code (revert)',
|
||||||
|
};
|
||||||
|
const func = new SharedFunctionStub(FunctionBodyType.Code)
|
||||||
|
.withParameterNames(...testCase.parameters);
|
||||||
|
const functions = new SharedFunctionCollectionStub().withFunction(func);
|
||||||
|
const call = new FunctionCallStub()
|
||||||
|
.withFunctionName(func.name)
|
||||||
|
.withArguments(testCase.callArgs);
|
||||||
|
const args = new FunctionCallArgumentCollectionStub().withArguments(testCase.callArgs);
|
||||||
|
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||||
|
.setup({ givenCode: func.body.code.do, givenArgs: args, result: expected.execute })
|
||||||
|
.setup({ givenCode: func.body.code.revert, givenArgs: args, result: expected.revert });
|
||||||
|
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||||
|
// act
|
||||||
|
const actual = sut.compileCall([call], functions);
|
||||||
|
// assert
|
||||||
|
expect(actual.code).to.equal(expected.execute);
|
||||||
|
expect(actual.revertCode).to.equal(expected.revert);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('builds call sequence as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const firstFunction = new SharedFunctionStub(FunctionBodyType.Code)
|
||||||
|
.withName('first-function-name')
|
||||||
|
.withCode('first-function-code')
|
||||||
|
.withRevertCode('first-function-revert-code');
|
||||||
|
const secondFunction = new SharedFunctionStub(FunctionBodyType.Code)
|
||||||
|
.withName('second-function-name')
|
||||||
|
.withParameterNames('testParameter')
|
||||||
|
.withCode('second-function-code')
|
||||||
|
.withRevertCode('second-function-revert-code');
|
||||||
|
const secondCallArguments = { testParameter: 'testValue' };
|
||||||
|
const calls = [
|
||||||
|
new FunctionCallStub().withFunctionName(firstFunction.name).withArguments({}),
|
||||||
|
new FunctionCallStub().withFunctionName(secondFunction.name).withArguments(secondCallArguments),
|
||||||
|
];
|
||||||
|
const firstFunctionCallArgs = new FunctionCallArgumentCollectionStub();
|
||||||
|
const secondFunctionCallArgs = new FunctionCallArgumentCollectionStub()
|
||||||
|
.withArguments(secondCallArguments);
|
||||||
|
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||||
|
.setupToReturnFunctionCode(firstFunction, firstFunctionCallArgs)
|
||||||
|
.setupToReturnFunctionCode(secondFunction, secondFunctionCallArgs);
|
||||||
|
const expectedExecute = `${firstFunction.body.code.do}\n${secondFunction.body.code.do}`;
|
||||||
|
const expectedRevert = `${firstFunction.body.code.revert}\n${secondFunction.body.code.revert}`;
|
||||||
|
const functions = new SharedFunctionCollectionStub()
|
||||||
|
.withFunction(firstFunction)
|
||||||
|
.withFunction(secondFunction);
|
||||||
|
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||||
|
// act
|
||||||
|
const actual = sut.compileCall(calls, functions);
|
||||||
|
// assert
|
||||||
|
expect(actual.code).to.equal(expectedExecute);
|
||||||
|
expect(actual.revertCode).to.equal(expectedRevert);
|
||||||
|
});
|
||||||
|
describe('can compile a call tree (function calling another)', () => {
|
||||||
|
describe('single deep function call', () => {
|
||||||
|
it('builds 2nd level of depth without arguments', () => {
|
||||||
|
// arrange
|
||||||
|
const emptyArgs = new FunctionCallArgumentCollectionStub();
|
||||||
|
const deepFunctionName = 'deepFunction';
|
||||||
|
const functions = {
|
||||||
|
deep: new SharedFunctionStub(FunctionBodyType.Code)
|
||||||
|
.withName(deepFunctionName)
|
||||||
|
.withCode('deep function code')
|
||||||
|
.withRevertCode('deep function final code'),
|
||||||
|
front: new SharedFunctionStub(FunctionBodyType.Calls)
|
||||||
|
.withName('frontFunction')
|
||||||
|
.withCalls(new FunctionCallStub()
|
||||||
|
.withFunctionName(deepFunctionName)
|
||||||
|
.withArgumentCollection(emptyArgs),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
const expected = {
|
||||||
|
code: 'final code',
|
||||||
|
revert: 'final revert code',
|
||||||
|
};
|
||||||
|
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||||
|
.setup({
|
||||||
|
givenCode: functions.deep.body.code.do,
|
||||||
|
givenArgs: emptyArgs,
|
||||||
|
result: expected.code,
|
||||||
|
})
|
||||||
|
.setup({
|
||||||
|
givenCode: functions.deep.body.code.revert,
|
||||||
|
givenArgs: emptyArgs,
|
||||||
|
result: expected.revert,
|
||||||
|
});
|
||||||
|
const mainCall = new FunctionCallStub()
|
||||||
|
.withFunctionName(functions.front.name)
|
||||||
|
.withArgumentCollection(emptyArgs);
|
||||||
|
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||||
|
// act
|
||||||
|
const actual = sut.compileCall(
|
||||||
|
[mainCall],
|
||||||
|
new SharedFunctionCollectionStub().withFunction(functions.deep, functions.front),
|
||||||
|
);
|
||||||
|
// assert
|
||||||
|
expect(actual.code).to.equal(expected.code);
|
||||||
|
expect(actual.revertCode).to.equal(expected.revert);
|
||||||
|
});
|
||||||
|
it('builds 2nd level of depth by compiling arguments', () => {
|
||||||
|
// arrange
|
||||||
|
const scenario = {
|
||||||
|
front: {
|
||||||
|
functionName: 'frontFunction',
|
||||||
|
parameterName: 'frontFunctionParameterName',
|
||||||
|
args: {
|
||||||
|
fromMainCall: 'initial argument to be compiled',
|
||||||
|
toNextStatic: 'value from "front" to "deep" in function definition',
|
||||||
|
toNextCompiled: 'argument from "front" to "deep" (compiled)',
|
||||||
|
},
|
||||||
|
callArgs: {
|
||||||
|
initialFromMainCall: () => new FunctionCallArgumentCollectionStub()
|
||||||
|
.withArgument(scenario.front.parameterName, scenario.front.args.fromMainCall),
|
||||||
|
expectedCallDeep: () => new FunctionCallArgumentCollectionStub()
|
||||||
|
.withArgument(scenario.deep.parameterName, scenario.front.args.toNextCompiled),
|
||||||
|
},
|
||||||
|
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||||
|
.withName(scenario.front.functionName)
|
||||||
|
.withParameterNames(scenario.front.parameterName)
|
||||||
|
.withCalls(new FunctionCallStub()
|
||||||
|
.withFunctionName(scenario.deep.functionName)
|
||||||
|
.withArgument(scenario.deep.parameterName, scenario.front.args.toNextStatic),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
deep: {
|
||||||
|
functionName: 'deepFunction',
|
||||||
|
parameterName: 'deepFunctionParameterName',
|
||||||
|
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
|
||||||
|
.withName(scenario.deep.functionName)
|
||||||
|
.withParameterNames(scenario.deep.parameterName)
|
||||||
|
.withCode(`${scenario.deep.functionName} function code`)
|
||||||
|
.withRevertCode(`${scenario.deep.functionName} function revert code`),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const expected = {
|
||||||
|
code: 'final code',
|
||||||
|
revert: 'final revert code',
|
||||||
|
};
|
||||||
|
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||||
|
.setup({ // Front ===args===> Deep
|
||||||
|
givenCode: scenario.front.args.toNextStatic,
|
||||||
|
givenArgs: scenario.front.callArgs.initialFromMainCall(),
|
||||||
|
result: scenario.front.args.toNextCompiled,
|
||||||
|
})
|
||||||
|
// set-up compiling of deep, compiled argument should be sent
|
||||||
|
.setup({
|
||||||
|
givenCode: scenario.deep.getFunction().body.code.do,
|
||||||
|
givenArgs: scenario.front.callArgs.expectedCallDeep(),
|
||||||
|
result: expected.code,
|
||||||
|
})
|
||||||
|
.setup({
|
||||||
|
givenCode: scenario.deep.getFunction().body.code.revert,
|
||||||
|
givenArgs: scenario.front.callArgs.expectedCallDeep(),
|
||||||
|
result: expected.revert,
|
||||||
|
});
|
||||||
|
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||||
|
// act
|
||||||
|
const actual = sut.compileCall(
|
||||||
|
[
|
||||||
|
new FunctionCallStub()
|
||||||
|
.withFunctionName(scenario.front.functionName)
|
||||||
|
.withArgumentCollection(scenario.front.callArgs.initialFromMainCall()),
|
||||||
|
],
|
||||||
|
new SharedFunctionCollectionStub().withFunction(
|
||||||
|
scenario.deep.getFunction(), scenario.front.getFunction()),
|
||||||
|
);
|
||||||
|
// assert
|
||||||
|
expect(actual.code).to.equal(expected.code);
|
||||||
|
expect(actual.revertCode).to.equal(expected.revert);
|
||||||
|
});
|
||||||
|
it('builds 3rd level of depth by compiling arguments', () => {
|
||||||
|
// arrange
|
||||||
|
const scenario = {
|
||||||
|
first: {
|
||||||
|
functionName: 'firstFunction',
|
||||||
|
parameter: 'firstParameter',
|
||||||
|
args: {
|
||||||
|
fromMainCall: 'initial argument to be compiled',
|
||||||
|
toNextStatic: 'value from "first" to "second" in function definition',
|
||||||
|
toNextCompiled: 'argument from "first" to "second" (compiled)',
|
||||||
|
},
|
||||||
|
callArgs: {
|
||||||
|
initialFromMainCall: () => new FunctionCallArgumentCollectionStub()
|
||||||
|
.withArgument(scenario.first.parameter, scenario.first.args.fromMainCall),
|
||||||
|
expectedToSecond: () => new FunctionCallArgumentCollectionStub()
|
||||||
|
.withArgument(scenario.second.parameter, scenario.first.args.toNextCompiled),
|
||||||
|
},
|
||||||
|
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||||
|
.withName(scenario.first.functionName)
|
||||||
|
.withParameterNames(scenario.first.parameter)
|
||||||
|
.withCalls(new FunctionCallStub()
|
||||||
|
.withFunctionName(scenario.second.functionName)
|
||||||
|
.withArgument(scenario.second.parameter, scenario.first.args.toNextStatic),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
second: {
|
||||||
|
functionName: 'secondFunction',
|
||||||
|
parameter: 'secondParameter',
|
||||||
|
args: {
|
||||||
|
toNextCompiled: 'argument second to third (compiled)',
|
||||||
|
toNextStatic: 'calling second to third',
|
||||||
|
},
|
||||||
|
callArgs: {
|
||||||
|
expectedToThird: () => new FunctionCallArgumentCollectionStub()
|
||||||
|
.withArgument(scenario.third.parameter, scenario.second.args.toNextCompiled),
|
||||||
|
},
|
||||||
|
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||||
|
.withName(scenario.second.functionName)
|
||||||
|
.withParameterNames(scenario.second.parameter)
|
||||||
|
.withCalls(new FunctionCallStub()
|
||||||
|
.withFunctionName(scenario.third.functionName)
|
||||||
|
.withArgument(scenario.third.parameter, scenario.second.args.toNextStatic),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
third: {
|
||||||
|
functionName: 'thirdFunction',
|
||||||
|
parameter: 'thirdParameter',
|
||||||
|
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
|
||||||
|
.withName(scenario.third.functionName)
|
||||||
|
.withParameterNames(scenario.third.parameter)
|
||||||
|
.withCode(`${scenario.third.functionName} function code`)
|
||||||
|
.withRevertCode(`${scenario.third.functionName} function revert code`),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const expected = {
|
||||||
|
code: 'final code',
|
||||||
|
revert: 'final revert code',
|
||||||
|
};
|
||||||
|
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||||
|
.setup({ // First ===args===> Second
|
||||||
|
givenCode: scenario.first.args.toNextStatic,
|
||||||
|
givenArgs: scenario.first.callArgs.initialFromMainCall(),
|
||||||
|
result: scenario.first.args.toNextCompiled,
|
||||||
|
})
|
||||||
|
.setup({ // Second ===args===> third
|
||||||
|
givenCode: scenario.second.args.toNextStatic,
|
||||||
|
givenArgs: scenario.first.callArgs.expectedToSecond(),
|
||||||
|
result: scenario.second.args.toNextCompiled,
|
||||||
|
})
|
||||||
|
// Compiling of third functions code with expected arguments
|
||||||
|
.setup({
|
||||||
|
givenCode: scenario.third.getFunction().body.code.do,
|
||||||
|
givenArgs: scenario.second.callArgs.expectedToThird(),
|
||||||
|
result: expected.code,
|
||||||
|
})
|
||||||
|
.setup({
|
||||||
|
givenCode: scenario.third.getFunction().body.code.revert,
|
||||||
|
givenArgs: scenario.second.callArgs.expectedToThird(),
|
||||||
|
result: expected.revert,
|
||||||
|
});
|
||||||
|
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||||
|
const mainCall = new FunctionCallStub()
|
||||||
|
.withFunctionName(scenario.first.functionName)
|
||||||
|
.withArgumentCollection(scenario.first.callArgs.initialFromMainCall());
|
||||||
|
// act
|
||||||
|
const actual = sut.compileCall(
|
||||||
|
[mainCall],
|
||||||
|
new SharedFunctionCollectionStub().withFunction(
|
||||||
|
scenario.first.getFunction(),
|
||||||
|
scenario.second.getFunction(),
|
||||||
|
scenario.third.getFunction()),
|
||||||
|
);
|
||||||
|
// assert
|
||||||
|
expect(actual.code).to.equal(expected.code);
|
||||||
|
expect(actual.revertCode).to.equal(expected.revert);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('multiple deep function calls', () => {
|
||||||
|
it('builds 2nd level of depth without arguments', () => {
|
||||||
|
// arrange
|
||||||
|
const emptyArgs = new FunctionCallArgumentCollectionStub();
|
||||||
|
const functions = {
|
||||||
|
call1: {
|
||||||
|
deep: {
|
||||||
|
functionName: 'deepFunction',
|
||||||
|
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
|
||||||
|
.withName(functions.call1.deep.functionName)
|
||||||
|
.withCode('deep function (1) code')
|
||||||
|
.withRevertCode('deep function (1) final code'),
|
||||||
|
},
|
||||||
|
front: {
|
||||||
|
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||||
|
.withName('frontFunction')
|
||||||
|
.withCalls(new FunctionCallStub()
|
||||||
|
.withFunctionName(functions.call1.deep.functionName)
|
||||||
|
.withArgumentCollection(emptyArgs),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
call2: {
|
||||||
|
deep: {
|
||||||
|
functionName: 'deepFunction2',
|
||||||
|
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
|
||||||
|
.withName(functions.call2.deep.functionName)
|
||||||
|
.withCode('deep function (2) code')
|
||||||
|
.withRevertCode('deep function (2) final code'),
|
||||||
|
},
|
||||||
|
front: {
|
||||||
|
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
|
||||||
|
.withName('frontFunction2')
|
||||||
|
.withCalls(new FunctionCallStub()
|
||||||
|
.withFunctionName(functions.call2.deep.functionName)
|
||||||
|
.withArgumentCollection(emptyArgs),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getMainCall: () => [
|
||||||
|
new FunctionCallStub()
|
||||||
|
.withFunctionName(functions.call1.front.getFunction().name)
|
||||||
|
.withArgumentCollection(emptyArgs),
|
||||||
|
new FunctionCallStub()
|
||||||
|
.withFunctionName(functions.call2.front.getFunction().name)
|
||||||
|
.withArgumentCollection(emptyArgs),
|
||||||
|
],
|
||||||
|
getCollection: () => new SharedFunctionCollectionStub().withFunction(
|
||||||
|
functions.call1.deep.getFunction(),
|
||||||
|
functions.call1.front.getFunction(),
|
||||||
|
functions.call2.deep.getFunction(),
|
||||||
|
functions.call2.front.getFunction(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||||
|
.setupToReturnFunctionCode(functions.call1.deep.getFunction(), emptyArgs)
|
||||||
|
.setupToReturnFunctionCode(functions.call2.deep.getFunction(), emptyArgs);
|
||||||
|
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||||
|
const expected = {
|
||||||
|
code: `${functions.call1.deep.getFunction().body.code.do}\n${functions.call2.deep.getFunction().body.code.do}`,
|
||||||
|
revert: `${functions.call1.deep.getFunction().body.code.revert}\n${functions.call2.deep.getFunction().body.code.revert}`,
|
||||||
|
};
|
||||||
|
// act
|
||||||
|
const actual = sut.compileCall(
|
||||||
|
functions.getMainCall(),
|
||||||
|
functions.getCollection(),
|
||||||
|
);
|
||||||
|
// assert
|
||||||
|
expect(actual.code).to.equal(expected.code);
|
||||||
|
expect(actual.revertCode).to.equal(expected.revert);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
class MockableFunctionCallCompiler extends FunctionCallCompiler {
|
||||||
|
constructor(expressionsCompiler: IExpressionsCompiler = new ExpressionsCompilerStub()) {
|
||||||
|
super(expressionsCompiler);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||||
|
|
||||||
|
describe('FunctionCall', () => {
|
||||||
|
describe('ctor', () => {
|
||||||
|
describe('args', () => {
|
||||||
|
it('throws when args is undefined', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'undefined args';
|
||||||
|
const args = undefined;
|
||||||
|
// act
|
||||||
|
const act = () => new FunctionCallBuilder()
|
||||||
|
.withArgs(args)
|
||||||
|
.build();
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
it('sets args as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const expected = new FunctionCallArgumentCollectionStub()
|
||||||
|
.withArgument('testParameter', 'testValue');
|
||||||
|
// act
|
||||||
|
const sut = new FunctionCallBuilder()
|
||||||
|
.withArgs(expected)
|
||||||
|
.build();
|
||||||
|
// assert
|
||||||
|
expect(sut.args).to.deep.equal(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('functionName', () => {
|
||||||
|
it('throws when function name is undefined', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'empty function name in function call';
|
||||||
|
const functionName = undefined;
|
||||||
|
// act
|
||||||
|
const act = () => new FunctionCallBuilder()
|
||||||
|
.withFunctionName(functionName)
|
||||||
|
.build();
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
it('sets function name as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const expected = 'expectedFunctionName';
|
||||||
|
// act
|
||||||
|
const sut = new FunctionCallBuilder()
|
||||||
|
.withFunctionName(expected)
|
||||||
|
.build();
|
||||||
|
// assert
|
||||||
|
expect(sut.functionName).to.equal(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
class FunctionCallBuilder {
|
||||||
|
private functionName = 'functionName';
|
||||||
|
private args: IReadOnlyFunctionCallArgumentCollection = new FunctionCallArgumentCollectionStub();
|
||||||
|
|
||||||
|
public withFunctionName(functionName: string) {
|
||||||
|
this.functionName = functionName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public withArgs(args: IReadOnlyFunctionCallArgumentCollection) {
|
||||||
|
this.args = args;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public build() {
|
||||||
|
return new FunctionCall(this.functionName, this.args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { parseFunctionCalls } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCallParser';
|
||||||
|
import { FunctionCallDataStub } from '@tests/unit/stubs/FunctionCallDataStub';
|
||||||
|
|
||||||
|
describe('FunctionCallParser', () => {
|
||||||
|
describe('parseFunctionCalls', () => {
|
||||||
|
it('throws with undefined call', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'undefined call data';
|
||||||
|
const call = undefined;
|
||||||
|
// act
|
||||||
|
const act = () => parseFunctionCalls(call);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
it('throws if call is not an object', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'called function(s) must be an object';
|
||||||
|
const invalidCalls: readonly any[] = ['string', 33];
|
||||||
|
invalidCalls.forEach((invalidCall) => {
|
||||||
|
// act
|
||||||
|
const act = () => parseFunctionCalls(invalidCall);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('throws if call sequence has undefined call', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'undefined function call';
|
||||||
|
const data = [
|
||||||
|
new FunctionCallDataStub(),
|
||||||
|
undefined,
|
||||||
|
];
|
||||||
|
// act
|
||||||
|
const act = () => parseFunctionCalls(data);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
it('throws if call sequence has undefined function name', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'empty function name in function call';
|
||||||
|
const data = [
|
||||||
|
new FunctionCallDataStub().withName('function-name'),
|
||||||
|
new FunctionCallDataStub().withName(undefined),
|
||||||
|
];
|
||||||
|
// act
|
||||||
|
const act = () => parseFunctionCalls(data);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
it('parses single call as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedFunctionName = 'functionName';
|
||||||
|
const expectedParameterName = 'parameterName';
|
||||||
|
const expectedArgumentValue = 'argumentValue';
|
||||||
|
const data = new FunctionCallDataStub()
|
||||||
|
.withName(expectedFunctionName)
|
||||||
|
.withParameters({ [expectedParameterName]: expectedArgumentValue });
|
||||||
|
// act
|
||||||
|
const actual = parseFunctionCalls(data);
|
||||||
|
// assert
|
||||||
|
expect(actual).to.have.lengthOf(1);
|
||||||
|
const call = actual[0];
|
||||||
|
expect(call.functionName).to.equal(expectedFunctionName);
|
||||||
|
const args = call.args;
|
||||||
|
expect(args.getAllParameterNames()).to.have.lengthOf(1);
|
||||||
|
expect(args.hasArgument(expectedParameterName)).to.equal(true,
|
||||||
|
`Does not include expected parameter: "${expectedParameterName}"\n` +
|
||||||
|
`But includes: "${args.getAllParameterNames()}"`);
|
||||||
|
const argument = args.getArgument(expectedParameterName);
|
||||||
|
expect(argument.parameterName).to.equal(expectedParameterName);
|
||||||
|
expect(argument.argumentValue).to.equal(expectedArgumentValue);
|
||||||
|
});
|
||||||
|
it('parses multiple calls as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const getFunctionName = (index: number) => `functionName${index}`;
|
||||||
|
const getParameterName = (index: number) => `parameterName${index}`;
|
||||||
|
const getArgumentValue = (index: number) => `argumentValue${index}`;
|
||||||
|
const createCall = (index: number) => new FunctionCallDataStub()
|
||||||
|
.withName(getFunctionName(index))
|
||||||
|
.withParameters({ [getParameterName(index)]: getArgumentValue(index)});
|
||||||
|
const calls = [ createCall(0), createCall(1), createCall(2), createCall(3) ];
|
||||||
|
// act
|
||||||
|
const actual = parseFunctionCalls(calls);
|
||||||
|
// assert
|
||||||
|
expect(actual).to.have.lengthOf(calls.length);
|
||||||
|
for (let i = 0; i < calls.length; i++) {
|
||||||
|
const call = actual[i];
|
||||||
|
const expectedParameterName = getParameterName(i);
|
||||||
|
const expectedArgumentValue = getArgumentValue(i);
|
||||||
|
expect(call.functionName).to.equal(getFunctionName(i));
|
||||||
|
expect(call.args.getAllParameterNames()).to.have.lengthOf(1);
|
||||||
|
expect(call.args.hasArgument(expectedParameterName)).to.equal(true);
|
||||||
|
const argument = call.args.getArgument(expectedParameterName);
|
||||||
|
expect(argument.parameterName).to.equal(expectedParameterName);
|
||||||
|
expect(argument.argumentValue).to.equal(expectedArgumentValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
import 'mocha';
|
|
||||||
import { expect } from 'chai';
|
|
||||||
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
|
||||||
import { FunctionData, ParameterDefinitionData } from 'js-yaml-loader!@/*';
|
|
||||||
import { IFunctionCallCompiler } from '@/application/Parser/Script/Compiler/FunctionCall/IFunctionCallCompiler';
|
|
||||||
import { FunctionCompiler } from '@/application/Parser/Script/Compiler/Function/FunctionCompiler';
|
|
||||||
import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
|
||||||
import { FunctionCallCompilerStub } from '@tests/unit/stubs/FunctionCallCompilerStub';
|
|
||||||
import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
|
|
||||||
import { ParameterDefinitionDataStub } from '@tests/unit/stubs/ParameterDefinitionDataStub';
|
|
||||||
import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
|
|
||||||
|
|
||||||
describe('FunctionsCompiler', () => {
|
|
||||||
describe('compileFunctions', () => {
|
|
||||||
describe('validates functions', () => {
|
|
||||||
it('throws if one of the functions is undefined', () => {
|
|
||||||
// arrange
|
|
||||||
const expectedError = `some functions are undefined`;
|
|
||||||
const functions = [ FunctionDataStub.createWithCode(), undefined ];
|
|
||||||
const sut = new MockableFunctionCompiler();
|
|
||||||
// act
|
|
||||||
const act = () => sut.compileFunctions(functions);
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
|
||||||
it('throws when functions have same names', () => {
|
|
||||||
// arrange
|
|
||||||
const name = 'same-func-name';
|
|
||||||
const expectedError = `duplicate function name: "${name}"`;
|
|
||||||
const functions = [
|
|
||||||
FunctionDataStub.createWithCode().withName(name),
|
|
||||||
FunctionDataStub.createWithCode().withName(name),
|
|
||||||
];
|
|
||||||
const sut = new MockableFunctionCompiler();
|
|
||||||
// act
|
|
||||||
const act = () => sut.compileFunctions(functions);
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
|
||||||
describe('throws when parameters type is not as expected', () => {
|
|
||||||
const testCases = [
|
|
||||||
{
|
|
||||||
state: 'when not an array',
|
|
||||||
invalidType: 5,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
state: 'when array but not of objects',
|
|
||||||
invalidType: [ 'a', { a: 'b'} ],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
for (const testCase of testCases) {
|
|
||||||
it(testCase.state, () => {
|
|
||||||
// arrange
|
|
||||||
const func = FunctionDataStub
|
|
||||||
.createWithCall()
|
|
||||||
.withParametersObject(testCase.invalidType as any);
|
|
||||||
const expectedError = `parameters must be an array of objects in function(s) "${func.name}"`;
|
|
||||||
const sut = new MockableFunctionCompiler();
|
|
||||||
// act
|
|
||||||
const act = () => sut.compileFunctions([ func ]);
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
describe('throws when when function have duplicate code', () => {
|
|
||||||
it('code', () => {
|
|
||||||
// arrange
|
|
||||||
const code = 'duplicate-code';
|
|
||||||
const expectedError = `duplicate "code" in functions: "${code}"`;
|
|
||||||
const functions = [
|
|
||||||
FunctionDataStub.createWithoutCallOrCodes().withName('func-1').withCode(code),
|
|
||||||
FunctionDataStub.createWithoutCallOrCodes().withName('func-2').withCode(code),
|
|
||||||
];
|
|
||||||
const sut = new MockableFunctionCompiler();
|
|
||||||
// act
|
|
||||||
const act = () => sut.compileFunctions(functions);
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
|
||||||
it('revertCode', () => {
|
|
||||||
// arrange
|
|
||||||
const revertCode = 'duplicate-revert-code';
|
|
||||||
const expectedError = `duplicate "revertCode" in functions: "${revertCode}"`;
|
|
||||||
const functions = [
|
|
||||||
FunctionDataStub.createWithoutCallOrCodes()
|
|
||||||
.withName('func-1').withCode('code-1').withRevertCode(revertCode),
|
|
||||||
FunctionDataStub.createWithoutCallOrCodes()
|
|
||||||
.withName('func-2').withCode('code-2').withRevertCode(revertCode),
|
|
||||||
];
|
|
||||||
const sut = new MockableFunctionCompiler();
|
|
||||||
// act
|
|
||||||
const act = () => sut.compileFunctions(functions);
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('both code and call are defined', () => {
|
|
||||||
// arrange
|
|
||||||
const functionName = 'invalid-function';
|
|
||||||
const expectedError = `both "code" and "call" are defined in "${functionName}"`;
|
|
||||||
const invalidFunction = FunctionDataStub.createWithoutCallOrCodes()
|
|
||||||
.withName(functionName)
|
|
||||||
.withCode('code')
|
|
||||||
.withMockCall();
|
|
||||||
const sut = new MockableFunctionCompiler();
|
|
||||||
// act
|
|
||||||
const act = () => sut.compileFunctions([ invalidFunction ]);
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
|
||||||
it('neither code and call is defined', () => {
|
|
||||||
// arrange
|
|
||||||
const functionName = 'invalid-function';
|
|
||||||
const expectedError = `neither "code" or "call" is defined in "${functionName}"`;
|
|
||||||
const invalidFunction = FunctionDataStub.createWithoutCallOrCodes()
|
|
||||||
.withName(functionName);
|
|
||||||
const sut = new MockableFunctionCompiler();
|
|
||||||
// act
|
|
||||||
const act = () => sut.compileFunctions([ invalidFunction ]);
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
|
||||||
it('rethrows including function name when FunctionParameter throws', () => {
|
|
||||||
// arrange
|
|
||||||
const functionName = 'invalid-function';
|
|
||||||
const expectedError = `neither "code" or "call" is defined in "${functionName}"`;
|
|
||||||
const invalidFunction = FunctionDataStub.createWithoutCallOrCodes()
|
|
||||||
.withName(functionName);
|
|
||||||
const sut = new MockableFunctionCompiler();
|
|
||||||
// act
|
|
||||||
const act = () => sut.compileFunctions([ invalidFunction ]);
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
|
||||||
it('rethrows including function name when FunctionParameter throws', () => {
|
|
||||||
// arrange
|
|
||||||
const invalidParameterName = 'invalid function p@r4meter name';
|
|
||||||
const functionName = 'functionName';
|
|
||||||
let parameterException: Error;
|
|
||||||
// tslint:disable-next-line:no-unused-expression
|
|
||||||
try { new FunctionParameter(invalidParameterName, false); } catch (e) { parameterException = e; }
|
|
||||||
const expectedError = `"${functionName}": ${parameterException.message}`;
|
|
||||||
const functionData = FunctionDataStub.createWithCode()
|
|
||||||
.withName(functionName)
|
|
||||||
.withParameters(new ParameterDefinitionDataStub().withName(invalidParameterName));
|
|
||||||
|
|
||||||
// act
|
|
||||||
const sut = new MockableFunctionCompiler();
|
|
||||||
const act = () => sut.compileFunctions([ functionData ]);
|
|
||||||
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('returns empty with empty functions', () => {
|
|
||||||
// arrange
|
|
||||||
const emptyValues = [ [], undefined ];
|
|
||||||
const sut = new MockableFunctionCompiler();
|
|
||||||
for (const emptyFunctions of emptyValues) {
|
|
||||||
// act
|
|
||||||
const actual = sut.compileFunctions(emptyFunctions);
|
|
||||||
// assert
|
|
||||||
expect(actual).to.not.equal(undefined);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
it('parses single function with code as expected', () => {
|
|
||||||
// arrange
|
|
||||||
const name = 'function-name';
|
|
||||||
const expected = FunctionDataStub
|
|
||||||
.createWithoutCallOrCodes()
|
|
||||||
.withName(name)
|
|
||||||
.withCode('expected-code')
|
|
||||||
.withRevertCode('expected-revert-code')
|
|
||||||
.withParameters(
|
|
||||||
new ParameterDefinitionDataStub().withName('expectedParameter').withOptionality(true),
|
|
||||||
new ParameterDefinitionDataStub().withName('expectedParameter2').withOptionality(false),
|
|
||||||
);
|
|
||||||
const sut = new MockableFunctionCompiler();
|
|
||||||
// act
|
|
||||||
const collection = sut.compileFunctions([ expected ]);
|
|
||||||
// expect
|
|
||||||
const actual = collection.getFunctionByName(name);
|
|
||||||
expectEqualFunctions(expected, actual);
|
|
||||||
});
|
|
||||||
it('parses function with call as expected', () => {
|
|
||||||
// arrange
|
|
||||||
const calleeName = 'callee-function';
|
|
||||||
const caller = FunctionDataStub.createWithoutCallOrCodes()
|
|
||||||
.withName('caller-function')
|
|
||||||
.withCall({ function: calleeName });
|
|
||||||
const callee = FunctionDataStub.createWithoutCallOrCodes()
|
|
||||||
.withName(calleeName)
|
|
||||||
.withCode('expected-code')
|
|
||||||
.withRevertCode('expected-revert-code');
|
|
||||||
const sut = new MockableFunctionCompiler();
|
|
||||||
// act
|
|
||||||
const collection = sut.compileFunctions([ caller, callee ]);
|
|
||||||
// expect
|
|
||||||
const actual = collection.getFunctionByName(caller.name);
|
|
||||||
expectEqualFunctionCode(callee, actual);
|
|
||||||
});
|
|
||||||
it('parses multiple functions with call as expected', () => {
|
|
||||||
// arrange
|
|
||||||
const calleeName = 'callee-function';
|
|
||||||
const caller1 = FunctionDataStub.createWithoutCallOrCodes()
|
|
||||||
.withName('caller-function')
|
|
||||||
.withCall({ function: calleeName });
|
|
||||||
const caller2 = FunctionDataStub.createWithoutCallOrCodes()
|
|
||||||
.withName('caller-function-2')
|
|
||||||
.withCall({ function: calleeName });
|
|
||||||
const callee = FunctionDataStub.createWithoutCallOrCodes()
|
|
||||||
.withName(calleeName)
|
|
||||||
.withCode('expected-code')
|
|
||||||
.withRevertCode('expected-revert-code');
|
|
||||||
const sut = new MockableFunctionCompiler();
|
|
||||||
// act
|
|
||||||
const collection = sut.compileFunctions([ caller1, caller2, callee ]);
|
|
||||||
// expect
|
|
||||||
const compiledCaller1 = collection.getFunctionByName(caller1.name);
|
|
||||||
const compiledCaller2 = collection.getFunctionByName(caller2.name);
|
|
||||||
expectEqualFunctionCode(callee, compiledCaller1);
|
|
||||||
expectEqualFunctionCode(callee, compiledCaller2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function expectEqualFunctions(expected: FunctionData, actual: ISharedFunction) {
|
|
||||||
expect(actual.name).to.equal(expected.name);
|
|
||||||
expect(areScrambledEqual(actual.parameters, expected.parameters));
|
|
||||||
expectEqualFunctionCode(expected, actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
function expectEqualFunctionCode(expected: FunctionData, actual: ISharedFunction) {
|
|
||||||
expect(actual.code).to.equal(expected.code);
|
|
||||||
expect(actual.revertCode).to.equal(expected.revertCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
function areScrambledEqual(
|
|
||||||
expected: IReadOnlyFunctionParameterCollection,
|
|
||||||
actual: readonly ParameterDefinitionData[],
|
|
||||||
) {
|
|
||||||
if (expected.all.length !== actual.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (const expectedParameter of expected.all) {
|
|
||||||
if (!actual.some(
|
|
||||||
(a) => a.name === expectedParameter.name
|
|
||||||
&& (a.optional || false) === expectedParameter.isOptional)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
class MockableFunctionCompiler extends FunctionCompiler {
|
|
||||||
constructor(functionCallCompiler: IFunctionCallCompiler = new FunctionCallCompilerStub()) {
|
|
||||||
super(functionCallCompiler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,23 @@
|
|||||||
import 'mocha';
|
import 'mocha';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { SharedFunction } from '@/application/Parser/Script/Compiler/Function/SharedFunction';
|
|
||||||
import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
||||||
import { FunctionParameterCollectionStub } from '@tests/unit/stubs/FunctionParameterCollectionStub';
|
import { FunctionParameterCollectionStub } from '@tests/unit/stubs/FunctionParameterCollectionStub';
|
||||||
|
import { createCallerFunction, createFunctionWithInlineCode } from '@/application/Parser/Script/Compiler/Function/SharedFunction';
|
||||||
|
import { IFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/IFunctionCall';
|
||||||
|
import { FunctionCallStub } from '@tests/unit/stubs/FunctionCallStub';
|
||||||
|
import { FunctionBodyType } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||||
|
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||||
|
|
||||||
describe('SharedFunction', () => {
|
describe('SharedFunction', () => {
|
||||||
describe('name', () => {
|
describe('name', () => {
|
||||||
|
runForEachFactoryMethod((build) => {
|
||||||
it('sets as expected', () => {
|
it('sets as expected', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expected = 'expected-function-name';
|
const expected = 'expected-function-name';
|
||||||
|
const builder = new SharedFunctionBuilder()
|
||||||
|
.withName(expected);
|
||||||
// act
|
// act
|
||||||
const sut = new SharedFunctionBuilder()
|
const sut = build(builder);
|
||||||
.withName(expected)
|
|
||||||
.build();
|
|
||||||
// assert
|
// assert
|
||||||
expect(sut.name).equal(expected);
|
expect(sut.name).equal(expected);
|
||||||
});
|
});
|
||||||
@@ -21,39 +26,44 @@ describe('SharedFunction', () => {
|
|||||||
const expectedError = 'undefined function name';
|
const expectedError = 'undefined function name';
|
||||||
const invalidValues = [ undefined, '' ];
|
const invalidValues = [ undefined, '' ];
|
||||||
for (const invalidValue of invalidValues) {
|
for (const invalidValue of invalidValues) {
|
||||||
|
const builder = new SharedFunctionBuilder()
|
||||||
|
.withName(invalidValue);
|
||||||
// act
|
// act
|
||||||
const act = () => new SharedFunctionBuilder()
|
const act = () => build(builder);
|
||||||
.withName(invalidValue)
|
|
||||||
.build();
|
|
||||||
// assert
|
// assert
|
||||||
expect(act).to.throw(expectedError);
|
expect(act).to.throw(expectedError);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
describe('parameters', () => {
|
describe('parameters', () => {
|
||||||
|
runForEachFactoryMethod((build) => {
|
||||||
it('sets as expected', () => {
|
it('sets as expected', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expected = new FunctionParameterCollectionStub()
|
const expected = new FunctionParameterCollectionStub()
|
||||||
.withParameterName('test-parameter');
|
.withParameterName('test-parameter');
|
||||||
|
const builder = new SharedFunctionBuilder()
|
||||||
|
.withParameters(expected);
|
||||||
// act
|
// act
|
||||||
const sut = new SharedFunctionBuilder()
|
const sut = build(builder);
|
||||||
.withParameters(expected)
|
|
||||||
.build();
|
|
||||||
// assert
|
// assert
|
||||||
expect(sut.parameters).to.equal(expected);
|
expect(sut.parameters).equal(expected);
|
||||||
});
|
});
|
||||||
it('throws if undefined', () => {
|
it('throws if undefined', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expectedError = 'undefined parameters';
|
const expectedError = 'undefined parameters';
|
||||||
const parameters = undefined;
|
const parameters = undefined;
|
||||||
|
const builder = new SharedFunctionBuilder()
|
||||||
|
.withParameters(parameters);
|
||||||
// act
|
// act
|
||||||
const act = () => new SharedFunctionBuilder()
|
const act = () => build(builder);
|
||||||
.withParameters(parameters)
|
|
||||||
.build();
|
|
||||||
// assert
|
// assert
|
||||||
expect(act).to.throw(expectedError);
|
expect(act).to.throw(expectedError);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
describe('body', () => {
|
||||||
|
describe('createFunctionWithInlineCode', () => {
|
||||||
describe('code', () => {
|
describe('code', () => {
|
||||||
it('sets as expected', () => {
|
it('sets as expected', () => {
|
||||||
// arrange
|
// arrange
|
||||||
@@ -61,21 +71,21 @@ describe('SharedFunction', () => {
|
|||||||
// act
|
// act
|
||||||
const sut = new SharedFunctionBuilder()
|
const sut = new SharedFunctionBuilder()
|
||||||
.withCode(expected)
|
.withCode(expected)
|
||||||
.build();
|
.createFunctionWithInlineCode();
|
||||||
// assert
|
// assert
|
||||||
expect(sut.code).equal(expected);
|
expect(sut.body.code.do).equal(expected);
|
||||||
});
|
});
|
||||||
it('throws if empty or undefined', () => {
|
it('throws if empty or undefined', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const functionName = 'expected-function-name';
|
const functionName = 'expected-function-name';
|
||||||
const expectedError = `undefined function ("${functionName}") code`;
|
const expectedError = `undefined code in function "${functionName}"`;
|
||||||
const invalidValues = [ undefined, '' ];
|
const invalidValues = [ undefined, '' ];
|
||||||
for (const invalidValue of invalidValues) {
|
for (const invalidValue of invalidValues) {
|
||||||
// act
|
// act
|
||||||
const act = () => new SharedFunctionBuilder()
|
const act = () => new SharedFunctionBuilder()
|
||||||
.withName(functionName)
|
.withName(functionName)
|
||||||
.withCode(invalidValue)
|
.withCode(invalidValue)
|
||||||
.build();
|
.createFunctionWithInlineCode();
|
||||||
// assert
|
// assert
|
||||||
expect(act).to.throw(expectedError);
|
expect(act).to.throw(expectedError);
|
||||||
}
|
}
|
||||||
@@ -89,28 +99,135 @@ describe('SharedFunction', () => {
|
|||||||
// act
|
// act
|
||||||
const sut = new SharedFunctionBuilder()
|
const sut = new SharedFunctionBuilder()
|
||||||
.withRevertCode(data)
|
.withRevertCode(data)
|
||||||
.build();
|
.createFunctionWithInlineCode();
|
||||||
// assert
|
// assert
|
||||||
expect(sut.revertCode).equal(data);
|
expect(sut.body.code.revert).equal(data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('sets type as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedType = FunctionBodyType.Code;
|
||||||
|
// act
|
||||||
|
const sut = new SharedFunctionBuilder()
|
||||||
|
.createFunctionWithInlineCode();
|
||||||
|
// assert
|
||||||
|
expect(sut.body.type).equal(expectedType);
|
||||||
|
});
|
||||||
|
it('calls are undefined', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedCalls = undefined;
|
||||||
|
// act
|
||||||
|
const sut = new SharedFunctionBuilder()
|
||||||
|
.createFunctionWithInlineCode();
|
||||||
|
// assert
|
||||||
|
expect(sut.body.calls).equal(expectedCalls);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('createCallerFunction', () => {
|
||||||
|
describe('callSequence', () => {
|
||||||
|
it('sets as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const expected = [
|
||||||
|
new FunctionCallStub().withFunctionName('firstFunction'),
|
||||||
|
new FunctionCallStub().withFunctionName('secondFunction'),
|
||||||
|
];
|
||||||
|
// act
|
||||||
|
const sut = new SharedFunctionBuilder()
|
||||||
|
.withCallSequence(expected)
|
||||||
|
.createCallerFunction();
|
||||||
|
// assert
|
||||||
|
expect(sut.body.calls).equal(expected);
|
||||||
|
});
|
||||||
|
it('throws if undefined', () => {
|
||||||
|
// arrange
|
||||||
|
const functionName = 'invalidFunction';
|
||||||
|
const callSequence = undefined;
|
||||||
|
const expectedError = `undefined call sequence in function "${functionName}"`;
|
||||||
|
// act
|
||||||
|
const act = () => new SharedFunctionBuilder()
|
||||||
|
.withName(functionName)
|
||||||
|
.withCallSequence(callSequence)
|
||||||
|
.createCallerFunction();
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
it('throws if empty', () => {
|
||||||
|
// arrange
|
||||||
|
const functionName = 'invalidFunction';
|
||||||
|
const callSequence = [ ];
|
||||||
|
const expectedError = `empty call sequence in function "${functionName}"`;
|
||||||
|
// act
|
||||||
|
const act = () => new SharedFunctionBuilder()
|
||||||
|
.withName(functionName)
|
||||||
|
.withCallSequence(callSequence)
|
||||||
|
.createCallerFunction();
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('sets type as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedType = FunctionBodyType.Calls;
|
||||||
|
// act
|
||||||
|
const sut = new SharedFunctionBuilder()
|
||||||
|
.createCallerFunction();
|
||||||
|
// assert
|
||||||
|
expect(sut.body.type).equal(expectedType);
|
||||||
|
});
|
||||||
|
it('code is undefined', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedCode = undefined;
|
||||||
|
// act
|
||||||
|
const sut = new SharedFunctionBuilder()
|
||||||
|
.createCallerFunction();
|
||||||
|
// assert
|
||||||
|
expect(sut.body.code).equal(expectedCode);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function runForEachFactoryMethod(
|
||||||
|
act: (action: (sut: SharedFunctionBuilder) => ISharedFunction) => void): void {
|
||||||
|
describe('createCallerFunction', () => {
|
||||||
|
const action = (builder: SharedFunctionBuilder) => builder.createCallerFunction();
|
||||||
|
act(action);
|
||||||
|
});
|
||||||
|
describe('createFunctionWithInlineCode', () => {
|
||||||
|
const action = (builder: SharedFunctionBuilder) => builder.createFunctionWithInlineCode();
|
||||||
|
act(action);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Using an abstraction here allows for easy refactorings in
|
||||||
|
parameters or moving between functional and object-oriented
|
||||||
|
solutions without refactorings all tests.
|
||||||
|
*/
|
||||||
class SharedFunctionBuilder {
|
class SharedFunctionBuilder {
|
||||||
private name = 'name';
|
private name = 'name';
|
||||||
private parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
|
private parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
|
||||||
|
private callSequence: readonly IFunctionCall[] = [ new FunctionCallStub() ];
|
||||||
private code = 'code';
|
private code = 'code';
|
||||||
private revertCode = 'revert-code';
|
private revertCode = 'revert-code';
|
||||||
|
|
||||||
public build(): SharedFunction {
|
public createCallerFunction(): ISharedFunction {
|
||||||
return new SharedFunction(
|
return createCallerFunction(
|
||||||
|
this.name,
|
||||||
|
this.parameters,
|
||||||
|
this.callSequence,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public createFunctionWithInlineCode(): ISharedFunction {
|
||||||
|
return createFunctionWithInlineCode(
|
||||||
this.name,
|
this.name,
|
||||||
this.parameters,
|
this.parameters,
|
||||||
this.code,
|
this.code,
|
||||||
this.revertCode,
|
this.revertCode,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public withName(name: string) {
|
public withName(name: string) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
return this;
|
return this;
|
||||||
@@ -127,4 +244,8 @@ class SharedFunctionBuilder {
|
|||||||
this.revertCode = revertCode;
|
this.revertCode = revertCode;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
public withCallSequence(callSequence: readonly IFunctionCall[]) {
|
||||||
|
this.callSequence = callSequence;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import 'mocha';
|
|||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { SharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/SharedFunctionCollection';
|
import { SharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/SharedFunctionCollection';
|
||||||
import { SharedFunctionStub } from '@tests/unit/stubs/SharedFunctionStub';
|
import { SharedFunctionStub } from '@tests/unit/stubs/SharedFunctionStub';
|
||||||
|
import { FunctionBodyType } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||||
|
import { FunctionCallStub } from '@tests/unit/stubs/FunctionCallStub';
|
||||||
|
|
||||||
describe('SharedFunctionCollection', () => {
|
describe('SharedFunctionCollection', () => {
|
||||||
describe('addFunction', () => {
|
describe('addFunction', () => {
|
||||||
@@ -19,7 +21,7 @@ describe('SharedFunctionCollection', () => {
|
|||||||
// arrange
|
// arrange
|
||||||
const functionName = 'duplicate-function';
|
const functionName = 'duplicate-function';
|
||||||
const expectedError = `function with name ${functionName} already exists`;
|
const expectedError = `function with name ${functionName} already exists`;
|
||||||
const func = new SharedFunctionStub()
|
const func = new SharedFunctionStub(FunctionBodyType.Code)
|
||||||
.withName('duplicate-function');
|
.withName('duplicate-function');
|
||||||
const sut = new SharedFunctionCollection();
|
const sut = new SharedFunctionCollection();
|
||||||
sut.addFunction(func);
|
sut.addFunction(func);
|
||||||
@@ -27,7 +29,6 @@ describe('SharedFunctionCollection', () => {
|
|||||||
const act = () => sut.addFunction(func);
|
const act = () => sut.addFunction(func);
|
||||||
// assert
|
// assert
|
||||||
expect(act).to.throw(expectedError);
|
expect(act).to.throw(expectedError);
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('getFunctionByName', () => {
|
describe('getFunctionByName', () => {
|
||||||
@@ -48,7 +49,7 @@ describe('SharedFunctionCollection', () => {
|
|||||||
// arrange
|
// arrange
|
||||||
const name = 'unique-name';
|
const name = 'unique-name';
|
||||||
const expectedError = `called function is not defined "${name}"`;
|
const expectedError = `called function is not defined "${name}"`;
|
||||||
const func = new SharedFunctionStub()
|
const func = new SharedFunctionStub(FunctionBodyType.Code)
|
||||||
.withName('unexpected-name');
|
.withName('unexpected-name');
|
||||||
const sut = new SharedFunctionCollection();
|
const sut = new SharedFunctionCollection();
|
||||||
sut.addFunction(func);
|
sut.addFunction(func);
|
||||||
@@ -57,18 +58,33 @@ describe('SharedFunctionCollection', () => {
|
|||||||
// assert
|
// assert
|
||||||
expect(act).to.throw(expectedError);
|
expect(act).to.throw(expectedError);
|
||||||
});
|
});
|
||||||
it('returns existing function', () => {
|
describe('returns existing function', () => {
|
||||||
|
it('when function with inline code is added', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const name = 'expected-function-name';
|
const expected = new SharedFunctionStub(FunctionBodyType.Code)
|
||||||
const expected = new SharedFunctionStub()
|
.withName('expected-function-name');
|
||||||
.withName(name);
|
|
||||||
const sut = new SharedFunctionCollection();
|
const sut = new SharedFunctionCollection();
|
||||||
sut.addFunction(new SharedFunctionStub().withName('another-function-name'));
|
|
||||||
sut.addFunction(expected);
|
|
||||||
// act
|
// act
|
||||||
const actual = sut.getFunctionByName(name);
|
sut.addFunction(expected);
|
||||||
|
const actual = sut.getFunctionByName(expected.name);
|
||||||
// assert
|
// assert
|
||||||
expect(actual).to.equal(expected);
|
expect(actual).to.equal(expected);
|
||||||
});
|
});
|
||||||
|
it('when calling function is added', () => {
|
||||||
|
// arrange
|
||||||
|
const callee = new SharedFunctionStub(FunctionBodyType.Code)
|
||||||
|
.withName('calleeFunction');
|
||||||
|
const caller = new SharedFunctionStub(FunctionBodyType.Calls)
|
||||||
|
.withName('callerFunction')
|
||||||
|
.withCalls(new FunctionCallStub().withFunctionName(callee.name));
|
||||||
|
const sut = new SharedFunctionCollection();
|
||||||
|
// act
|
||||||
|
sut.addFunction(callee);
|
||||||
|
sut.addFunction(caller);
|
||||||
|
const actual = sut.getFunctionByName(caller.name);
|
||||||
|
// assert
|
||||||
|
expect(actual).to.equal(caller);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,278 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||||
|
import { FunctionData } from 'js-yaml-loader!@/*';
|
||||||
|
import { SharedFunctionsParser } from '@/application/Parser/Script/Compiler/Function/SharedFunctionsParser';
|
||||||
|
import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
|
||||||
|
import { ParameterDefinitionDataStub } from '@tests/unit/stubs/ParameterDefinitionDataStub';
|
||||||
|
import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
|
||||||
|
import { FunctionCallDataStub } from '@tests/unit/stubs/FunctionCallDataStub';
|
||||||
|
|
||||||
|
describe('SharedFunctionsParser', () => {
|
||||||
|
describe('parseFunctions', () => {
|
||||||
|
describe('validates functions', () => {
|
||||||
|
it('throws if one of the functions is undefined', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = `some functions are undefined`;
|
||||||
|
const functions = [ FunctionDataStub.createWithCode(), undefined ];
|
||||||
|
const sut = new SharedFunctionsParser();
|
||||||
|
// act
|
||||||
|
const act = () => sut.parseFunctions(functions);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
it('throws when functions have same names', () => {
|
||||||
|
// arrange
|
||||||
|
const name = 'same-func-name';
|
||||||
|
const expectedError = `duplicate function name: "${name}"`;
|
||||||
|
const functions = [
|
||||||
|
FunctionDataStub.createWithCode().withName(name),
|
||||||
|
FunctionDataStub.createWithCode().withName(name),
|
||||||
|
];
|
||||||
|
const sut = new SharedFunctionsParser();
|
||||||
|
// act
|
||||||
|
const act = () => sut.parseFunctions(functions);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
describe('throws when when function have duplicate code', () => {
|
||||||
|
it('code', () => {
|
||||||
|
// arrange
|
||||||
|
const code = 'duplicate-code';
|
||||||
|
const expectedError = `duplicate "code" in functions: "${code}"`;
|
||||||
|
const functions = [
|
||||||
|
FunctionDataStub.createWithoutCallOrCodes().withName('func-1').withCode(code),
|
||||||
|
FunctionDataStub.createWithoutCallOrCodes().withName('func-2').withCode(code),
|
||||||
|
];
|
||||||
|
const sut = new SharedFunctionsParser();
|
||||||
|
// act
|
||||||
|
const act = () => sut.parseFunctions(functions);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
it('revertCode', () => {
|
||||||
|
// arrange
|
||||||
|
const revertCode = 'duplicate-revert-code';
|
||||||
|
const expectedError = `duplicate "revertCode" in functions: "${revertCode}"`;
|
||||||
|
const functions = [
|
||||||
|
FunctionDataStub.createWithoutCallOrCodes()
|
||||||
|
.withName('func-1').withCode('code-1').withRevertCode(revertCode),
|
||||||
|
FunctionDataStub.createWithoutCallOrCodes()
|
||||||
|
.withName('func-2').withCode('code-2').withRevertCode(revertCode),
|
||||||
|
];
|
||||||
|
const sut = new SharedFunctionsParser();
|
||||||
|
// act
|
||||||
|
const act = () => sut.parseFunctions(functions);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('ensures either call or code is defined', () => {
|
||||||
|
it('both code and call are defined', () => {
|
||||||
|
// arrange
|
||||||
|
const functionName = 'invalid-function';
|
||||||
|
const expectedError = `both "code" and "call" are defined in "${functionName}"`;
|
||||||
|
const invalidFunction = FunctionDataStub.createWithoutCallOrCodes()
|
||||||
|
.withName(functionName)
|
||||||
|
.withCode('code')
|
||||||
|
.withMockCall();
|
||||||
|
const sut = new SharedFunctionsParser();
|
||||||
|
// act
|
||||||
|
const act = () => sut.parseFunctions([ invalidFunction ]);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
it('neither code and call is defined', () => {
|
||||||
|
// arrange
|
||||||
|
const functionName = 'invalid-function';
|
||||||
|
const expectedError = `neither "code" or "call" is defined in "${functionName}"`;
|
||||||
|
const invalidFunction = FunctionDataStub.createWithoutCallOrCodes()
|
||||||
|
.withName(functionName);
|
||||||
|
const sut = new SharedFunctionsParser();
|
||||||
|
// act
|
||||||
|
const act = () => sut.parseFunctions([ invalidFunction ]);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('throws when parameters type is not as expected', () => {
|
||||||
|
const testCases = [
|
||||||
|
{
|
||||||
|
state: 'when not an array',
|
||||||
|
invalidType: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
state: 'when array but not of objects',
|
||||||
|
invalidType: [ 'a', { a: 'b'} ],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
for (const testCase of testCases) {
|
||||||
|
it(testCase.state, () => {
|
||||||
|
// arrange
|
||||||
|
const func = FunctionDataStub
|
||||||
|
.createWithCall()
|
||||||
|
.withParametersObject(testCase.invalidType as any);
|
||||||
|
const expectedError = `parameters must be an array of objects in function(s) "${func.name}"`;
|
||||||
|
const sut = new SharedFunctionsParser();
|
||||||
|
// act
|
||||||
|
const act = () => sut.parseFunctions([ func ]);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('rethrows including function name when FunctionParameter throws', () => {
|
||||||
|
// arrange
|
||||||
|
const invalidParameterName = 'invalid function p@r4meter name';
|
||||||
|
const functionName = 'functionName';
|
||||||
|
let parameterException: Error;
|
||||||
|
// tslint:disable-next-line:no-unused-expression
|
||||||
|
try { new FunctionParameter(invalidParameterName, false); } catch (e) { parameterException = e; }
|
||||||
|
const expectedError = `"${functionName}": ${parameterException.message}`;
|
||||||
|
const functionData = FunctionDataStub.createWithCode()
|
||||||
|
.withName(functionName)
|
||||||
|
.withParameters(new ParameterDefinitionDataStub().withName(invalidParameterName));
|
||||||
|
|
||||||
|
// act
|
||||||
|
const sut = new SharedFunctionsParser();
|
||||||
|
const act = () => sut.parseFunctions([ functionData ]);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('empty functions', () => {
|
||||||
|
it('returns empty collection', () => {
|
||||||
|
// arrange
|
||||||
|
const emptyValues = [ [], undefined ];
|
||||||
|
const sut = new SharedFunctionsParser();
|
||||||
|
for (const emptyFunctions of emptyValues) {
|
||||||
|
// act
|
||||||
|
const actual = sut.parseFunctions(emptyFunctions);
|
||||||
|
// assert
|
||||||
|
expect(actual).to.not.equal(undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('function with inline code', () => {
|
||||||
|
it('parses single function with code as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const name = 'function-name';
|
||||||
|
const expected = FunctionDataStub
|
||||||
|
.createWithoutCallOrCodes()
|
||||||
|
.withName(name)
|
||||||
|
.withCode('expected-code')
|
||||||
|
.withRevertCode('expected-revert-code')
|
||||||
|
.withParameters(
|
||||||
|
new ParameterDefinitionDataStub().withName('expectedParameter').withOptionality(true),
|
||||||
|
new ParameterDefinitionDataStub().withName('expectedParameter2').withOptionality(false),
|
||||||
|
);
|
||||||
|
const sut = new SharedFunctionsParser();
|
||||||
|
// act
|
||||||
|
const collection = sut.parseFunctions([ expected ]);
|
||||||
|
// expect
|
||||||
|
const actual = collection.getFunctionByName(name);
|
||||||
|
expectEqualName(expected, actual);
|
||||||
|
expectEqualParameters(expected, actual);
|
||||||
|
expectEqualFunctionWithInlineCode(expected, actual);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('function with calls', () => {
|
||||||
|
it('parses single function with call as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const call = new FunctionCallDataStub()
|
||||||
|
.withName('calleeFunction')
|
||||||
|
.withParameters({test: 'value'});
|
||||||
|
const data = FunctionDataStub.createWithoutCallOrCodes()
|
||||||
|
.withName('caller-function')
|
||||||
|
.withCall(call);
|
||||||
|
const sut = new SharedFunctionsParser();
|
||||||
|
// act
|
||||||
|
const collection = sut.parseFunctions([ data ]);
|
||||||
|
// expect
|
||||||
|
const actual = collection.getFunctionByName(data.name);
|
||||||
|
expectEqualName(data, actual);
|
||||||
|
expectEqualParameters(data, actual);
|
||||||
|
expectEqualCalls([ call ], actual);
|
||||||
|
});
|
||||||
|
it('parses multiple functions with call as expected', () => {
|
||||||
|
// arrange
|
||||||
|
const call1 = new FunctionCallDataStub()
|
||||||
|
.withName('calleeFunction1')
|
||||||
|
.withParameters({ param: 'value' });
|
||||||
|
const call2 = new FunctionCallDataStub()
|
||||||
|
.withName('calleeFunction2')
|
||||||
|
.withParameters( {param2: 'value2'});
|
||||||
|
const caller1 = FunctionDataStub.createWithoutCallOrCodes()
|
||||||
|
.withName('caller-function')
|
||||||
|
.withCall(call1);
|
||||||
|
const caller2 = FunctionDataStub.createWithoutCallOrCodes()
|
||||||
|
.withName('caller-function-2')
|
||||||
|
.withCall([ call1, call2 ]);
|
||||||
|
const sut = new SharedFunctionsParser();
|
||||||
|
// act
|
||||||
|
const collection = sut.parseFunctions([ caller1, caller2 ]);
|
||||||
|
// expect
|
||||||
|
const compiledCaller1 = collection.getFunctionByName(caller1.name);
|
||||||
|
expectEqualName(caller1, compiledCaller1);
|
||||||
|
expectEqualParameters(caller1, compiledCaller1);
|
||||||
|
expectEqualCalls([ call1 ], compiledCaller1);
|
||||||
|
const compiledCaller2 = collection.getFunctionByName(caller2.name);
|
||||||
|
expectEqualName(caller2, compiledCaller2);
|
||||||
|
expectEqualParameters(caller2, compiledCaller2);
|
||||||
|
expectEqualCalls([ call1, call2 ], compiledCaller2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function expectEqualName(
|
||||||
|
expected: FunctionDataStub, actual: ISharedFunction): void {
|
||||||
|
expect(actual.name).to.equal(expected.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectEqualParameters(
|
||||||
|
expected: FunctionDataStub, actual: ISharedFunction): void {
|
||||||
|
const actualSimplifiedParameters = actual.parameters.all.map((parameter) => ({
|
||||||
|
name: parameter.name,
|
||||||
|
optional: parameter.isOptional,
|
||||||
|
}));
|
||||||
|
const expectedSimplifiedParameters = expected.parameters?.map((parameter) => ({
|
||||||
|
name: parameter.name,
|
||||||
|
optional: parameter.optional || false,
|
||||||
|
})) || [];
|
||||||
|
expect(expectedSimplifiedParameters).to.deep.equal(actualSimplifiedParameters, 'Unequal parameters');
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectEqualFunctionWithInlineCode(
|
||||||
|
expected: FunctionData, actual: ISharedFunction): void {
|
||||||
|
expect(actual.body,
|
||||||
|
`function "${actual.name}" has no body`);
|
||||||
|
expect(actual.body.code,
|
||||||
|
`function "${actual.name}" has no code`);
|
||||||
|
expect(actual.body.code.do).to.equal(expected.code);
|
||||||
|
expect(actual.body.code.revert).to.equal(expected.revertCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectEqualCalls(
|
||||||
|
expected: FunctionCallDataStub[], actual: ISharedFunction) {
|
||||||
|
expect(actual.body,
|
||||||
|
`function "${actual.name}" has no body`);
|
||||||
|
expect(actual.body.calls,
|
||||||
|
`function "${actual.name}" has no calls`);
|
||||||
|
const actualSimplifiedCalls = actual.body.calls
|
||||||
|
.map((call) => ({
|
||||||
|
function: call.functionName,
|
||||||
|
params: call.args.getAllParameterNames().map((name) => ({
|
||||||
|
name, value: call.args.getArgument(name).argumentValue,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
const expectedSimplifiedCalls = expected
|
||||||
|
.map((call) => ({
|
||||||
|
function: call.function,
|
||||||
|
params: Object.keys(call.parameters).map((key) => (
|
||||||
|
{ name: key, value: call.parameters[key] }
|
||||||
|
)),
|
||||||
|
}));
|
||||||
|
expect(actualSimplifiedCalls).to.deep.equal(expectedSimplifiedCalls, 'Unequal calls');
|
||||||
|
}
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import 'mocha';
|
|
||||||
import { expect } from 'chai';
|
|
||||||
import { FunctionCall } from '@/application/Parser/Script/Compiler/FunctionCall/FunctionCall';
|
|
||||||
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/IFunctionCallArgumentCollection';
|
|
||||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
|
||||||
|
|
||||||
describe('FunctionCall', () => {
|
|
||||||
describe('ctor', () => {
|
|
||||||
it('throws when args is undefined', () => {
|
|
||||||
// arrange
|
|
||||||
const expectedError = 'undefined args';
|
|
||||||
const args = undefined;
|
|
||||||
// act
|
|
||||||
const act = () => new FunctionCallBuilder()
|
|
||||||
.withArgs(args)
|
|
||||||
.build();
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
|
||||||
it('throws when function name is undefined', () => {
|
|
||||||
// arrange
|
|
||||||
const expectedError = 'empty function name in function call';
|
|
||||||
const functionName = undefined;
|
|
||||||
// act
|
|
||||||
const act = () => new FunctionCallBuilder()
|
|
||||||
.withFunctionName(functionName)
|
|
||||||
.build();
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
class FunctionCallBuilder {
|
|
||||||
private functionName = 'functionName';
|
|
||||||
private args: IReadOnlyFunctionCallArgumentCollection = new FunctionCallArgumentCollectionStub();
|
|
||||||
|
|
||||||
public withFunctionName(functionName: string) {
|
|
||||||
this.functionName = functionName;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public withArgs(args: IReadOnlyFunctionCallArgumentCollection) {
|
|
||||||
this.args = args;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public build() {
|
|
||||||
return new FunctionCall(this.functionName, this.args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
import 'mocha';
|
|
||||||
import { expect } from 'chai';
|
|
||||||
import { FunctionCallData, FunctionCallParametersData } from 'js-yaml-loader!@/*';
|
|
||||||
import { FunctionCallCompiler } from '@/application/Parser/Script/Compiler/FunctionCall/FunctionCallCompiler';
|
|
||||||
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
|
||||||
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
|
|
||||||
import { ExpressionsCompilerStub } from '@tests/unit/stubs/ExpressionsCompilerStub';
|
|
||||||
import { SharedFunctionCollectionStub } from '@tests/unit/stubs/SharedFunctionCollectionStub';
|
|
||||||
import { SharedFunctionStub } from '@tests/unit/stubs/SharedFunctionStub';
|
|
||||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
|
||||||
|
|
||||||
describe('FunctionCallCompiler', () => {
|
|
||||||
describe('compileCall', () => {
|
|
||||||
describe('parameter validation', () => {
|
|
||||||
describe('call', () => {
|
|
||||||
it('throws with undefined call', () => {
|
|
||||||
// arrange
|
|
||||||
const expectedError = 'undefined call';
|
|
||||||
const call = undefined;
|
|
||||||
const functions = new SharedFunctionCollectionStub();
|
|
||||||
const sut = new MockableFunctionCallCompiler();
|
|
||||||
// act
|
|
||||||
const act = () => sut.compileCall(call, functions);
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
|
||||||
it('throws if call is not an object', () => {
|
|
||||||
// arrange
|
|
||||||
const expectedError = 'called function(s) must be an object';
|
|
||||||
const invalidCalls: readonly any[] = ['string', 33];
|
|
||||||
const sut = new MockableFunctionCallCompiler();
|
|
||||||
const functions = new SharedFunctionCollectionStub();
|
|
||||||
invalidCalls.forEach((invalidCall) => {
|
|
||||||
// act
|
|
||||||
const act = () => sut.compileCall(invalidCall, functions);
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('throws if call sequence has undefined call', () => {
|
|
||||||
// arrange
|
|
||||||
const expectedError = 'undefined function call';
|
|
||||||
const call: FunctionCallData[] = [
|
|
||||||
{ function: 'function-name' },
|
|
||||||
undefined,
|
|
||||||
];
|
|
||||||
const functions = new SharedFunctionCollectionStub();
|
|
||||||
const sut = new MockableFunctionCallCompiler();
|
|
||||||
// act
|
|
||||||
const act = () => sut.compileCall(call, functions);
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
|
||||||
it('throws if call sequence has undefined function name', () => {
|
|
||||||
// arrange
|
|
||||||
const expectedError = 'empty function name in function call';
|
|
||||||
const call: FunctionCallData[] = [
|
|
||||||
{ function: 'function-name' },
|
|
||||||
{ function: undefined },
|
|
||||||
];
|
|
||||||
const functions = new SharedFunctionCollectionStub();
|
|
||||||
const sut = new MockableFunctionCallCompiler();
|
|
||||||
// act
|
|
||||||
const act = () => sut.compileCall(call, functions);
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
|
||||||
it('throws if call parameters does not match function parameters', () => {
|
|
||||||
// arrange
|
|
||||||
const functionName = 'test-function-name';
|
|
||||||
const testCases = [
|
|
||||||
{
|
|
||||||
name: 'an unexpected parameter instead',
|
|
||||||
functionParameters: [ 'another-parameter' ],
|
|
||||||
callParameters: [ 'unexpected-parameter' ],
|
|
||||||
expectedError: `function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'an unexpected parameter when none required',
|
|
||||||
functionParameters: undefined,
|
|
||||||
callParameters: [ 'unexpected-parameter' ],
|
|
||||||
expectedError: `function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'expected and unexpected parameter',
|
|
||||||
functionParameters: [ 'expected-parameter' ],
|
|
||||||
callParameters: [ 'expected-parameter', 'unexpected-parameter' ],
|
|
||||||
expectedError: `function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
for (const testCase of testCases) {
|
|
||||||
it(testCase.name, () => {
|
|
||||||
const func = new SharedFunctionStub()
|
|
||||||
.withName('test-function-name')
|
|
||||||
.withParameterNames(...testCase.functionParameters);
|
|
||||||
let params: FunctionCallParametersData = {};
|
|
||||||
for (const parameter of testCase.callParameters) {
|
|
||||||
params = {...params, [parameter]: 'defined-parameter-value '};
|
|
||||||
}
|
|
||||||
const call: FunctionCallData = { function: func.name, parameters: params };
|
|
||||||
const functions = new SharedFunctionCollectionStub()
|
|
||||||
.withFunction(func);
|
|
||||||
const sut = new MockableFunctionCallCompiler();
|
|
||||||
// act
|
|
||||||
const act = () => sut.compileCall(call, functions);
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(testCase.expectedError);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('functions', () => {
|
|
||||||
it('throws with undefined functions', () => {
|
|
||||||
// arrange
|
|
||||||
const expectedError = 'undefined functions';
|
|
||||||
const call: FunctionCallData = { function: 'function-call' };
|
|
||||||
const functions = undefined;
|
|
||||||
const sut = new MockableFunctionCallCompiler();
|
|
||||||
// act
|
|
||||||
const act = () => sut.compileCall(call, functions);
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
|
||||||
it('throws if function does not exist', () => {
|
|
||||||
// arrange
|
|
||||||
const expectedError = 'function does not exist';
|
|
||||||
const call: FunctionCallData = { function: 'function-call' };
|
|
||||||
const functions: ISharedFunctionCollection = {
|
|
||||||
getFunctionByName: () => { throw new Error(expectedError); },
|
|
||||||
};
|
|
||||||
const sut = new MockableFunctionCallCompiler();
|
|
||||||
// act
|
|
||||||
const act = () => sut.compileCall(call, functions);
|
|
||||||
// assert
|
|
||||||
expect(act).to.throw(expectedError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
describe('builds code as expected', () => {
|
|
||||||
describe('builds single call as expected', () => {
|
|
||||||
// arrange
|
|
||||||
const parametersTestCases = [
|
|
||||||
{
|
|
||||||
name: 'empty parameters',
|
|
||||||
parameters: [],
|
|
||||||
callArgs: { },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'non-empty parameters',
|
|
||||||
parameters: [ 'param1', 'param2' ],
|
|
||||||
callArgs: { param1: 'value1', param2: 'value2' },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
for (const testCase of parametersTestCases) {
|
|
||||||
it(testCase.name, () => {
|
|
||||||
const expectedExecute = `expected-execute`;
|
|
||||||
const expectedRevert = `expected-revert`;
|
|
||||||
const func = new SharedFunctionStub().withParameterNames(...testCase.parameters);
|
|
||||||
const functions = new SharedFunctionCollectionStub().withFunction(func);
|
|
||||||
const call: FunctionCallData = { function: func.name, parameters: testCase.callArgs };
|
|
||||||
const args = new FunctionCallArgumentCollectionStub().withArguments(testCase.callArgs);
|
|
||||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
|
||||||
.setup(func.code, args, expectedExecute)
|
|
||||||
.setup(func.revertCode, args, expectedRevert);
|
|
||||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
|
||||||
// act
|
|
||||||
const actual = sut.compileCall(call, functions);
|
|
||||||
// assert
|
|
||||||
expect(actual.code).to.equal(expectedExecute);
|
|
||||||
expect(actual.revertCode).to.equal(expectedRevert);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
it('builds call sequence as expected', () => {
|
|
||||||
// arrange
|
|
||||||
const firstFunction = new SharedFunctionStub()
|
|
||||||
.withName('first-function-name')
|
|
||||||
.withCode('first-function-code')
|
|
||||||
.withRevertCode('first-function-revert-code');
|
|
||||||
const secondFunction = new SharedFunctionStub()
|
|
||||||
.withName('second-function-name')
|
|
||||||
.withParameterNames('testParameter')
|
|
||||||
.withCode('second-function-code')
|
|
||||||
.withRevertCode('second-function-revert-code');
|
|
||||||
const secondCallArguments = { testParameter: 'testValue' };
|
|
||||||
const call: FunctionCallData[] = [
|
|
||||||
{ function: firstFunction.name },
|
|
||||||
{ function: secondFunction.name, parameters: secondCallArguments },
|
|
||||||
];
|
|
||||||
const firstFunctionCallArgs = new FunctionCallArgumentCollectionStub();
|
|
||||||
const secondFunctionCallArgs = new FunctionCallArgumentCollectionStub()
|
|
||||||
.withArguments(secondCallArguments);
|
|
||||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
|
||||||
.setup(firstFunction.code, firstFunctionCallArgs, firstFunction.code)
|
|
||||||
.setup(firstFunction.revertCode, firstFunctionCallArgs, firstFunction.revertCode)
|
|
||||||
.setup(secondFunction.code, secondFunctionCallArgs, secondFunction.code)
|
|
||||||
.setup(secondFunction.revertCode, secondFunctionCallArgs, secondFunction.revertCode);
|
|
||||||
const expectedExecute = `${firstFunction.code}\n${secondFunction.code}`;
|
|
||||||
const expectedRevert = `${firstFunction.revertCode}\n${secondFunction.revertCode}`;
|
|
||||||
const functions = new SharedFunctionCollectionStub()
|
|
||||||
.withFunction(firstFunction)
|
|
||||||
.withFunction(secondFunction);
|
|
||||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
|
||||||
// act
|
|
||||||
const actual = sut.compileCall(call, functions);
|
|
||||||
// assert
|
|
||||||
expect(actual.code).to.equal(expectedExecute);
|
|
||||||
expect(actual.revertCode).to.equal(expectedRevert);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
class MockableFunctionCallCompiler extends FunctionCallCompiler {
|
|
||||||
constructor(expressionsCompiler: IExpressionsCompiler = new ExpressionsCompilerStub()) {
|
|
||||||
super(expressionsCompiler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,19 @@
|
|||||||
import 'mocha';
|
import 'mocha';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { ScriptCompiler } from '@/application/Parser/Script/Compiler/ScriptCompiler';
|
|
||||||
import { FunctionData } from 'js-yaml-loader!@/*';
|
import { FunctionData } from 'js-yaml-loader!@/*';
|
||||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||||
import { IFunctionCompiler } from '@/application/Parser/Script/Compiler/Function/IFunctionCompiler';
|
import { ScriptCompiler } from '@/application/Parser/Script/Compiler/ScriptCompiler';
|
||||||
import { IFunctionCallCompiler } from '@/application/Parser/Script/Compiler/FunctionCall/IFunctionCallCompiler';
|
import { ISharedFunctionsParser } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionsParser';
|
||||||
import { ICompiledCode } from '@/application/Parser/Script/Compiler/FunctionCall/ICompiledCode';
|
import { ICompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/ICompiledCode';
|
||||||
|
import { IFunctionCallCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/IFunctionCallCompiler';
|
||||||
import { LanguageSyntaxStub } from '@tests/unit/stubs/LanguageSyntaxStub';
|
import { LanguageSyntaxStub } from '@tests/unit/stubs/LanguageSyntaxStub';
|
||||||
import { ScriptDataStub } from '@tests/unit/stubs/ScriptDataStub';
|
import { ScriptDataStub } from '@tests/unit/stubs/ScriptDataStub';
|
||||||
import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
|
import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
|
||||||
import { FunctionCallCompilerStub } from '@tests/unit/stubs/FunctionCallCompilerStub';
|
import { FunctionCallCompilerStub } from '@tests/unit/stubs/FunctionCallCompilerStub';
|
||||||
import { FunctionCompilerStub } from '@tests/unit/stubs/FunctionCompilerStub';
|
import { SharedFunctionsParserStub } from '@tests/unit/stubs/SharedFunctionsParserStub';
|
||||||
import { SharedFunctionCollectionStub } from '@tests/unit/stubs/SharedFunctionCollectionStub';
|
import { SharedFunctionCollectionStub } from '@tests/unit/stubs/SharedFunctionCollectionStub';
|
||||||
|
import { parseFunctionCalls } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCallParser';
|
||||||
|
import { FunctionCallDataStub } from '@tests/unit/stubs/FunctionCallDataStub';
|
||||||
|
|
||||||
describe('ScriptCompiler', () => {
|
describe('ScriptCompiler', () => {
|
||||||
describe('ctor', () => {
|
describe('ctor', () => {
|
||||||
@@ -82,16 +84,17 @@ describe('ScriptCompiler', () => {
|
|||||||
code: 'expected-code',
|
code: 'expected-code',
|
||||||
revertCode: 'expected-revert-code',
|
revertCode: 'expected-revert-code',
|
||||||
};
|
};
|
||||||
const script = ScriptDataStub.createWithCall();
|
const call = new FunctionCallDataStub();
|
||||||
|
const script = ScriptDataStub.createWithCall(call);
|
||||||
const functions = [ FunctionDataStub.createWithCode().withName('existing-func') ];
|
const functions = [ FunctionDataStub.createWithCode().withName('existing-func') ];
|
||||||
const compiledFunctions = new SharedFunctionCollectionStub();
|
const compiledFunctions = new SharedFunctionCollectionStub();
|
||||||
const compilerMock = new FunctionCompilerStub();
|
const functionParserMock = new SharedFunctionsParserStub();
|
||||||
compilerMock.setup(functions, compiledFunctions);
|
functionParserMock.setup(functions, compiledFunctions);
|
||||||
const callCompilerMock = new FunctionCallCompilerStub();
|
const callCompilerMock = new FunctionCallCompilerStub();
|
||||||
callCompilerMock.setup(script.call, compiledFunctions, expected);
|
callCompilerMock.setup(parseFunctionCalls(call), compiledFunctions, expected);
|
||||||
const sut = new ScriptCompilerBuilder()
|
const sut = new ScriptCompilerBuilder()
|
||||||
.withFunctions(...functions)
|
.withFunctions(...functions)
|
||||||
.withFunctionCompiler(compilerMock)
|
.withSharedFunctionsParser(functionParserMock)
|
||||||
.withFunctionCallCompiler(callCompilerMock)
|
.withFunctionCallCompiler(callCompilerMock)
|
||||||
.build();
|
.build();
|
||||||
// act
|
// act
|
||||||
@@ -171,7 +174,7 @@ class ScriptCompilerBuilder {
|
|||||||
}
|
}
|
||||||
private functions: FunctionData[];
|
private functions: FunctionData[];
|
||||||
private syntax: ILanguageSyntax = new LanguageSyntaxStub();
|
private syntax: ILanguageSyntax = new LanguageSyntaxStub();
|
||||||
private functionCompiler: IFunctionCompiler = new FunctionCompilerStub();
|
private sharedFunctionsParser: ISharedFunctionsParser = new SharedFunctionsParserStub();
|
||||||
private callCompiler: IFunctionCallCompiler = new FunctionCallCompilerStub();
|
private callCompiler: IFunctionCallCompiler = new FunctionCallCompilerStub();
|
||||||
public withFunctions(...functions: FunctionData[]): ScriptCompilerBuilder {
|
public withFunctions(...functions: FunctionData[]): ScriptCompilerBuilder {
|
||||||
this.functions = functions;
|
this.functions = functions;
|
||||||
@@ -193,8 +196,8 @@ class ScriptCompilerBuilder {
|
|||||||
this.syntax = syntax;
|
this.syntax = syntax;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public withFunctionCompiler(functionCompiler: IFunctionCompiler): ScriptCompilerBuilder {
|
public withSharedFunctionsParser(SharedFunctionsParser: ISharedFunctionsParser): ScriptCompilerBuilder {
|
||||||
this.functionCompiler = functionCompiler;
|
this.sharedFunctionsParser = SharedFunctionsParser;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public withFunctionCallCompiler(callCompiler: IFunctionCallCompiler): ScriptCompilerBuilder {
|
public withFunctionCallCompiler(callCompiler: IFunctionCallCompiler): ScriptCompilerBuilder {
|
||||||
@@ -205,6 +208,6 @@ class ScriptCompilerBuilder {
|
|||||||
if (!this.functions) {
|
if (!this.functions) {
|
||||||
throw new Error('Function behavior not defined');
|
throw new Error('Function behavior not defined');
|
||||||
}
|
}
|
||||||
return new ScriptCompiler(this.functions, this.syntax, this.functionCompiler, this.callCompiler);
|
return new ScriptCompiler(this.functions, this.syntax, this.sharedFunctionsParser, this.callCompiler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { IExpressionEvaluationContext } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
|
import { IExpressionEvaluationContext } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
|
||||||
import { IPipelineCompiler } from '@/application/Parser/Script/Compiler/Expressions/Pipes/IPipelineCompiler';
|
import { IPipelineCompiler } from '@/application/Parser/Script/Compiler/Expressions/Pipes/IPipelineCompiler';
|
||||||
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/IFunctionCallArgumentCollection';
|
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
import { FunctionCallArgumentCollectionStub } from './FunctionCallArgumentCollectionStub';
|
import { FunctionCallArgumentCollectionStub } from './FunctionCallArgumentCollectionStub';
|
||||||
import { PipelineCompilerStub } from './PipelineCompilerStub';
|
import { PipelineCompilerStub } from './PipelineCompilerStub';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
|
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
|
||||||
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/IFunctionCallArgumentCollection';
|
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
import { scrambledEqual } from '@/application/Common/Array';
|
import { scrambledEqual } from '@/application/Common/Array';
|
||||||
|
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||||
|
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||||
|
|
||||||
|
|
||||||
export class ExpressionsCompilerStub implements IExpressionsCompiler {
|
export class ExpressionsCompilerStub implements IExpressionsCompiler {
|
||||||
@@ -8,32 +10,34 @@ export class ExpressionsCompilerStub implements IExpressionsCompiler {
|
|||||||
|
|
||||||
private readonly scenarios = new Array<ITestScenario>();
|
private readonly scenarios = new Array<ITestScenario>();
|
||||||
|
|
||||||
public setup(
|
public setup(scenario: ITestScenario): ExpressionsCompilerStub {
|
||||||
code: string,
|
this.scenarios.push(scenario);
|
||||||
parameters: IReadOnlyFunctionCallArgumentCollection,
|
|
||||||
result: string): ExpressionsCompilerStub {
|
|
||||||
this.scenarios.push({ code, parameters, result });
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
public setupToReturnFunctionCode(func: ISharedFunction, givenArgs: FunctionCallArgumentCollectionStub) {
|
||||||
|
return this
|
||||||
|
.setup({ givenCode: func.body.code.do, givenArgs, result: func.body.code.do })
|
||||||
|
.setup({ givenCode: func.body.code.revert, givenArgs, result: func.body.code.revert });
|
||||||
|
}
|
||||||
public compileExpressions(
|
public compileExpressions(
|
||||||
code: string,
|
code: string,
|
||||||
parameters: IReadOnlyFunctionCallArgumentCollection): string {
|
parameters: IReadOnlyFunctionCallArgumentCollection): string {
|
||||||
this.callHistory.push({ code, parameters});
|
this.callHistory.push({ code, parameters});
|
||||||
const scenario = this.scenarios.find((s) => s.code === code && deepEqual(s.parameters, parameters));
|
const scenario = this.scenarios.find((s) => s.givenCode === code && deepEqual(s.givenArgs, parameters));
|
||||||
if (scenario) {
|
if (scenario) {
|
||||||
return scenario.result;
|
return scenario.result;
|
||||||
}
|
}
|
||||||
const parametersAndValues = parameters
|
const parametersAndValues = parameters
|
||||||
.getAllParameterNames()
|
.getAllParameterNames()
|
||||||
.map((name) => `${name}=${parameters.getArgument(name).argumentValue}`)
|
.map((name) => `${name}=${parameters.getArgument(name).argumentValue}`)
|
||||||
.join('", "');
|
.join('\n\t');
|
||||||
return `[ExpressionsCompilerStub] code: "${code}" | parameters: "${parametersAndValues}"`;
|
return `[ExpressionsCompilerStub]\ncode: "${code}"\nparameters: ${parametersAndValues}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ITestScenario {
|
interface ITestScenario {
|
||||||
readonly code: string;
|
readonly givenCode: string;
|
||||||
readonly parameters: IReadOnlyFunctionCallArgumentCollection;
|
readonly givenArgs: IReadOnlyFunctionCallArgumentCollection;
|
||||||
readonly result: string;
|
readonly result: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,8 +50,8 @@ function deepEqual(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (const parameterName of expectedParameterNames) {
|
for (const parameterName of expectedParameterNames) {
|
||||||
const expectedValue = expected.getArgument(parameterName);
|
const expectedValue = expected.getArgument(parameterName).argumentValue;
|
||||||
const actualValue = expected.getArgument(parameterName);
|
const actualValue = actual.getArgument(parameterName).argumentValue;
|
||||||
if (expectedValue !== actualValue) {
|
if (expectedValue !== actualValue) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// tslint:disable-next-line:max-line-length
|
// tslint:disable-next-line:max-line-length
|
||||||
import { IFunctionCallArgument } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/IFunctionCallArgument';
|
import { IFunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgument';
|
||||||
import { IFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/IFunctionCallArgumentCollection';
|
import { IFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
import { FunctionCallArgumentStub } from './FunctionCallArgumentStub';
|
import { FunctionCallArgumentStub } from './FunctionCallArgumentStub';
|
||||||
|
|
||||||
export class FunctionCallArgumentCollectionStub implements IFunctionCallArgumentCollection {
|
export class FunctionCallArgumentCollectionStub implements IFunctionCallArgumentCollection {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// tslint:disable-next-line:max-line-length
|
// tslint:disable-next-line:max-line-length
|
||||||
import { IFunctionCallArgument } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/IFunctionCallArgument';
|
import { IFunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgument';
|
||||||
|
|
||||||
export class FunctionCallArgumentStub implements IFunctionCallArgument {
|
export class FunctionCallArgumentStub implements IFunctionCallArgument {
|
||||||
public parameterName = 'stub-parameter-name';
|
public parameterName = 'stub-parameter-name';
|
||||||
|
|||||||
@@ -1,26 +1,40 @@
|
|||||||
|
import { ICompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/ICompiledCode';
|
||||||
|
import { IFunctionCallCompiler } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/IFunctionCallCompiler';
|
||||||
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
||||||
import { ICompiledCode } from '@/application/Parser/Script/Compiler/FunctionCall/ICompiledCode';
|
import { IFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/IFunctionCall';
|
||||||
import { IFunctionCallCompiler } from '@/application/Parser/Script/Compiler/FunctionCall/IFunctionCallCompiler';
|
|
||||||
import { FunctionCallData, ScriptFunctionCallData } from 'js-yaml-loader!@/*';
|
|
||||||
|
|
||||||
interface Scenario { call: ScriptFunctionCallData; functions: ISharedFunctionCollection; result: ICompiledCode; }
|
interface IScenario {
|
||||||
|
calls: IFunctionCall[];
|
||||||
|
functions: ISharedFunctionCollection;
|
||||||
|
result: ICompiledCode;
|
||||||
|
}
|
||||||
|
|
||||||
export class FunctionCallCompilerStub implements IFunctionCallCompiler {
|
export class FunctionCallCompilerStub implements IFunctionCallCompiler {
|
||||||
public scenarios = new Array<Scenario>();
|
public scenarios = new Array<IScenario>();
|
||||||
public setup(call: ScriptFunctionCallData, functions: ISharedFunctionCollection, result: ICompiledCode) {
|
public setup(
|
||||||
this.scenarios.push({ call, functions, result });
|
calls: IFunctionCall[],
|
||||||
|
functions: ISharedFunctionCollection,
|
||||||
|
result: ICompiledCode) {
|
||||||
|
this.scenarios.push({ calls, functions, result });
|
||||||
}
|
}
|
||||||
public compileCall(
|
public compileCall(
|
||||||
call: ScriptFunctionCallData,
|
calls: IFunctionCall[],
|
||||||
functions: ISharedFunctionCollection): ICompiledCode {
|
functions: ISharedFunctionCollection): ICompiledCode {
|
||||||
const predefined = this.scenarios.find((s) => s.call === call && s.functions === functions);
|
const predefined = this.scenarios.find((s) => areEqual(s.calls, calls) && s.functions === functions);
|
||||||
if (predefined) {
|
if (predefined) {
|
||||||
return predefined.result;
|
return predefined.result;
|
||||||
}
|
}
|
||||||
const callee = functions.getFunctionByName((call as FunctionCallData).function);
|
|
||||||
return {
|
return {
|
||||||
code: callee.code,
|
code: 'function code [FunctionCallCompilerStub]',
|
||||||
revertCode: callee.revertCode,
|
revertCode: 'function revert code [FunctionCallCompilerStub]',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function areEqual(
|
||||||
|
first: readonly IFunctionCall[],
|
||||||
|
second: readonly IFunctionCall[]) {
|
||||||
|
const comparer = (a: IFunctionCall, b: IFunctionCall) => a.functionName.localeCompare(b.functionName);
|
||||||
|
const printSorted = (calls: readonly IFunctionCall[]) => JSON.stringify([...calls].sort(comparer));
|
||||||
|
return printSorted(first) === printSorted(second);
|
||||||
|
}
|
||||||
|
|||||||
15
tests/unit/stubs/FunctionCallDataStub.ts
Normal file
15
tests/unit/stubs/FunctionCallDataStub.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { FunctionCallData, FunctionCallParametersData } from 'js-yaml-loader!@/*';
|
||||||
|
|
||||||
|
export class FunctionCallDataStub implements FunctionCallData {
|
||||||
|
public function = 'callDatStubCalleeFunction';
|
||||||
|
public parameters: { [index: string]: string } = { testParameter : 'testArgument' };
|
||||||
|
|
||||||
|
public withName(functionName: string) {
|
||||||
|
this.function = functionName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public withParameters(parameters: FunctionCallParametersData) {
|
||||||
|
this.parameters = parameters;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
24
tests/unit/stubs/FunctionCallStub.ts
Normal file
24
tests/unit/stubs/FunctionCallStub.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { IFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/IFunctionCall';
|
||||||
|
import { FunctionCallArgumentCollectionStub } from './FunctionCallArgumentCollectionStub';
|
||||||
|
|
||||||
|
export class FunctionCallStub implements IFunctionCall {
|
||||||
|
public functionName = 'functionCallStub';
|
||||||
|
public args = new FunctionCallArgumentCollectionStub();
|
||||||
|
|
||||||
|
public withFunctionName(functionName: string) {
|
||||||
|
this.functionName = functionName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public withArgument(parameterName: string, argumentValue: string) {
|
||||||
|
this.args.withArgument(parameterName, argumentValue);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public withArguments(args: { readonly [index: string]: string }) {
|
||||||
|
this.args.withArguments(args);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public withArgumentCollection(args: FunctionCallArgumentCollectionStub) {
|
||||||
|
this.args = args;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
tests/unit/stubs/FunctionCodeStub.ts
Normal file
14
tests/unit/stubs/FunctionCodeStub.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { IFunctionCode } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||||
|
|
||||||
|
export class FunctionCodeStub implements IFunctionCode {
|
||||||
|
public do: string = 'do code (function-code-stub)';
|
||||||
|
public revert?: string = 'revert code (function-code-stub)';
|
||||||
|
public withDo(code: string) {
|
||||||
|
this.do = code;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public withRevert(revert: string) {
|
||||||
|
this.revert = revert;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FunctionData, ParameterDefinitionData, ScriptFunctionCallData } from 'js-yaml-loader!@/*';
|
import { FunctionData, ParameterDefinitionData, FunctionCallsData } from 'js-yaml-loader!@/*';
|
||||||
|
import { FunctionCallDataStub } from './FunctionCallDataStub';
|
||||||
|
|
||||||
export class FunctionDataStub implements FunctionData {
|
export class FunctionDataStub implements FunctionData {
|
||||||
public static createWithCode() {
|
public static createWithCode() {
|
||||||
@@ -6,7 +7,7 @@ export class FunctionDataStub implements FunctionData {
|
|||||||
.withCode('stub-code')
|
.withCode('stub-code')
|
||||||
.withRevertCode('stub-revert-code');
|
.withRevertCode('stub-revert-code');
|
||||||
}
|
}
|
||||||
public static createWithCall(call?: ScriptFunctionCallData) {
|
public static createWithCall(call?: FunctionCallsData) {
|
||||||
let instance = new FunctionDataStub();
|
let instance = new FunctionDataStub();
|
||||||
if (call) {
|
if (call) {
|
||||||
instance = instance.withCall(call);
|
instance = instance.withCall(call);
|
||||||
@@ -22,10 +23,10 @@ export class FunctionDataStub implements FunctionData {
|
|||||||
public name = 'functionDataStub';
|
public name = 'functionDataStub';
|
||||||
public code: string;
|
public code: string;
|
||||||
public revertCode: string;
|
public revertCode: string;
|
||||||
public call?: ScriptFunctionCallData;
|
public call?: FunctionCallsData;
|
||||||
public parameters?: readonly ParameterDefinitionData[];
|
public parameters?: readonly ParameterDefinitionData[];
|
||||||
|
|
||||||
private constructor() { }
|
private constructor() { /* use static factory methods to create an instance */ }
|
||||||
|
|
||||||
public withName(name: string) {
|
public withName(name: string) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@@ -46,12 +47,12 @@ export class FunctionDataStub implements FunctionData {
|
|||||||
this.revertCode = revertCode;
|
this.revertCode = revertCode;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public withCall(call: ScriptFunctionCallData) {
|
public withCall(call: FunctionCallsData) {
|
||||||
this.call = call;
|
this.call = call;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public withMockCall() {
|
public withMockCall() {
|
||||||
this.call = { function: 'func' };
|
this.call = new FunctionCallDataStub();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||||
import { ScriptFunctionCallData, ScriptData } from 'js-yaml-loader!@/*';
|
import { FunctionCallData, ScriptData } from 'js-yaml-loader!@/*';
|
||||||
|
import { FunctionCallDataStub } from '@tests/unit/stubs/FunctionCallDataStub';
|
||||||
|
|
||||||
export class ScriptDataStub implements ScriptData {
|
export class ScriptDataStub implements ScriptData {
|
||||||
public static createWithCode(): ScriptDataStub {
|
public static createWithCode(): ScriptDataStub {
|
||||||
@@ -7,7 +8,7 @@ export class ScriptDataStub implements ScriptData {
|
|||||||
.withCode('stub-code')
|
.withCode('stub-code')
|
||||||
.withRevertCode('stub-revert-code');
|
.withRevertCode('stub-revert-code');
|
||||||
}
|
}
|
||||||
public static createWithCall(call?: ScriptFunctionCallData): ScriptDataStub {
|
public static createWithCall(call?: FunctionCallData): ScriptDataStub {
|
||||||
let instance = new ScriptDataStub();
|
let instance = new ScriptDataStub();
|
||||||
if (call) {
|
if (call) {
|
||||||
instance = instance.withCall(call);
|
instance = instance.withCall(call);
|
||||||
@@ -27,7 +28,7 @@ export class ScriptDataStub implements ScriptData {
|
|||||||
public recommend = RecommendationLevel[RecommendationLevel.Standard].toLowerCase();
|
public recommend = RecommendationLevel[RecommendationLevel.Standard].toLowerCase();
|
||||||
public docs = ['hello.com'];
|
public docs = ['hello.com'];
|
||||||
|
|
||||||
private constructor() { }
|
private constructor() { /* use static methods for constructing */ }
|
||||||
|
|
||||||
public withName(name: string): ScriptDataStub {
|
public withName(name: string): ScriptDataStub {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@@ -46,10 +47,10 @@ export class ScriptDataStub implements ScriptData {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public withMockCall(): ScriptDataStub {
|
public withMockCall(): ScriptDataStub {
|
||||||
this.call = { function: 'func', parameters: [] };
|
this.call = new FunctionCallDataStub();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public withCall(call: ScriptFunctionCallData): ScriptDataStub {
|
public withCall(call: FunctionCallData): ScriptDataStub {
|
||||||
this.call = call;
|
this.call = call;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||||
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
||||||
import { SharedFunctionStub } from './SharedFunctionStub';
|
import { SharedFunctionStub } from './SharedFunctionStub';
|
||||||
|
import { FunctionBodyType } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||||
|
|
||||||
export class SharedFunctionCollectionStub implements ISharedFunctionCollection {
|
export class SharedFunctionCollectionStub implements ISharedFunctionCollection {
|
||||||
private readonly functions = new Map<string, ISharedFunction>();
|
private readonly functions = new Map<string, ISharedFunction>();
|
||||||
public withFunction(func: ISharedFunction) {
|
public withFunction(...funcs: readonly ISharedFunction[]) {
|
||||||
|
for (const func of funcs) {
|
||||||
this.functions.set(func.name, func);
|
this.functions.set(func.name, func);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
public getFunctionByName(name: string): ISharedFunction {
|
public getFunctionByName(name: string): ISharedFunction {
|
||||||
if (this.functions.has(name)) {
|
if (this.functions.has(name)) {
|
||||||
return this.functions.get(name);
|
return this.functions.get(name);
|
||||||
}
|
}
|
||||||
return new SharedFunctionStub()
|
return new SharedFunctionStub(FunctionBodyType.Code)
|
||||||
.withName(name)
|
.withName(name)
|
||||||
.withCode('code by SharedFunctionCollectionStub')
|
.withCode('code by SharedFunctionCollectionStub')
|
||||||
.withRevertCode('revert-code by SharedFunctionCollectionStub');
|
.withRevertCode('revert-code by SharedFunctionCollectionStub');
|
||||||
|
|||||||
@@ -1,13 +1,33 @@
|
|||||||
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
import { ISharedFunction, ISharedFunctionBody, FunctionBodyType } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||||
import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
||||||
import { FunctionParameterCollectionStub } from './FunctionParameterCollectionStub';
|
import { FunctionParameterCollectionStub } from './FunctionParameterCollectionStub';
|
||||||
|
import { FunctionCallStub } from './FunctionCallStub';
|
||||||
|
import { IFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/IFunctionCall';
|
||||||
|
|
||||||
export class SharedFunctionStub implements ISharedFunction {
|
export class SharedFunctionStub implements ISharedFunction {
|
||||||
public name = 'shared-function-stub-name';
|
public name = 'shared-function-stub-name';
|
||||||
public parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub()
|
public parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub()
|
||||||
.withParameterName('shared-function-stub-parameter-name');
|
.withParameterName('shared-function-stub-parameter-name');
|
||||||
public code = 'shared-function-stub-code';
|
|
||||||
public revertCode = 'shared-function-stub-revert-code';
|
private code = 'shared-function-stub-code';
|
||||||
|
private revertCode = 'shared-function-stub-revert-code';
|
||||||
|
private bodyType: FunctionBodyType = FunctionBodyType.Code;
|
||||||
|
private calls: IFunctionCall[] = [ new FunctionCallStub() ];
|
||||||
|
|
||||||
|
constructor(type: FunctionBodyType) {
|
||||||
|
this.bodyType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get body(): ISharedFunctionBody {
|
||||||
|
return {
|
||||||
|
type: this.bodyType,
|
||||||
|
code: this.bodyType === FunctionBodyType.Code ? {
|
||||||
|
do: this.code,
|
||||||
|
revert: this.revertCode,
|
||||||
|
} : undefined,
|
||||||
|
calls: this.bodyType === FunctionBodyType.Calls ? this.calls : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public withName(name: string) {
|
public withName(name: string) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@@ -25,6 +45,10 @@ export class SharedFunctionStub implements ISharedFunction {
|
|||||||
this.parameters = parameters;
|
this.parameters = parameters;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
public withCalls(...calls: readonly IFunctionCall[]) {
|
||||||
|
this.calls = [...calls];
|
||||||
|
return this;
|
||||||
|
}
|
||||||
public withParameterNames(...parameterNames: readonly string[]) {
|
public withParameterNames(...parameterNames: readonly string[]) {
|
||||||
let collection = new FunctionParameterCollectionStub();
|
let collection = new FunctionParameterCollectionStub();
|
||||||
for (const name of parameterNames) {
|
for (const name of parameterNames) {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { sequenceEqual } from '@/application/Common/Array';
|
import { sequenceEqual } from '@/application/Common/Array';
|
||||||
import { IFunctionCompiler } from '@/application/Parser/Script/Compiler/Function/IFunctionCompiler';
|
|
||||||
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
||||||
|
import { ISharedFunctionsParser } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionsParser';
|
||||||
import { FunctionData } from 'js-yaml-loader!@/*';
|
import { FunctionData } from 'js-yaml-loader!@/*';
|
||||||
import { SharedFunctionCollectionStub } from './SharedFunctionCollectionStub';
|
import { SharedFunctionCollectionStub } from './SharedFunctionCollectionStub';
|
||||||
|
|
||||||
export class FunctionCompilerStub implements IFunctionCompiler {
|
export class SharedFunctionsParserStub implements ISharedFunctionsParser {
|
||||||
private setupResults = new Array<{
|
private setupResults = new Array<{
|
||||||
functions: readonly FunctionData[],
|
functions: readonly FunctionData[],
|
||||||
result: ISharedFunctionCollection,
|
result: ISharedFunctionCollection,
|
||||||
@@ -14,7 +14,7 @@ export class FunctionCompilerStub implements IFunctionCompiler {
|
|||||||
this.setupResults.push( { functions, result });
|
this.setupResults.push( { functions, result });
|
||||||
}
|
}
|
||||||
|
|
||||||
public compileFunctions(functions: readonly FunctionData[]): ISharedFunctionCollection {
|
public parseFunctions(functions: readonly FunctionData[]): ISharedFunctionCollection {
|
||||||
const result = this.findResult(functions);
|
const result = this.findResult(functions);
|
||||||
return result || new SharedFunctionCollectionStub();
|
return result || new SharedFunctionCollectionStub();
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user