allow functions to call other functions #53
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
import { FunctionData, InstructionHolder } from 'js-yaml-loader!*';
|
||||
import { SharedFunction } from './SharedFunction';
|
||||
import { SharedFunctionCollection } from './SharedFunctionCollection';
|
||||
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
||||
import { IFunctionCompiler } from './IFunctionCompiler';
|
||||
import { IFunctionCallCompiler } from '../FunctionCall/IFunctionCallCompiler';
|
||||
import { FunctionCallCompiler } from '../FunctionCall/FunctionCallCompiler';
|
||||
|
||||
export class FunctionCompiler implements IFunctionCompiler {
|
||||
public static readonly instance: IFunctionCompiler = new FunctionCompiler();
|
||||
protected constructor(
|
||||
private readonly functionCallCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance) {
|
||||
}
|
||||
public compileFunctions(functions: readonly FunctionData[]): ISharedFunctionCollection {
|
||||
const collection = new SharedFunctionCollection();
|
||||
if (!functions || !functions.length) {
|
||||
return collection;
|
||||
}
|
||||
ensureValidFunctions(functions);
|
||||
functions
|
||||
.filter((func) => hasCode(func))
|
||||
.forEach((func) => {
|
||||
const shared = new SharedFunction(func.name, func.parameters, func.code, func.revertCode);
|
||||
collection.addFunction(shared);
|
||||
});
|
||||
functions
|
||||
.filter((func) => hasCall(func))
|
||||
.forEach((func) => {
|
||||
const code = this.functionCallCompiler.compileCall(func.call, collection);
|
||||
const shared = new SharedFunction(func.name, func.parameters, code.code, code.revertCode);
|
||||
collection.addFunction(shared);
|
||||
});
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
function hasCode(data: FunctionData): boolean {
|
||||
return Boolean(data.code);
|
||||
}
|
||||
|
||||
function hasCall(data: FunctionData): boolean {
|
||||
return Boolean(data.call);
|
||||
}
|
||||
|
||||
|
||||
function ensureValidFunctions(functions: readonly FunctionData[]) {
|
||||
ensureNoUndefinedItem(functions);
|
||||
ensureNoDuplicatesInFunctionNames(functions);
|
||||
ensureNoDuplicatesInParameterNames(functions);
|
||||
ensureNoDuplicateCode(functions);
|
||||
ensureEitherCallOrCodeIsDefined(functions);
|
||||
}
|
||||
|
||||
function printList(list: readonly string[]): string {
|
||||
return `"${list.join('","')}"`;
|
||||
}
|
||||
|
||||
function ensureEitherCallOrCodeIsDefined(holders: readonly InstructionHolder[]) {
|
||||
// Ensure functions do not define both call and code
|
||||
const withBothCallAndCode = holders.filter((holder) => hasCode(holder) && hasCall(holder));
|
||||
if (withBothCallAndCode.length) {
|
||||
throw new Error(`both "code" and "call" are defined in ${printNames(withBothCallAndCode)}`);
|
||||
}
|
||||
// Ensure functions have either code or call
|
||||
const hasEitherCodeOrCall = holders.filter((holder) => !hasCode(holder) && !hasCall(holder));
|
||||
if (hasEitherCodeOrCall.length) {
|
||||
throw new Error(`neither "code" or "call" is defined in ${printNames(hasEitherCodeOrCall)}`);
|
||||
}
|
||||
}
|
||||
function printNames(holders: readonly InstructionHolder[]) {
|
||||
return printList(holders.map((holder) => holder.name));
|
||||
}
|
||||
|
||||
function ensureNoDuplicatesInFunctionNames(functions: readonly FunctionData[]) {
|
||||
const duplicateFunctionNames = getDuplicates(functions
|
||||
.map((func) => func.name.toLowerCase()));
|
||||
if (duplicateFunctionNames.length) {
|
||||
throw new Error(`duplicate function name: ${printList(duplicateFunctionNames)}`);
|
||||
}
|
||||
}
|
||||
function ensureNoUndefinedItem(functions: readonly FunctionData[]) {
|
||||
if (functions.some((func) => !func)) {
|
||||
throw new Error(`some functions are undefined`);
|
||||
}
|
||||
}
|
||||
function ensureNoDuplicatesInParameterNames(functions: readonly FunctionData[]) {
|
||||
const functionsWithParameters = functions
|
||||
.filter((func) => func.parameters && func.parameters.length > 0);
|
||||
for (const func of functionsWithParameters) {
|
||||
const duplicateParameterNames = getDuplicates(func.parameters);
|
||||
if (duplicateParameterNames.length) {
|
||||
throw new Error(`"${func.name}": duplicate parameter name: ${printList(duplicateParameterNames)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
function ensureNoDuplicateCode(functions: readonly FunctionData[]) {
|
||||
const duplicateCodes = getDuplicates(functions
|
||||
.map((func) => func.code)
|
||||
.filter((code) => code),
|
||||
);
|
||||
if (duplicateCodes.length > 0) {
|
||||
throw new Error(`duplicate "code" in functions: ${printList(duplicateCodes)}`);
|
||||
}
|
||||
const duplicateRevertCodes = getDuplicates(functions
|
||||
.filter((func) => func.revertCode)
|
||||
.map((func) => func.revertCode));
|
||||
if (duplicateRevertCodes.length > 0) {
|
||||
throw new Error(`duplicate "revertCode" in functions: ${printList(duplicateRevertCodes)}`);
|
||||
}
|
||||
}
|
||||
|
||||
function getDuplicates(texts: readonly string[]): string[] {
|
||||
return texts.filter((item, index) => texts.indexOf(item) !== index);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { FunctionData } from 'js-yaml-loader!*';
|
||||
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
||||
|
||||
export interface IFunctionCompiler {
|
||||
compileFunctions(functions: readonly FunctionData[]): ISharedFunctionCollection;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface ISharedFunction {
|
||||
readonly name: string;
|
||||
readonly parameters?: readonly string[];
|
||||
readonly code: string;
|
||||
readonly revertCode?: string;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ISharedFunction } from './ISharedFunction';
|
||||
|
||||
export interface ISharedFunctionCollection {
|
||||
getFunctionByName(name: string): ISharedFunction;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ISharedFunction } from './ISharedFunction';
|
||||
|
||||
export class SharedFunction implements ISharedFunction {
|
||||
constructor(
|
||||
public readonly name: string,
|
||||
public readonly parameters: readonly string[],
|
||||
public readonly code: string,
|
||||
public readonly revertCode: string,
|
||||
) {
|
||||
if (!name) { throw new Error('undefined function name'); }
|
||||
if (!code) { throw new Error(`undefined function ("${name}") code`); }
|
||||
this.parameters = parameters || [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ISharedFunction } from './ISharedFunction';
|
||||
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
||||
|
||||
export class SharedFunctionCollection implements ISharedFunctionCollection {
|
||||
private readonly functionsByName = new Map<string, ISharedFunction>();
|
||||
|
||||
public addFunction(func: ISharedFunction): void {
|
||||
if (!func) { throw new Error('undefined function'); }
|
||||
if (this.functionsByName.has(func.name)) {
|
||||
throw new Error(`function with name ${func.name} already exists`);
|
||||
}
|
||||
this.functionsByName.set(func.name, func);
|
||||
}
|
||||
|
||||
public getFunctionByName(name: string): ISharedFunction {
|
||||
if (!name) { throw Error('undefined function name'); }
|
||||
const func = this.functionsByName.get(name);
|
||||
if (!func) {
|
||||
throw new Error(`called function is not defined "${name}"`);
|
||||
}
|
||||
return func;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user