/*
* 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 { RenderFunction, ClickFunction, UnionValue, ICellAddress, IArea, FunctionUnion } from 'treb-base-types';
import type { ExpressionUnit } from 'treb-parser';
/**
* FIXME: possible to add stuff in here if we need it
*/
export interface FunctionContext {
address: ICellAddress;
area?: IArea;
/** application function for fp functions */
apply?: (fn: FunctionUnion, args: UnionValue[]) => UnionValue;
}
// FIXME: at least some of this could move to base types
/*
export enum ReturnType {
value, reference
}
*/
/**
* descriptor for an individual argument
*/
export interface ArgumentDescriptor {
/** used in tooltips and function descriptions */
name?: string;
/** used in tooltips and function descriptions */
description?: string;
/** default value in tooltips and function descriptions. this has
* no effect on the actual function. if you need a default value in
* the function, use a default argument in the typescript declaration
* (i.e. the normal way you'd set a default argument)
*/
default?: number|string|boolean;
/** @internal TODO: rename */
passthrough?: boolean;
/**
* allows error values to propagate. otherwise, a function will
* return an #ARG error if any arguments contain errors. used in
* functions like IsError and IfError.
*/
allow_error?: boolean;
/**
* this argument (reference) should be treated as an address, not resolved.
* used in reference + lookup functions like Offset
*/
address?: boolean;
/**
* require the argument to be a union
*/
boxed?: boolean;
/**
* this argument repeasts. this has no impact on the function descriptor
* but it's useful to know for clients.
*/
repeat?: boolean;
/**
* argument will return metadata about the cell
*/
metadata?: boolean;
/**
* automate array application. set this flag to allow unrolling of array
* parameters. "unrolling" means if you call a function with an array
* parameter, the function will be called once for each value in the array,
* and will return an array.
*
* if this flag is set in any argument descriptor in a function, we'll
* apply arrays. that's done when the function is installed.
*
* FIXME: this should maybe be the default, and we have an !unroll flag
*/
unroll?: boolean;
}
/**
* @internal
*/
export interface ContextResult {
context: Record;
/**
* rewrite args
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args: any[];
/** possibly rewrite argument descriptors, not required */
argument_descriptors?: ArgumentDescriptor[];
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type FunctionImplementation = (this: FunctionContext|undefined, ...args: any[]) => UnionValue;
/**
* merging the old function descriptor and decorated function types, since
* there's a good deal of overlap and we spend a lot of effort keeping them
* in sync.
*
* this is a wrapper object that contains the function and (mostly optional)
* metadata.
*/
export interface CompositeFunctionDescriptor {
/**
* description for the function wizard
*/
description?: string;
/**
* list of arguments, for the function wizard and tooltip
*/
arguments?: ArgumentDescriptor[];
/**
* volatile: value changes on every recalc, even if dependencies
* don't change
*/
volatile?: boolean;
/**
* FIXME: we need to unify type with what's in the cell class
*/
render?: RenderFunction; // (options: any) => boolean;
click?: ClickFunction; // (options: any) => {value?: any };
/**
* the actual function. if this is an object member and needs access
* to the containing instance, make sure to bind it to that instance.
*
* otherwise, `this` will be bound to a `CalculationContext` object
* if your function is defined as a function (i.e. not an arrow function).
*
*/
fn: FunctionImplementation;
/**
* limited visibility
*
* internal functions do not show up in the spreadsheet. we have an
* annotation value which should be usef in the future but it's not
* implemented yet.
*
* should we allow these functions to be used, and just not tooltip them;
* or block them entirely? for now we'll do the former as it's helpful to
* defug.
*/
visibility?: 'internal'|'annotation';
/**
* for the future
*/
category?: string[];
/*
* if we want to collapse imports (convert functions -> literal calculated
* values) this flag indicates which functions should be converted. we could
* theoretically use the category flag but that could be fuzzy, so we will
* have an explicit flag. applies only to the MC functions atm.
*/
extension?: boolean;
/**
* there is some set of functions that need an "_xlfn." prefix on export.
* I'm not sure why or where the list comes from, but we want to flag
* those functions so we can export them properly.
*/
xlfn?: boolean;
/**
* support returning references. 'value' is default. FIXME: could just be a bool?
*/
return_type?: 'reference'|'value';
/**
* @internal
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export?: (...args: any[]) => string;
/**
* pass through UI functions, if possible. this is for lambda and let; we
* want to pass through sparklines, if a sparkline is the first function call
*
* @internal
*/
pass_through_ui?: 'direct' | 'indirect';
/**
* flag indicating we've unrolled this function. it's possible functions
* will run through the registration process more than once and we don't
* want to have extra depth.
*
* @internal
*/
unrolled?: boolean;
/**
* this is new: custom binding context for lambda functions (lambda, let,
* and maybe others?) still working out the semantics of this.
*
* @internal
*/
create_binding_context?: (context: {
args: ExpressionUnit[];
descriptors: ArgumentDescriptor[];
}) => ContextResult | undefined;
/** flag indicating this function needs fp support */
fp?: boolean;
}
export interface FunctionMap {
[index: string]: CompositeFunctionDescriptor;
}
/**
* the stored value also includes a canonical name. this used to be separate
* from the registered name (because those were functions, and had to adhere
* to language rules) but now we use arbitrary tokens, so we can consolidate.
*/
export interface ExtendedFunctionDescriptor extends CompositeFunctionDescriptor {
canonical_name: string;
}
export interface ExtendedFunctionMap {
[index: string]: ExtendedFunctionDescriptor;
}
export type IntrinsicValue = number|string|boolean|undefined;