allow functions to call other functions #53

This commit is contained in:
undergroundwires
2021-01-16 13:26:41 +01:00
parent f1abd7682f
commit 7661575573
38 changed files with 1507 additions and 645 deletions

View File

@@ -0,0 +1,192 @@
import 'mocha';
import { expect } from 'chai';
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
import { FunctionData } from 'js-yaml-loader!*';
import { IFunctionCallCompiler } from '@/application/Parser/Script/Compiler/FunctionCall/IFunctionCallCompiler';
import { FunctionCompiler } from '@/application/Parser/Script/Compiler/Function/FunctionCompiler';
import { FunctionCallCompilerStub } from '../../../../../stubs/FunctionCallCompilerStub';
import { FunctionDataStub } from '../../../../../stubs/FunctionDataStub';
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);
});
it('throws when function parameters have same names', () => {
// arrange
const parameterName = 'duplicate-parameter';
const func = FunctionDataStub.createWithCall()
.withParameters(parameterName, parameterName);
const expectedError = `"${func.name}": duplicate parameter name: "${parameterName}"`;
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('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('expected-parameter-1', 'expected-parameter-2');
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(actual.parameters).to.deep.equal(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);
}
class MockableFunctionCompiler extends FunctionCompiler {
constructor(functionCallCompiler: IFunctionCallCompiler = new FunctionCallCompilerStub()) {
super(functionCallCompiler);
}
}

View File

@@ -0,0 +1,128 @@
import 'mocha';
import { expect } from 'chai';
import { SharedFunction } from '@/application/Parser/Script/Compiler/Function/SharedFunction';
describe('SharedFunction', () => {
describe('name', () => {
it('sets as expected', () => {
// arrange
const expected = 'expected-function-name';
// act
const sut = new SharedFunctionBuilder()
.withName(expected)
.build();
// assert
expect(sut.name).equal(expected);
});
it('throws if empty or undefined', () => {
// arrange
const expectedError = 'undefined function name';
const invalidValues = [ undefined, '' ];
for (const invalidValue of invalidValues) {
// act
const act = () => new SharedFunctionBuilder()
.withName(invalidValue)
.build();
// assert
expect(act).to.throw(expectedError);
}
});
});
describe('parameters', () => {
it('sets as expected', () => {
// arrange
const expected = [ 'expected-parameter' ];
// act
const sut = new SharedFunctionBuilder()
.withParameters(expected)
.build();
// assert
expect(sut.parameters).to.deep.equal(expected);
});
it('returns empty array if undefined', () => {
// arrange
const expected = [ ];
const value = undefined;
// act
const sut = new SharedFunctionBuilder()
.withParameters(value)
.build();
// assert
expect(sut.parameters).to.not.equal(undefined);
expect(sut.parameters).to.deep.equal(expected);
});
});
describe('code', () => {
it('sets as expected', () => {
// arrange
const expected = 'expected-code';
// act
const sut = new SharedFunctionBuilder()
.withCode(expected)
.build();
// assert
expect(sut.code).equal(expected);
});
it('throws if empty or undefined', () => {
// arrange
const functionName = 'expected-function-name';
const expectedError = `undefined function ("${functionName}") code`;
const invalidValues = [ undefined, '' ];
for (const invalidValue of invalidValues) {
// act
const act = () => new SharedFunctionBuilder()
.withName(functionName)
.withCode(invalidValue)
.build();
// assert
expect(act).to.throw(expectedError);
}
});
});
describe('revertCode', () => {
it('sets as expected', () => {
// arrange
const testData = [ 'expected-revert-code', undefined, '' ];
for (const data of testData) {
// act
const sut = new SharedFunctionBuilder()
.withRevertCode(data)
.build();
// assert
expect(sut.revertCode).equal(data);
}
});
});
});
class SharedFunctionBuilder {
private name = 'name';
private parameters: readonly string[] = [ 'parameter' ];
private code = 'code';
private revertCode = 'revert-code';
public build(): SharedFunction {
return new SharedFunction(
this.name,
this.parameters,
this.code,
this.revertCode,
);
}
public withName(name: string) {
this.name = name;
return this;
}
public withParameters(parameters: readonly string[]) {
this.parameters = parameters;
return this;
}
public withCode(code: string) {
this.code = code;
return this;
}
public withRevertCode(revertCode: string) {
this.revertCode = revertCode;
return this;
}
}

View File

@@ -0,0 +1,74 @@
import 'mocha';
import { expect } from 'chai';
import { SharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/SharedFunctionCollection';
import { SharedFunctionStub } from '../../../../../stubs/SharedFunctionStub';
describe('SharedFunctionCollection', () => {
describe('addFunction', () => {
it('throws if function is undefined', () => {
// arrange
const expectedError = 'undefined function';
const func = undefined;
const sut = new SharedFunctionCollection();
// act
const act = () => sut.addFunction(func);
// assert
expect(act).to.throw(expectedError);
});
it('throws if function with same name already exists', () => {
// arrange
const functionName = 'duplicate-function';
const expectedError = `function with name ${functionName} already exists`;
const func = new SharedFunctionStub()
.withName('duplicate-function');
const sut = new SharedFunctionCollection();
sut.addFunction(func);
// act
const act = () => sut.addFunction(func);
// assert
expect(act).to.throw(expectedError);
});
});
describe('getFunctionByName', () => {
it('throws if name is undefined', () => {
// arrange
const expectedError = 'undefined function name';
const invalidValues = [ undefined, '' ];
const sut = new SharedFunctionCollection();
for (const invalidValue of invalidValues) {
const name = invalidValue;
// act
const act = () => sut.getFunctionByName(name);
// assert
expect(act).to.throw(expectedError);
}
});
it('throws if function does not exist', () => {
// arrange
const name = 'unique-name';
const expectedError = `called function is not defined "${name}"`;
const func = new SharedFunctionStub()
.withName('unexpected-name');
const sut = new SharedFunctionCollection();
sut.addFunction(func);
// act
const act = () => sut.getFunctionByName(name);
// assert
expect(act).to.throw(expectedError);
});
it('returns existing function', () => {
// arrange
const name = 'expected-function-name';
const expected = new SharedFunctionStub()
.withName(name);
const sut = new SharedFunctionCollection();
sut.addFunction(new SharedFunctionStub().withName('another-function-name'));
sut.addFunction(expected);
// act
const actual = sut.getFunctionByName(name);
// assert
expect(actual).to.equal(expected);
});
});
});