/* * This file is part of TREB. * * TREB is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any * later version. * * TREB is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along * with TREB. If not, see . * * Copyright 2022-2026 trebco, llc. * info@treb.app * */ import type { ExtendedFunctionDescriptor, CompositeFunctionDescriptor, FunctionMap, ExtendedFunctionMap } from './descriptors'; import { ApplyArrayX } from './utilities'; /** * singleton (static instance) of function library; includes utility methods */ export class FunctionLibrary { /** the actual functions */ protected functions: ExtendedFunctionMap = {}; public RegisterMap(map: Map) { const pojo: Record = {}; for (const [name, descriptor] of map.entries()) { pojo[name] = descriptor; } return this.Register(pojo); } /** * register one or more functions. keys in the passed object are * considered the canonical function names, and must be (icase) unique. */ public Register(...maps: FunctionMap[]): void { for (const map of maps) { for (const name of Object.keys(map)) { // some rules for names. the length thing is arbitrary, but come on. // leading ascii-letter is also kind of arbitrary, but it can't be // a number. the legal characters thing is probably broken: we should // allow extended characters. if (/[^a-zA-Z0-9._]/.test(name)) { throw new Error('invalid function name (invalid character)'); } if (name.length > 255) { throw new Error('invalid function name (too long, > 255)'); } if (/^[^a-zA-Z]/.test(name)) { throw new Error('invalid function name (start with an ascii letter)'); } const normalized = name.toLowerCase(); if (this.functions[normalized]) { throw new Error(`function name (${normalized}) is already in use`); } const descriptor = map[name] as ExtendedFunctionDescriptor; descriptor.canonical_name = name.toUpperCase(); if (!descriptor.unrolled) { // potentially apply a wrapper to unroll loops const unroll = descriptor.arguments?.map(test => !!test.unroll); if (unroll?.some(value => !!value)) { descriptor.fn = ApplyArrayX(unroll, descriptor.fn); descriptor.unrolled = true; } // we could set that flag even if we don't do anything, // sp we don't check again (minor optimization) } this.functions[normalized] = descriptor; } } } /** lookup function (actual map is protected) */ public Get(name: string) { const normalized = name.toLowerCase(); return this.functions[normalized]; } /** get a list, for AC services */ public List(filter_internal = true) { const list: ExtendedFunctionMap = {}; for (const key of Object.keys(this.functions)) { if (filter_internal && this.functions[key].visibility === 'internal') { continue; } list[key] = this.functions[key]; } return list; } /** * create an alias. we clone the descriptor and use the alias as the * canonical name, so should work better than just a pointer. */ public Alias(name: string, reference: string, warn = false) { const ref = this.Get(reference); if (!ref) { if (warn) { console.warn(`referenced function ${reference} does not exist`); return; } else { throw new Error(`referenced function ${reference} does not exist`); } } this.Register({[name]: {...ref} as CompositeFunctionDescriptor}); } }