diff --git a/.gitignore b/.gitignore
index f63fec173..aaa6e36c2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,6 @@ dev*.html
# Doc snippets are regenerated from the docs before every test run
# (the `test:*` scripts run `snippets:extract` first), so they are never committed.
/test-utils/snippets/*.generated.ts
+
+# Generated at docs:build from built-in-functions.tmpl.md (HF-249 single-source); do not commit.
+docs/guide/built-in-functions.md
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7d6a67192..0e9c62181 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Added the `getAvailableFunctions()` and `getFunctionDetails()` methods (both static and instance) for retrieving function metadata. [#1692](https://github.com/handsontable/hyperformula/pull/1692)
- Added new functions: VSTACK, HSTACK. [#1698](https://github.com/handsontable/hyperformula/pull/1698)
- Added a new function: `XIRR`. [#1701](https://github.com/handsontable/hyperformula/pull/1701)
+- The built-in functions guide table is now generated from the function metadata API (single source of truth), and now also documents OFFSET and VERSION. [#1692](https://github.com/handsontable/hyperformula/pull/1692)
- Added an Indonesian (Bahasa Indonesia) language pack. [#1674](https://github.com/handsontable/hyperformula/pull/1674)
- Added a `stringifyCurrency` config option that lets you plug in a custom currency formatter for the `TEXT` function. [#1145](https://github.com/handsontable/hyperformula/issues/1145)
diff --git a/DEV_DOCS.md b/DEV_DOCS.md
index c4c5435c6..ea01b494f 100644
--- a/DEV_DOCS.md
+++ b/DEV_DOCS.md
@@ -104,6 +104,18 @@ Adding a built-in function is similar to adding a [custom function](docs/guide/c
4. Add translations to all language files in `src/i18n/languages/`.
5. Add tests in `test/unit/interpreter/`.
+The built-in functions guide page `docs/guide/built-in-functions.md` is a **build product** and is
+**gitignored** — do not commit or hand-edit it. It is generated from two committed sources:
+
+- **prose** (intro, category list, footnotes) lives in `docs/guide/built-in-functions.tmpl.md`, which carries the
+ empty `AUTOGENERATED:FUNCTIONS` markers;
+- the **function table** is rendered from HyperFormula's API (`getAvailableFunctions`/`getFunctionDetails`), i.e.
+ from the catalogue in `src/interpreter/functionMetadata/`.
+
+`npm run docs:generate-functions` splices the table into the template and writes the gitignored page; it runs
+automatically as the first step of `npm run docs:build` and `npm run docs:dev`. To change the wording, edit the
+template; to change a function's row, edit its catalogue metadata.
+
## Internationalization and function translations
HyperFormula supports internationalization and provides localized function names for all built-in languages. Translation files live in `src/i18n/languages/`. New functions must include translations for all built-in languages.
diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md
deleted file mode 100644
index 97aab9ffa..000000000
--- a/docs/guide/built-in-functions.md
+++ /dev/null
@@ -1,578 +0,0 @@
-# Built-in functions
-
-
-
-
-
-## Overview
-
-HyperFormula comes with an extensive library of pre-built functions. You can use
-them to create complex formulas for any business application. Formula syntax and
-logic of function are similar to what is considered the standard in modern
-spreadsheet software. That is because a spreadsheet is probably the most
-universal software ever created. We wanted the same flexibility for HyperFormula
-but without the constraints of the spreadsheet UI.
-
-Each of HyperFormula's built-in function names is available in [17 languages](localizing-functions.md#list-of-supported-languages) and [custom language packs](localizing-functions.md) can be added.
-
-The latest version of HyperFormula has an extensive collection of
-**{{ $page.functionsCount }}** functions grouped into categories:
-
-- [Array manipulation](#array-manipulation)
-- [Database](#database)
-- [Date and time](#date-and-time)
-- [Engineering](#engineering)
-- [Information](#information)
-- [Financial](#financial)
-- [Logical](#logical)
-- [Lookup and reference](#lookup-and-reference)
-- [Math and trigonometry](#math-and-trigonometry)
-- [Matrix functions](#matrix-functions)
-- [Operator](#operator)
-- [Statistical](#statistical)
-- [Text](#text)
-
-_Some categories such as compatibility and cube are yet to be supported._
-
-::: tip
-You can modify the built-in functions or create your own, by adding a [custom function](custom-functions.md).
-:::
-
-## Function metadata
-
-HyperFormula exposes metadata about the functions it knows — localized and
-canonical names, category, a short description, and the parameter list — which is
-useful for building UI such as a function picker or a reference panel. Two
-methods return it, each available as a **static** method (covering the
-globally-registered built-ins and their aliases) and as an **instance** method
-(which additionally sees the instance's [custom functions](custom-functions.md)
-and defaults to its configured language):
-
-- [`getAvailableFunctions()`](../api/classes/hyperformula.md#getavailablefunctions)
- — a short list with one entry per function.
-- [`getFunctionDetails()`](../api/classes/hyperformula.md#getfunctiondetails)
- — the full details of a single function, including its parameters.
-
-```js
-// static methods take the language code explicitly
-const functions = HyperFormula.getAvailableFunctions('enGB');
-const sumDetails = HyperFormula.getFunctionDetails('SUM', 'enGB');
-
-// instance methods use the instance's configured language
-const hf = HyperFormula.buildEmpty({ language: 'enGB' });
-const instanceFunctions = hf.getAvailableFunctions();
-const instanceSumDetails = hf.getFunctionDetails('SUM');
-```
-
-See the linked API reference for each method's exact return shape and for how
-custom functions are reported.
-
-## List of available functions
-
-Total number of functions: **{{ $page.functionsCount }}**
-
-### Array manipulation
-
-| Function ID | Description | Syntax |
-|:----------------|:-----------------------------------------------------------------|:-----------------------------------------------------------|
-| ARRAYFORMULA | Enables the array arithmetic mode for a single formula. | ARRAYFORMULA(Formula) |
-| FILTER | Filters an array, based on multiple conditions (boolean arrays). | FILTER(SourceArray, BoolArray1, BoolArray2, ...BoolArrayN) |
-| ARRAY_CONSTRAIN | Truncates an array to given dimensions. | ARRAY_CONSTRAIN(Array, Height, Width) |
-| SEQUENCE | Returns an array of sequential numbers. | SEQUENCE(Rows, [Cols], [Start], [Step]) |
-| VSTACK | Stacks arrays vertically into a single array. | VSTACK(Array1, [Array2], ...[ArrayN]) |
-| HSTACK | Stacks arrays horizontally into a single array. | HSTACK(Array1, [Array2], ...[ArrayN]) |
-
-### Date and time
-
-| Function ID | Description | Syntax |
-|:-----------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------|
-| DATE | Returns the specified date as the number of full days since [`nullDate`](../api/interfaces/configparams.md#nulldate). | DATE(Year, Month, Day) |
-| DATEDIF | Calculates distance between two dates.
Supported units: "D" (days), "M" (months), "Y" (years), "MD" (days ignoring months and years), "YM" (months ignoring years), or "YD" (days ignoring years). | DATEDIF(Date1, Date2, Unit) |
-| DATEVALUE | Parses a date string and returns it as the number of full days since [`nullDate`](../api/interfaces/configparams.md#nulldate).
Accepts formats set by the [`dateFormats`](../api/interfaces/configparams.md#dateformats) option. | DATEVALUE(Datestring) |
-| DAY | Returns the day of the given date value. | DAY(Number) |
-| DAYS | Calculates the difference between two date values. | DAYS(Date2, Date1) |
-| DAYS360 | Calculates the difference between two date values in days, in 360-day basis. | DAYS360(Date2, Date1[, Format]) |
-| EDATE | Shifts the given startdate by given number of months and returns it as the number of full days since [`nullDate`](../api/interfaces/configparams.md#nulldate).[^non-odff] | EDATE(Startdate, Months) |
-| EOMONTH | Returns the date of the last day of a month which falls months away from the start date. Returns the value in the form of number of full days since [`nullDate`](../api/interfaces/configparams.md#nulldate).[^non-odff] | EOMONTH(Startdate, Months) |
-| HOUR | Returns hour component of given time. | HOUR(Time) |
-| INTERVAL | Returns interval string from given number of seconds. | INTERVAL(Seconds) |
-| ISOWEEKNUM | Returns an ISO week number that corresponds to the week of year. | ISOWEEKNUM(Date) |
-| MINUTE | Returns minute component of given time. | MINUTE(Time) |
-| MONTH | Returns the month for the given date value. | MONTH(Number) |
-| NETWORKDAYS | Returns the number of working days between two given dates. | NETWORKDAYS(Date1, Date2[, Holidays]) |
-| NETWORKDAYS.INTL | Returns the number of working days between two given dates. | NETWORKDAYS.INTL(Date1, Date2[, Mode [, Holidays]]) |
-| NOW | Returns current date + time as a number of days since [`nullDate`](../api/interfaces/configparams.md#nulldate). | NOW() |
-| SECOND | Returns second component of given time. | SECOND(Time) |
-| TIME | Returns the number that represents a given time as a fraction of full day. | TIME(Hour, Minute, Second) |
-| TIMEVALUE | Parses a time string and returns a number that represents it as a fraction of a full day.
Accepts formats set by the [`timeFormats`](../api/interfaces/configparams.md#timeformats) option. | TIMEVALUE(Timestring) |
-| TODAY | Returns an integer representing the current date as the number of full days since [`nullDate`](../api/interfaces/configparams.md#nulldate). | TODAY() |
-| WEEKDAY | Computes a number between 1-7 representing the day of week. | WEEKDAY(Date, Type) |
-| WEEKNUM | Returns a week number that corresponds to the week of year. | WEEKNUM(Date, Type) |
-| WORKDAY | Returns the working day number of days from start day. | WORKDAY(Date, Shift[, Holidays]) |
-| WORKDAY.INTL | Returns the working day number of days from start day. | WORKDAY(Date, Shift[, Mode[, Holidays]]) |
-| YEAR | Returns the year as a number according to the internal calculation rules. | YEAR(Number) |
-| YEARFRAC | Computes the difference between two date values, in fraction of years. | YEARFRAC(Date2, Date1[, Format]) |
-
-### Database
-
-| Function ID | Description | Syntax |
-|:------------|:----------------------------------------------------------------------------------------------------------------|:----------------------------------|
-| DAVERAGE | Returns the average of all values in a database field that match the given criteria. | DAVERAGE(Database, Field, Criteria) |
-| DCOUNT | Counts the cells containing numbers in a database field that match the given criteria. | DCOUNT(Database, Field, Criteria) |
-| DCOUNTA | Counts the non-empty cells in a database field that match the given criteria. | DCOUNTA(Database, Field, Criteria) |
-| DGET | Returns the single value from a database field that matches the given criteria. Returns #VALUE! if no records match, and #NUM! if more than one record matches. | DGET(Database, Field, Criteria) |
-| DMAX | Returns the maximum value in a database field that matches the given criteria. | DMAX(Database, Field, Criteria) |
-| DMIN | Returns the minimum value in a database field that matches the given criteria. | DMIN(Database, Field, Criteria) |
-| DPRODUCT | Returns the product of all values in a database field that match the given criteria. | DPRODUCT(Database, Field, Criteria) |
-| DSTDEV | Returns the sample standard deviation of all values in a database field that match the given criteria. | DSTDEV(Database, Field, Criteria) |
-| DSTDEVP | Returns the population standard deviation of all values in a database field that match the given criteria. | DSTDEVP(Database, Field, Criteria) |
-| DSUM | Returns the sum of all values in a database field that match the given criteria. | DSUM(Database, Field, Criteria) |
-| DVAR | Returns the sample variance of all values in a database field that match the given criteria. | DVAR(Database, Field, Criteria) |
-| DVARP | Returns the population variance of all values in a database field that match the given criteria. | DVARP(Database, Field, Criteria) |
-
-### Engineering
-
-| Function ID | Description | Syntax |
-|:------------|:------------------------------------------------------------------------------------|:-------------------------------------------|
-| BIN2DEC | The result is the decimal number for the binary number entered. | BIN2DEC(Number) |
-| BIN2HEX | The result is the hexadecimal number for the binary number entered. | BIN2HEX(Number, Places) |
-| BIN2OCT | The result is the octal number for the binary number entered. | BIN2OCT(Number, Places) |
-| BITAND | Returns a bitwise logical "and" of the parameters. | BITAND(Number1, Number2) |
-| BITLSHIFT | Shifts a number left by n bits. | BITLSHIFT(Number, Shift) |
-| BITOR | Returns a bitwise logical "or" of the parameters. | BITOR(Number1, Number2) |
-| BITRSHIFT | Shifts a number right by n bits. | BITRSHIFT(Number, Shift) |
-| BITXOR | Returns a bitwise logical "exclusive or" of the parameters. | BITXOR(Number1, Number2) |
-| COMPLEX | Returns complex number from Re and Im parts. | COMPLEX(Re, Im[, Symbol]) |
-| DEC2BIN | Returns the binary number for the decimal number entered between –512 and 511. | DEC2BIN(Number, Places) |
-| DEC2HEX | Returns the hexadecimal number for the decimal number entered. | DEC2HEX(Number, Places) |
-| DEC2OCT | Returns the octal number for the decimal number entered. | DEC2OCT(Number, Places) |
-| DELTA | Returns TRUE (1) if both numbers are equal, otherwise returns FALSE (0). | DELTA(Number_1, Number_2) |
-| ERF | Returns values of the Gaussian error integral. | ERF(Lower_Limit, Upper_Limit) |
-| ERFC | Returns complementary values of the Gaussian error integral between x and infinity. | ERFC(Lower_Limit) |
-| HEX2BIN | The result is the binary number for the hexadecimal number entered. | HEX2BIN(Number, Places) |
-| HEX2DEC | The result is the decimal number for the hexadecimal number entered. | HEX2DEC(Number) |
-| HEX2OCT | The result is the octal number for the hexadecimal number entered. | HEX2OCT(Number, Places) |
-| IMABS | Returns modulus of a complex number. | IMABS(Complex) |
-| IMAGINARY | Returns imaginary part of a complex number. | IMAGINARY(Complex) |
-| IMARGUMENT | Returns argument of a complex number. | IMARGUMENT(Complex) |
-| IMCONJUGATE | Returns conjugate of a complex number. | IMCONJUGATE(Complex) |
-| IMCOS | Returns cosine of a complex number. | IMCOS(Complex) |
-| IMCOSH | Returns hyperbolic cosine of a complex number. | IMCOSH(Complex) |
-| IMCOT | Returns cotangent of a complex number. | IMCOT(Complex) |
-| IMCSC | Returns cosecant of a complex number. | IMCSC(Complex) |
-| IMCSCH | Returns hyperbolic cosecant of a complex number. | IMCSCH(Complex) |
-| IMDIV | Divides two complex numbers. | IMDIV(Complex1, Complex2) |
-| IMEXP | Returns exponent of a complex number. | IMEXP(Complex) |
-| IMLN | Returns natural logarithm of a complex number. | IMLN(Complex) |
-| IMLOG2 | Returns binary logarithm of a complex number. | IMLOG2(Complex) |
-| IMLOG10 | Returns base-10 logarithm of a complex number. | IMLOG10(Complex) |
-| IMPOWER | Returns a complex number raised to a given power. | IMPOWER(Complex, Number) |
-| IMPRODUCT | Multiplies complex numbers. | IMPRODUCT(Complex1, Complex2, ...ComplexN) |
-| IMREAL | Returns real part of a complex number. | IMREAL(Complex) |
-| IMSEC | Returns the secant of a complex number. | IMSEC(Complex) |
-| IMSECH | Returns the hyperbolic secant of a complex number. | IMSECH(Complex) |
-| IMSIN | Returns sine of a complex number. | IMSIN(Complex) |
-| IMSINH | Returns hyperbolic sine of a complex number. | IMSINH(Complex) |
-| IMSQRT | Returns a square root of a complex number. | IMSQRT(Complex) |
-| IMSUB | Subtracts two complex numbers. | IMSUB(Complex1, Complex2) |
-| IMSUM | Adds complex numbers. | IMSUM(Complex1, Complex2, ..ComplexN) |
-| IMTAN | Returns the tangent of a complex number. | IMTAN(Complex) |
-| OCT2BIN | The result is the binary number for the octal number entered. | OCT2BIN(Number, Places) |
-| OCT2DEC | The result is the decimal number for the octal number entered. | OCT2DEC(Number) |
-| OCT2HEX | The result is the hexadecimal number for the octal number entered. | OCT2HEX(Number, Places) |
-
-### Information
-
-| Function ID | Description | Syntax |
-|:------------|:---------------------------------------------------------------------------------------------------------------|:-----------------|
-| ISBINARY | Returns TRUE if provided value is a valid binary number. | ISBINARY(Value) |
-| ISBLANK | Returns TRUE if the reference to a cell is blank. | ISBLANK(Value) |
-| ISERR | Returns TRUE if the value is error value except #N/A!. | ISERR(Value) |
-| ISERROR | Returns TRUE if the value is general error value. | ISERROR(Value) |
-| ISEVEN | Returns TRUE if the value is an even integer, or FALSE if the value is odd. | ISEVEN(Value) |
-| ISFORMULA | Checks whether referenced cell is a formula. | ISFORMULA(Value) |
-| ISLOGICAL | Tests for a logical value (TRUE or FALSE). | ISLOGICAL(Value) |
-| ISNA | Returns TRUE if the value is #N/A! error. | ISNA(Value) |
-| ISNONTEXT | Tests if the cell contents are text or numbers, and returns FALSE if the contents are text. | ISNONTEXT(Value) |
-| ISNUMBER | Returns TRUE if the value refers to a number. | ISNUMBER(Value) |
-| ISODD | Returns TRUE if the value is odd, or FALSE if the number is even. | ISODD(Value) |
-| ISREF | Returns TRUE if provided value is #REF! error. | ISREF(Value) |
-| ISTEXT | Returns TRUE if the cell contents reference text. | ISTEXT(Value) |
-| SHEET | Returns sheet number of a given value or a formula sheet number if no argument is provided. | SHEET([Value]) |
-| SHEETS | Returns number of sheet of a given reference or number of all sheets in workbook when no argument is provided. | SHEETS([Value]) |
-| NA | Returns #N/A! error value. | NA(Value) |
-
-### Financial
-
-| Function ID | Description | Syntax |
-|:------------|:---------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------|
-| CUMIPMT | Returns the cumulative interest paid on a loan between a start period and an end period. | CUMIPMT(Rate, Nper, Pv, Start, End, type) |
-| CUMPRINC | Returns the cumulative principal paid on a loan between a start period and an end period. | CUMPRINC(Rate, Nper, Pv, Start, End, Type) |
-| DB | Returns the depreciation of an asset for a period using the fixed-declining balance method. | DB(Cost, Salvage, Life, Period[, Month]) |
-| DDB | Returns the depreciation of an asset for a period using the double-declining balance method. | DDB(Cost, Salvage, Life, Period[, Factor]) |
-| DOLLARDE | Converts a price entered with a special notation to a price displayed as a decimal number. | DOLLARDE(Price, Fraction) |
-| DOLLARFR | Converts a price displayed as a decimal number to a price entered with a special notation. | DOLLARFR(Price, Fraction) |
-| EFFECT | Calculates the effective annual interest rate from a nominal interest rate and the number of compounding periods per year. | EFFECT (Nominal_rate, Npery) |
-| FV | Returns the future value of an investment. | FV(Rate, Nper, Pmt[, Pv,[ Type]]) |
-| FVSCHEDULE | Returns the future value of an investment based on a rate schedule. | FV(Pv, Schedule) |
-| IPMT | Returns the interest portion of a given loan payment in a given payment period. | IPMT(Rate, Per, Nper, Pv[, Fv[, Type]]) |
-| IRR | Returns the internal rate of return for a series of cash flows. | IRR(Values[, Guess]) |
-| ISPMT | Returns the interest paid for a given period of an investment with equal principal payments. | ISPMT(Rate, Per, Nper, Value) |
-| MIRR | Returns modified internal value for cashflows. | MIRR(Flows, FRate, RRate) |
-| NOMINAL | Returns the nominal interest rate. | NOMINAL(Effect_rate, Npery) |
-| NPER | Returns the number of periods for an investment assuming periodic, constant payments and a constant interest rate. | NPER(Rate, Pmt, Pv[, Fv[, Type]]) |
-| NPV | Returns net present value. | NPV(Rate, Value1, Value2, ...ValueN) |
-| PDURATION | Returns number of periods to reach specific value. | PDURATION(Rate, Pv, Fv) |
-| PMT | Returns the periodic payment for a loan. | PMT(Rate, Nper, Pv[, Fv[, Type]]) |
-| PPMT | Calculates the principal portion of a given loan payment. | PPMT(Rate, Per, Nper, Pv[, Fv[, Type]]) |
-| PV | Returns the present value of an investment. | PV(Rate, Nper, Pmt[, Fv[, Type]]) |
-| RATE | Returns the interest rate per period of an annuity. | RATE(Nper, Pmt, Pv[, Fv[, Type[, guess]]]) |
-| RRI | Returns an equivalent interest rate for the growth of an investment. | RRI(Nper, Pv, Fv) |
-| SLN | Returns the depreciation of an asset for one period, based on a straight-line method. | SLN(Cost, Salvage, Life) |
-| SYD | Returns the "sum-of-years" depreciation for an asset in a period. | SYD(Cost, Salvage, Life, Period) |
-| TBILLEQ | Returns the bond-equivalent yield for a Treasury bill. | TBILLEQ(Settlement, Maturity, Discount) |
-| TBILLPRICE | Returns the price per $100 face value for a Treasury bill. | TBILLPRICE(Settlement, Maturity, Discount) |
-| TBILLYIELD | Returns the yield for a Treasury bill. | TBILLYIELD(Settlement, Maturity, Price) |
-| XIRR | Returns the internal rate of return for a schedule of cash flows that is not necessarily periodic. | XIRR(Values, Dates[, Guess]) |
-| XNPV | Returns net present value. | XNPV(Rate, Payments, Dates) |
-
-### Logical
-
-| Function ID | Description | Syntax |
-|:------------|:---------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------|
-| AND | Returns TRUE if all arguments are TRUE. | AND(Logical_value1, Logical_value2, ...Logical_valueN) |
-| FALSE | Returns the logical value FALSE. | FALSE() |
-| IF | Specifies a logical test to be performed. | IF(Test, Then_value, Otherwise_value) |
-| IFS | Evaluates multiple logical tests and returns a value that corresponds to the first true condition. | IFS(Condition1, Value1 [, Condition2, Value2 [, ...ConditionN, ValueN]]) |
-| IFNA | Returns the value if the cell does not contains the #N/A (value not available) error value, or the alternative value if it does. | IFNA(Value, Alternate_value) |
-| IFERROR | Returns the value if the cell does not contains an error value, or the alternative value if it does. | IFERROR(Value, Alternate_value) |
-| NOT | Complements (inverts) a logical value. | NOT(Logicalvalue) |
-| SWITCH | Evaluates a list of arguments, consisting of an expression followed by a value. | SWITCH(Expression1, Value1 [, Expression2, Value2 [, ...ExpressionN, ValueN]]) |
-| OR | Returns TRUE if at least one argument is TRUE. | OR(Logical_value1, Logical_value2, ...Logical_valueN) |
-| TRUE | The logical value is set to TRUE. | TRUE() |
-| XOR | Returns true if an odd number of arguments evaluates to TRUE. | XOR(Logical_value1, Logical_value2, ...Logical_valueN) |
-
-### Lookup and reference
-
-| Function ID | Description | Syntax |
-|:------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------|
-| ADDRESS | Returns a cell reference as a string. | ADDRESS(Row, Column[, AbsoluteRelativeMode[, UseA1Notation[, Sheet]]]) |
-| CHOOSE | Uses an index to return a value from a list of values. | CHOOSE(Index, Value1, Value2, ...ValueN) |
-| COLUMN | Returns column number of a given reference or formula reference if argument not provided. | COLUMNS([Reference]) |
-| COLUMNS | Returns the number of columns in the given reference. | COLUMNS(Array) |
-| FORMULATEXT | Returns a formula in a given cell as a string. | FORMULATEXT(Reference) |
-| HLOOKUP | Searches horizontally with reference to adjacent cells to the bottom. | HLOOKUP(Search_Criterion, Array, Index, Sort_Order) |
-| HYPERLINK | Stores the url in the cell's metadata. It can be read using method [`getCellHyperlink`](../api/classes/hyperformula.md#getcellhyperlink) | HYPERLINK(Url[, LinkLabel]) |
-| INDEX | Returns the contents of a cell specified by row and column number. The column number is optional and defaults to 1. | INDEX(Range, Row [, Column]) |
-| MATCH | Returns the relative position of an item in an array that matches a specified value. | MATCH(Searchcriterion, LookupArray [, MatchType]) |
-| OFFSET | Returns the value of a cell offset by a certain number of rows and columns from a given reference point. | OFFSET(Reference, Rows, Columns, Height, Width) |
-| ROW | Returns row number of a given reference or formula reference if argument not provided. | ROW([Reference]) |
-| ROWS | Returns the number of rows in the given reference. | ROWS(Array) |
-| VLOOKUP | Searches vertically with reference to adjacent cells to the right. | VLOOKUP(Search_Criterion, Array, Index, Sort_Order) |
-| XLOOKUP | Searches for a key in a range and returns the item corresponding to the match it finds. If no match exists, then XLOOKUP can return the closest (approximate) match. | XLOOKUP(LookupValue, LookupArray, ReturnArray, [IfNotFound], [MatchMode], [SearchMode]) |
-
-### Math and trigonometry
-
-| Function ID | Description | Syntax |
-|:----------------|:--------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------|
-| ABS | Returns the absolute value of a number. | ABS(Number) |
-| ACOS | Returns the inverse trigonometric cosine of a number. | ACOS(Number) |
-| ACOSH | Returns the inverse hyperbolic cosine of a number. | ACOSH(Number) |
-| ACOT | Returns the inverse trigonometric cotangent of a number. | ACOT(Number) |
-| ACOTH | Returns the inverse hyperbolic cotangent of a number. | ACOTH(Number) |
-| ARABIC | Converts number from roman form. | ARABIC(String) |
-| ASIN | Returns the inverse trigonometric sine of a number. | ASIN(Number) |
-| ASINH | Returns the inverse hyperbolic sine of a number. | ASINH(Number) |
-| ATAN | Returns the inverse trigonometric tangent of a number. | ATAN(Number) |
-| ATAN2 | Returns the inverse trigonometric tangent of the specified x and y coordinates. | ATAN2(Numberx, Numbery) |
-| ATANH | Returns the inverse hyperbolic tangent of a number. | ATANH(Number) |
-| BASE | Converts a positive integer to a specified base into a text from the numbering system. | BASE(Number, Radix, [Minimumlength]) |
-| CEILING | Rounds a number up to the nearest multiple of Significance. | CEILING(Number, Significance) |
-| CEILING.MATH | Rounds a number up to the nearest multiple of Significance. | CEILING.MATH(Number[, Significance[, Mode]]) |
-| CEILING.PRECISE | Rounds a number up to the nearest multiple of Significance. | CEILING.PRECISE(Number[, Significance]) |
-| COMBIN | Returns number of combinations (without repetitions). | COMBIN(Number, Number) |
-| COMBINA | Returns number of combinations (with repetitions). | COMBINA(Number, Number) |
-| COS | Returns the cosine of the given angle (in radians). | COS(Number) |
-| COSH | Returns the hyperbolic cosine of the given value. | COSH(Number) |
-| COT | Returns the cotangent of the given angle (in radians). | COT(Number) |
-| COTH | Returns the hyperbolic cotangent of the given value. | COTH(Number) |
-| COUNTUNIQUE | Counts the number of unique values in a list of specified values and ranges. | COUNTUNIQUE(Value1, Value2, ...ValueN) |
-| CSC | Returns the cosecant of the given angle (in radians). | CSC(Number) |
-| CSCH | Returns the hyperbolic cosecant of the given value. | CSCH(Number) |
-| DECIMAL | Converts text with characters from a number system to a positive integer in the base radix given. | DECIMAL("Text", Radix) |
-| DEGREES | Converts radians into degrees. | DEGREES(Number) |
-| EVEN | Rounds a positive number up to the next even integer and a negative number down to the next even integer. | EVEN(Number) |
-| EXP | Returns constant e raised to the power of a number. | EXP(Number) |
-| FACT | Returns a factorial of a number. | FACT(Number) |
-| FACTDOUBLE | Returns a double factorial of a number. | FACTDOUBLE(Number) |
-| FLOOR | Rounds a number down to the nearest multiple of Significance. | FLOOR(Number, Significance) |
-| FLOOR.MATH | Rounds a number down to the nearest multiple of Significance. | FLOOR.MATH(Number[, Significance[, Mode]]) |
-| FLOOR.PRECISE | Rounds a number down to the nearest multiple of Significance. | FLOOR.PRECISE(Number[, Significance]) |
-| GCD | Computes greatest common divisor of numbers. | GCD(Number1, Number2, ...NumberN) |
-| INT | Rounds a number down to the nearest integer. | INT(Number) |
-| ISO.CEILING | Rounds a number up to the nearest multiple of Significance. | ISO.CEILING(Number[, Significance]) |
-| LCM | Computes least common multiple of numbers. | LCM(Number1, Number2, ...NumberN) |
-| LN | Returns the natural logarithm based on the constant e of a number. | LN(Number) |
-| LOG | Returns the logarithm of a number to the specified base. | LOG(Number, Base) |
-| LOG10 | Returns the base-10 logarithm of a number. | LOG10(Number) |
-| MOD | Returns the remainder when one integer is divided by another. | MOD(Dividend, Divisor) |
-| MROUND | Rounds number to the neares multiplicity. | MROUND(Number, Base) |
-| MULTINOMIAL | Returns number of multiset combinations. | MULTINOMIAL(Number1, Number2, ...NumberN) |
-| ODD | Rounds a positive number up to the nearest odd integer and a negative number down to the nearest odd integer. | ODD(Number) |
-| PI | Returns 3.14159265358979, the value of the mathematical constant PI to 14 decimal places. | PI() |
-| POWER | Returns a number raised to another number. | POWER(Base, Exponent) |
-| PRODUCT | Returns product of numbers. | PRODUCT(Number1, Number2, ...NumberN) |
-| QUOTIENT | Returns integer part of a division. | QUOTIENT(Dividend, Divisor) |
-| RADIANS | Converts degrees to radians. | RADIANS(Number) |
-| RAND | Returns a random number between 0 and 1. | RAND() |
-| RANDBETWEEN | Returns a random integer between two numbers. | RAND(Lowerbound, Upperbound) |
-| ROMAN | Converts number to roman form. | ROMAN(Number[, Mode]) |
-| ROUND | Rounds a number to a certain number of decimal places. | ROUND(Number, Count) |
-| ROUNDDOWN | Rounds a number down, toward zero, to a certain precision. | ROUNDDOWN(Number, Count) |
-| ROUNDUP | Rounds a number up, away from zero, to a certain precision. | ROUNDUP(Number, Count) |
-| SEC | Returns the secant of the given angle (in radians). | SEC(Number) |
-| SECH | Returns the hyperbolic secant of the given angle (in radians). | SEC(Number) |
-| SERIESSUM | Evaluates series at a point. | SERIESSUM(Number, Number, Number, Coefficients) |
-| SIN | Returns the sine of the given angle (in radians). | SIN(Number) |
-| SINH | Returns the hyperbolic sine of the given value. | SINH(Number) |
-| SIGN | Returns sign of a number. | SIGN(Number) |
-| SQRT | Returns the positive square root of a number. | SQRT(Number) |
-| SQRTPI | Returns sqrt of number times pi. | SQRTPI(Number) |
-| SUBTOTAL | Computes aggregation using function specified by number. | SUBTOTAL(Function, Number1, Number2, ...NumberN) |
-| SUM | Sums up the values of the specified cells. | SUM(Number1, Number2, ...NumberN) |
-| SUMIF | Sums up the values of cells that belong to the specified range and meet the specified condition. | SUMIF(Range, Criteria, Sumrange) |
-| SUMIFS | Sums up the values of cells that belong to the specified range and meet the specified sets of conditions. | SUMIFS(Sum_Range, Criterion_range1, Criterion1 [, Criterion_range2, Criterion2 [, ...Criterion_rangeN, CriterionN]]) |
-| SUMPRODUCT | Multiplies corresponding elements in the given arrays, and returns the sum of those products. | SUMPRODUCT(Array1, Array2, ...ArrayN) |
-| SUMSQ | Returns the sum of the squares of the arguments | SUMSQ(Number1, Number2, ...NumberN) |
-| SUMX2MY2 | Returns the sum of the square differences. | SUMX2MY2(Range1, Range2) |
-| SUMX2PY2 | Returns the sum of the square sums. | SUMX2PY2(Range1, Range2) |
-| SUMXMY2 | Returns the sum of the square of differences. | SUMXMY2(Range1, Range2) |
-| TAN | Returns the tangent of the given angle (in radians). | TAN(Number) |
-| TANH | Returns the hyperbolic tangent of the given value. | TANH(Number) |
-| TRUNC | Truncates a number by removing decimal places. | TRUNC(Number, Count) |
-
-### Matrix functions
-
-| Function ID | Description | Syntax |
-|:------------|:------------------------------------------------------------------------------------------------------------|:---------------------------------------|
-| MMULT | Calculates the array product of two arrays. | MMULT(Array, Array) |
-| MEDIANPOOL | Calculates a smaller range which is a median of a Window_size, in a given Range, for every Stride element. | MEDIANPOOL(Range, Window_size, Stride) |
-| MAXPOOL | Calculates a smaller range which is a maximum of a Window_size, in a given Range, for every Stride element. | MAXPOOL(Range, Window_size, Stride) |
-| TRANSPOSE | Transposes the rows and columns of an array. | TRANSPOSE(Array) |
-
-### Operator
-
-| Function ID | Description | Syntax |
-|:-----------------|:---------------------------------------------|:----------------------------|
-| HF.ADD | Adds two values. | HF.ADD(Number, Number) |
-| HF.CONCAT | Concatenates two strings. | HF.CONCAT(String, String) |
-| HF.DIVIDE | Divides two values. | HF.DIVIDE(Number, Number) |
-| HF.EQ | Tests two values for equality. | HF.EQ(Value, Value) |
-| HF.LTE | Tests two values for less-equal relation. | HF.LEQ(Value, Value) |
-| HF.LT | Tests two values for less-than relation. | HF.LT(Value, Value) |
-| HF.GTE | Tests two values for greater-equal relation. | HF.GEQ(Value, Value) |
-| HF.GT | Tests two values for greater-than relation. | HF.GT(Value, Value) |
-| HF.MINUS | Subtracts two values. | HF.MINUS(Number, Number) |
-| HF.MULTIPLY | Multiplies two values. | HF.MULTIPLY(Number, Number) |
-| HF.NE | Tests two values for inequality. | HF.NE(Value, Value) |
-| HF.POW | Computes power of two values. | HF.POW(Number, Number) |
-| HF.UMINUS | Negates the value. | HF.UMINUS(Number) |
-| HF.UNARY_PERCENT | Applies percent operator. | HF.UNARY_PERCENT(Number) |
-| HF.UPLUS | Applies unary plus. | HF.UPLUS(Number) |
-
-### Statistical
-
-| Function ID | Description | Syntax |
-|:----------------|:----------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------|
-| AVEDEV | Returns the average deviation of the arguments. | AVEDEV(Number1, Number2, ...NumberN) |
-| AVERAGE | Returns the average of the arguments. | AVERAGE(Number1, Number2, ...NumberN) |
-| AVERAGEA | Returns the average of the arguments. | AVERAGEA(Value1, Value2, ...ValueN) |
-| AVERAGEIF | Returns the arithmetic mean of all cells in a range that satisfy a given condition. | AVERAGEIF(Range, Criterion [, Average_Range ]) |
-| BESSELI | Returns value of Bessel function. | BESSELI(x, n) |
-| BESSELJ | Returns value of Bessel function. | BESSELJ(x, n) |
-| BESSELK | Returns value of Bessel function. | BESSELK(x, n) |
-| BESSELY | Returns value of Bessel function. | BESSELY(x, n) |
-| BETA.DIST | Returns the density of Beta distribution. | BETA.DIST(Number1, Number2, Number3, Boolean[, Number4[, Number5]]) |
-| BETADIST | Returns the density of Beta distribution. | BETADIST(Number1, Number2, Number3, Boolean[, Number4[, Number5]]) |
-| BETA.INV | Returns the inverse Beta distribution value. | BETA.INV(Number1, Number2, Number3[, Number4[, Number5]]) |
-| BETAINV | Returns the inverse of Beta distribution value. | BETAINV(Number1, Number2, Number3[, Number4[, Number5]]) |
-| BINOM.DIST | Returns density of binomial distribution. | BINOM.DIST(Number1, Number2, Number3, Boolean) |
-| BINOMDIST | Returns density of binomial distribution. | BINOMDIST(Number1, Number2, Number3, Boolean) |
-| BINOM.INV | Returns inverse binomial distribution value. | BINOM.INV(Number1, Number2, Number3) |
-| CHIDIST | Returns probability of chi-square right-side distribution. | CHIDIST(X, Degrees) |
-| CHIINV | Returns inverse of chi-square right-side distribution. | CHIINV(P, Degrees) |
-| CHIINVRT | Returns inverse of chi-square right-side distribution. | CHIINVRT(P, Degrees) |
-| CHISQ.DIST | Returns value of chi-square distribution. | CHISQ.DIST(X, Degrees, Mode) |
-| CHIDISTRT | Returns probability of chi-square right-side distribution. | CHIDISTRT(X, Degrees) |
-| CHISQ.DIST.RT | Returns probability of chi-square right-side distribution. | CHISQ.DIST.RT(X, Degrees) |
-| CHISQ.INV | Returns inverse of chi-square distribution. | CHISQ.INV.RT(P, Degrees) |
-| CHISQ.INV.RT | Returns inverse of chi-square right-side distribution. | CHISQ.INV.RT(P, Degrees) |
-| CHISQ.TEST | Returns chisquared-test value for a dataset. | CHISQ.TEST(Array1, Array2) |
-| CHITEST | Returns chisquared-test value for a dataset. | CHITEST(Array1, Array2) |
-| CONFIDENCE | Returns upper confidence bound for normal distribution. | CONFIDENCE(Alpha, Stdev, Size) |
-| CONFIDENCE.NORM | Returns upper confidence bound for normal distribution. | CONFIDENCE.NORM(Alpha, Stdev, Size) |
-| CONFIDENCE.T | Returns upper confidence bound for T distribution. | CONFIDENCE.T(Alpha, Stdev, Size) |
-| CORREL | Returns the correlation coefficient between two data sets. | CORREL(Data1, Data2) |
-| COUNT | Counts how many numbers are in the list of arguments. | COUNT(Value1, Value2, ...ValueN) |
-| COUNTA | Counts how many values are in the list of arguments. | COUNTA(Value1, Value2, ...ValueN) |
-| COUNTBLANK | Returns the number of empty cells. | COUNTBLANK(Range) |
-| COUNTIF | Returns the number of cells that meet with certain criteria within a cell range. | COUNTIF(Range, Criteria) |
-| COUNTIFS | Returns the count of rows or columns that meet criteria in multiple ranges. | COUNTIFS(Range1, Criterion1 [, Range2, Criterion2 [, ...RangeN, CriterionN]]) |
-| COVAR | Returns the covariance between two data sets, population normalized. | COVAR(Data1, Data2) |
-| COVARIANCE.P | Returns the covariance between two data sets, population normalized. | COVARIANCE.P(Data1, Data2) |
-| COVARIANCEP | Returns the covariance between two data sets, population normalized. | COVARIANCEP(Data1, Data2) |
-| COVARIANCE.S | Returns the covariance between two data sets, sample normalized. | COVARIANCE.S(Data1, Data2) |
-| COVARIANCES | Returns the covariance between two data sets, sample normalized. | COVARIANCES(Data1, Data2) |
-| CRITBINOM | Returns inverse binomial distribution value. | CRITBINOM(Number1, Number2, Number3) |
-| DEVSQ | Returns sum of squared deviations. | DEVSQ(Number1, Number2, ...NumberN) |
-| EXPON.DIST | Returns density of a exponential distribution. | EXPON.DIST(Number1, Number2, Boolean) |
-| EXPONDIST | Returns density of a exponential distribution. | EXPONDIST(Number1, Number2, Boolean) |
-| FDIST | Returns probability of F right-side distribution. | FDIST(X, Degree1, Degree2) |
-| FINV | Returns inverse of F right-side distribution. | FINV(P, Degree1, Degree2) |
-| F.DIST | Returns value of F distribution. | F.DIST(X, Degree1, Degree2, Mode) |
-| F.DIST.RT | Returns probability of F right-side distribution. | F.DIST.RT(X, Degree1, Degree2) |
-| FDISTRT | Returns probability of F right-side distribution. | FDISTRT(X, Degree1, Degree2) |
-| F.INV | Returns inverse of F distribution. | F.INV.RT(P, Degree1, Degree2) |
-| F.INV.RT | Returns inverse of F right-side distribution. | F.INV.RT(P, Degree1, Degree2) |
-| FINVRT | Returns inverse of F right-side distribution. | FINVRT(P, Degree1, Degree2) |
-| FISHER | Returns Fisher transformation value. | FISHER(Number) |
-| FISHERINV | Returns inverse Fischer transformation value. | FISHERINV(Number) |
-| F.TEST | Returns f-test value for a dataset. | Z.TEST(Array1, Array2) |
-| FTEST | Returns f-test value for a dataset. | ZTEST(Array1, Array2) |
-| GAMMA | Returns value of Gamma function. | GAMMA(Number) |
-| GAMMA.DIST | Returns density of Gamma distribution. | GAMMA.DIST(Number1, Number2, Number3, Boolean) |
-| GAMMADIST | Returns density of Gamma distribution. | GAMMADIST(Number1, Number2, Number3, Boolean) |
-| GAMMALN | Returns natural logarithm of Gamma function. | GAMMALN(Number) |
-| GAMMALN.PRECISE | Returns natural logarithm of Gamma function. | GAMMALN.PRECISE(Number) |
-| GAMMA.INV | Returns inverse Gamma distribution value. | GAMMA.INV(Number1, Number2, Number3) |
-| GAMMAINV | Returns inverse Gamma distribution value. | GAMMAINV(Number1, Number2, Number3) |
-| GAUSS | Returns the probability of gaussian variable falling more than this many times standard deviation from mean. | GAUSS(Number) |
-| GEOMEAN | Returns the geometric average. | GEOMEAN(Number1, Number2, ...NumberN) |
-| HARMEAN | Returns the harmonic average. | HARMEAN(Number1, Number2, ...NumberN) |
-| HYPGEOMDIST | Returns density of hypergeometric distribution. | HYPGEOMDIST(Number1, Number2, Number3, Number4, Boolean) |
-| HYPGEOM.DIST | Returns density of hypergeometric distribution. | HYPGEOM.DIST(Number1, Number2, Number3, Number4, Boolean) |
-| LARGE | Returns k-th largest value in a range. | LARGE(Range, K) |
-| LOGNORM.DIST | Returns density of lognormal distribution. | LOGNORM.DIST(X, Mean, Stddev, Mode) |
-| LOGNORMDIST | Returns density of lognormal distribution. | LOGNORMDIST(X, Mean, Stddev, Mode) |
-| LOGNORM.INV | Returns value of inverse lognormal distribution. | LOGNORM.INV(P, Mean, Stddev) |
-| LOGNORMINV | Returns value of inverse lognormal distribution. | LOGNORMINV(P, Mean, Stddev) |
-| LOGINV | Returns value of inverse lognormal distribution. | LOGINV(P, Mean, Stddev) |
-| MAX | Returns the maximum value in a list of arguments. | MAX(Number1, Number2, ...NumberN) |
-| MAXA | Returns the maximum value in a list of arguments. | MAXA(Value1, Value2, ...ValueN) |
-| MAXIFS | Returns the maximum value of the cells in a range that meet a set of criteria. | MAXIFS(Max_Range, Criterion_range1, Criterion1 [, Criterion_range2, Criterion2 [, ...Criterion_rangeN, CriterionN]]) |
-| MEDIAN | Returns the median of a set of numbers. | MEDIAN(Number1, Number2, ...NumberN) |
-| MIN | Returns the minimum value in a list of arguments. | MIN(Number1, Number2, ...NumberN) |
-| MINA | Returns the minimum value in a list of arguments. | MINA(Value1, Value2, ...ValueN) |
-| MINIFS | Returns the minimum value of the cells in a range that meet a set of criteria. | MINIFS(Min_Range, Criterion_range1, Criterion1 [, Criterion_range2, Criterion2 [, ...Criterion_rangeN, CriterionN]]) |
-| NEGBINOM.DIST | Returns density of negative binomial distribution. | NEGBINOM.DIST(Number1, Number2, Number3, Mode) |
-| NEGBINOMDIST | Returns density of negative binomial distribution. | NEGBINOMDIST(Number1, Number2, Number3, Mode) |
-| NORM.DIST | Returns density of normal distribution. | NORM.DIST(X, Mean, Stddev, Mode) |
-| NORMDIST | Returns density of normal distribution. | NORMDIST(X, Mean, Stddev, Mode) |
-| NORM.S.DIST | Returns density of normal distribution. | NORM.S.DIST(X, Mode) |
-| NORMDIST | Returns density of normal distribution. | NORMSDIST(X, Mode) |
-| NORM.INV | Returns value of inverse normal distribution. | NORM.INV(P, Mean, Stddev) |
-| NORMINV | Returns value of inverse normal distribution. | NORMINV(P, Mean, Stddev) |
-| NORM.S.INV | Returns value of inverse normal distribution. | NORM.S.INV(P) |
-| NORMSINV | Returns value of inverse normal distribution. | NORMSINV(P) |
-| PEARSON | Returns the correlation coefficient between two data sets. | PEARSON(Data1, Data2) |
-| PHI | Returns probability densitity of normal distribution. | PHI(X) |
-| PERCENTILE | Returns the k-th percentile of values in a range, inclusive of 0 and 1. | PERCENTILE(Data, K) |
-| PERCENTILE.EXC | Returns the k-th percentile of values in a range, exclusive of 0 and 1. | PERCENTILE.EXC(Data, K) |
-| PERCENTILE.INC | Returns the k-th percentile of values in a range, inclusive of 0 and 1. | PERCENTILE.INC(Data, K) |
-| POISSON | Returns density of Poisson distribution. | POISSON(X, Mean, Mode) |
-| POISSON.DIST | Returns density of Poisson distribution. | POISSON.DIST(X, Mean, Mode) |
-| POISSONDIST | Returns density of Poisson distribution. | POISSONDIST(X, Mean, Mode) |
-| QUARTILE | Returns the quartile of a data set, based on inclusive percentile values. | QUARTILE(Data, Quart) |
-| QUARTILE.EXC | Returns the quartile of a data set, based on exclusive percentile values. | QUARTILE.EXC(Data, Quart) |
-| QUARTILE.INC | Returns the quartile of a data set, based on inclusive percentile values. | QUARTILE.INC(Data, Quart) |
-| RSQ | Returns the squared correlation coefficient between two data sets. | RSQ(Data1, Data2) |
-| SKEW | Returns skeweness of a sample. | SKEW(Number1, Number2, ...NumberN) |
-| SKEW.P | Returns skeweness of a population. | SKEW.P(Number1, Number2, ...NumberN) |
-| SKEWP | Returns skeweness of a population. | SKEWP(Number1, Number2, ...NumberN) |
-| SLOPE | Returns the slope of a linear regression line. | SLOPE(Array1, Array2) |
-| SMALL | Returns k-th smallest value in a range. | SMALL(Range, K) |
-| STANDARDIZE | Returns normalized value wrt expected value and standard deviation. | STANDARDIZE(X, Mean, Stddev) |
-| STDEV | Returns standard deviation of a sample. | STDEV(Value1, Value2, ...ValueN) |
-| STDEVA | Returns standard deviation of a sample. | STDEVA(Value1, Value2, ...ValueN) |
-| STDEVP | Returns standard deviation of a population. | STDEVP(Value1, Value2, ...ValueN) |
-| STDEV.P | Returns standard deviation of a population. | STDEV.P(Value1, Value2, ...ValueN) |
-| STDEVPA | Returns standard deviation of a population. | STDEVPA(Value1, Value2, ...ValueN) |
-| STDEV.S | Returns standard deviation of a sample. | STDEV.S(Value1, Value2, ...ValueN) |
-| STDEVS | Returns standard deviation of a sample. | STDEVS(Value1, Value2, ...ValueN) |
-| STEYX | Returns standard error for predicted of the predicted y value for each x value. | STEYX(Array1, Array2) |
-| TDIST | Returns density of Student-t distribution, both-sided or right-tailed. | TDIST(X, Degrees, Mode) |
-| T.DIST | Returns density of Student-t distribution. | T.DIST(X, Degrees, Mode) |
-| T.DIST.2T | Returns density of Student-t distribution, both-sided. | T.DIST.2T(X, Degrees) |
-| TDIST2T | Returns density of Student-t distribution, both-sided. | TDIST2T(X, Degrees) |
-| T.DIST.RT | Returns density of Student-t distribution, right-tailed. | T.DIST.RT(X, Degrees) |
-| TDISTRT | Returns density of Student-t distribution, right-tailed. | TDISTRT(X, Degrees) |
-| TINV | Returns inverse Student-t distribution, both-sided. | TINV(P, Degrees) |
-| T.INV | Returns inverse Student-t distribution. | T.INV(P, Degrees) |
-| T.INV.2T | Returns inverse Student-t distribution, both-sided. | T.INV.2T(P, Degrees) |
-| TINV2T | Returns inverse Student-t distribution, both-sided. | TINV2T(P, Degrees) |
-| TTEST | Returns t-test value for a dataset. | TTEST(Array1, Array2) |
-| T.TEST | Returns t-test value for a dataset. | T.TEST(Array1, Array2) |
-| VAR | Returns variance of a sample. | VAR(Value1, Value2, ...ValueN) |
-| VARA | Returns variance of a sample. | VARA(Value1, Value2, ...ValueN) |
-| VARP | Returns variance of a population. | VARP(Value1, Value2, ...ValueN) |
-| VAR.P | Returns variance of a population. | VAR.P(Value1, Value2, ...ValueN) |
-| VARPA | Returns variance of a population. | VARPA(Value1, Value2, ...ValueN) |
-| VAR.S | Returns variance of a sample. | VAR.S(Value1, Value2, ...ValueN) |
-| VARS | Returns variance of a sample. | VARS(Value1, Value2, ...ValueN) |
-| WEIBULL | Returns density of Weibull distribution. | WEIBULL(Number1, Number2, Number3, Boolean) |
-| WEIBULL.DIST | Returns density of Weibull distribution. | WEIBULL.DIST(Number1, Number2, Number3, Boolean) |
-| WEIBULLDIST | Returns density of Weibull distribution. | WEIBULLDIST(Number1, Number2, Number3, Boolean) |
-| Z.TEST | Returns z-test value for a dataset. | Z.TEST(Array, X[, Sigma]) |
-| ZTEST | Returns z-test value for a dataset. | ZTEST(Array, X[, Sigma]) |
-
-### Text
-
-| Function ID | Description | Syntax |
-|:------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------|
-| CHAR | Converts a number into a character according to the current code table. | CHAR(Number) |
-| CLEAN | Returns text that has been "cleaned" of line breaks and other non-printable characters. | CLEAN("Text") |
-| CODE | Returns a numeric code for the first character in a text string. | CODE("Text") |
-| CONCATENATE | Combines several text strings into one string. | CONCATENATE("Text1", "Text2", ..."TextN") |
-| EXACT | Returns TRUE if both text strings are exactly the same. | EXACT(Text, Text) |
-| FIND | Returns the location of one text string inside another. | FIND( "Text1", "Text2"[, Number]) |
-| LEFT | Extracts a given number of characters from the left side of a text string. | LEFT("Text", Number) |
-| LEN | Returns length of a given text. | LEN("Text") |
-| LOWER | Returns text converted to lowercase. | LOWER(Text) |
-| MID | Returns substring of a given length starting from Start_position. | MID(Text, Start_position, Length) |
-| N | Converts a value to a number. | N(Value) |
-| PROPER | Capitalizes words given text string. | PROPER("Text") |
-| REPLACE | Replaces substring of a text of a given length that starts at given position. | REPLACE(Text, Start_position, Length, New_text) |
-| REPT | Repeats text a given number of times. | REPT("Text", Number) |
-| RIGHT | Extracts a given number of characters from the right side of a text string. | RIGHT("Text", Number) |
-| SEARCH | Returns the location of Search_string inside Text. Case-insensitive. Allows the use of wildcards. | SEARCH(Search_string, Text[, Start_position]) |
-| SPLIT | Divides the provided text using the space character as a separator and returns the substring at the zero-based position specified by the second argument.
`SPLIT("Lorem ipsum", 0) -> "Lorem"`
`SPLIT("Lorem ipsum", 1) -> "ipsum"` | SPLIT(Text, Index) |
-| SUBSTITUTE | Returns string where occurrences of Old_text are replaced by New_text. Replaces only specific occurrence if last parameter is provided. | SUBSTITUTE(Text, Old_text, New_text, [Occurrence]) |
-| T | Returns text if given value is text, empty string otherwise. | T(Value) |
-| TEXT | Converts a number into text according to a given format.
By default, accepts the same formats that can be passed to the [`dateFormats`](../api/interfaces/configparams.md#dateformats) option, but can be further customized with the [`stringifyDateTime`](../api/interfaces/configparams.md#stringifydatetime) and [`stringifyCurrency`](../api/interfaces/configparams.md#stringifycurrency) options. | TEXT(Number, Format) |
-| TEXTJOIN | Joins text from multiple strings and/or ranges with a delimiter. Supports array/range delimiters that cycle through gaps. When ignore_empty is TRUE, empty strings are skipped. Returns #VALUE! if result exceeds 32,767 characters. | TEXTJOIN(Delimiter, Ignore_empty, Text1, [Text2, ...]) |
-| TRIM | Strips extra spaces from text. | TRIM("Text") |
-| UNICHAR | Returns the character created by using provided code point. | UNICHAR(Number) |
-| UNICODE | Returns the Unicode code point of a first character of a text. | UNICODE(Text) |
-| UPPER | Returns text converted to uppercase. | UPPER(Text) |
-| VALUE | Parses a number, date, time, datetime, currency, or percentage from a text string. | VALUE(Text) |
-
-[^non-odff]:
- The return value of this function is compliant with the
- [OpenDocument standard](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html),
- but the return type is not. See
- [compatibility notes](list-of-differences.md).
diff --git a/docs/guide/built-in-functions.tmpl.md b/docs/guide/built-in-functions.tmpl.md
new file mode 100644
index 000000000..b9acf9614
--- /dev/null
+++ b/docs/guide/built-in-functions.tmpl.md
@@ -0,0 +1,69 @@
+# Built-in functions
+
+
+
+
+
+## Overview
+
+HyperFormula comes with an extensive library of pre-built functions. You can use
+them to create complex formulas for any business application. Formula syntax and
+logic of function are similar to what is considered the standard in modern
+spreadsheet software. That is because a spreadsheet is probably the most
+universal software ever created. We wanted the same flexibility for HyperFormula
+but without the constraints of the spreadsheet UI.
+
+Each of HyperFormula's built-in function names is available in [17 languages](localizing-functions.md#list-of-supported-languages) and [custom language packs](localizing-functions) can be added.
+
+The latest version of HyperFormula has an extensive collection of
+**{{ $page.functionsCount }}** functions grouped into categories:
+
+- [Array manipulation](#array-manipulation)
+- [Database](#database)
+- [Date and time](#date-and-time)
+- [Engineering](#engineering)
+- [Information](#information)
+- [Financial](#financial)
+- [Logical](#logical)
+- [Lookup and reference](#lookup-and-reference)
+- [Math and trigonometry](#math-and-trigonometry)
+- [Matrix functions](#matrix-functions)
+- [Operator](#operator)
+- [Statistical](#statistical)
+- [Text](#text)
+
+_Some categories such as compatibility and cube are yet to be supported._
+
+::: tip
+You can modify the built-in functions or create your own, by adding a [custom function](custom-functions).
+:::
+
+## List of available functions
+
+Total number of functions: **{{ $page.functionsCount }}**
+
+
+
+
+
+[^non-odff]:
+ The return value of this function is compliant with the
+ [OpenDocument standard](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html),
+ but the return type is not. See
+ [compatibility notes](list-of-differences.md).
diff --git a/package.json b/package.json
index cd9767e34..b3449cd80 100644
--- a/package.json
+++ b/package.json
@@ -55,12 +55,13 @@
"unpkg": "dist/hyperformula.min.js",
"typings": "./typings/index.d.ts",
"scripts": {
- "docs:dev": "npm run typedoc:build-api && cross-env NODE_OPTIONS=--openssl-legacy-provider vuepress dev docs --silent --no-clear-screen --no-cache",
- "docs:build": "npm run bundle-all && npm run typedoc:build-api && cross-env NODE_OPTIONS=--openssl-legacy-provider vuepress build docs",
+ "docs:dev": "npm run docs:generate-functions && npm run typedoc:build-api && cross-env NODE_OPTIONS=--openssl-legacy-provider vuepress dev docs --silent --no-clear-screen --no-cache",
+ "docs:build": "npm run docs:generate-functions && npm run bundle-all && npm run typedoc:build-api && cross-env NODE_OPTIONS=--openssl-legacy-provider vuepress build docs",
"docs:code-examples:generate-js": "bash docs/code-examples-generator.sh",
"docs:code-examples:generate-all-js": "bash docs/code-examples-generator.sh --generateAll",
"docs:code-examples:format-all-ts": "bash docs/code-examples-generator.sh --formatAllTsExamples",
"snippets:extract": "node script/extract-doc-snippets.js",
+ "docs:generate-functions": "npm run tsnode scripts/generate-builtin-functions-doc.ts",
"bundle-all": "cross-env HF_COMPILE=1 npm-run-all clean compile bundle:** verify-bundles",
"bundle:es": "(node script/if-ne-env.js HF_COMPILE=1 || npm run compile) && cross-env-shell BABEL_ENV=es env-cmd -f ht.config.js babel lib --out-file-extension .mjs --out-dir es",
"bundle:cjs": "(node script/if-ne-env.js HF_COMPILE=1 || npm run compile) && cross-env-shell BABEL_ENV=commonjs env-cmd -f ht.config.js babel lib --out-dir commonjs",
diff --git a/scripts/generate-builtin-functions-doc.ts b/scripts/generate-builtin-functions-doc.ts
new file mode 100644
index 000000000..28dde6b70
--- /dev/null
+++ b/scripts/generate-builtin-functions-doc.ts
@@ -0,0 +1,41 @@
+/**
+ * @license
+ * Copyright (c) 2025 Handsoncode. All rights reserved.
+ */
+
+/**
+ * HF-249 bullet 3 — generates the built-in functions guide page `docs/guide/built-in-functions.md` from
+ * HyperFormula's public API (single source of truth). Reads the committed template
+ * `built-in-functions.tmpl.md` (hand-written prose + empty `AUTOGENERATED:FUNCTIONS` markers), splices the
+ * generated table into the marker region, and writes `built-in-functions.md` — which is **gitignored** (a pure
+ * build product, per Kuba's HF-249 decision). Dev-only; never shipped (`tsconfig.json` `include` is `["src"]`).
+ * Runs as the first step of `docs:build`/`docs:dev` (also `npm run docs:generate-functions`), so the page is
+ * regenerated on every build and cannot drift from the catalogue.
+ */
+
+import * as fs from 'fs'
+import * as path from 'path'
+import {HyperFormula} from '../src'
+import {renderBuiltinFunctionsTable, spliceFunctionsTable} from '../src/interpreter/functionMetadata/renderBuiltinFunctionsTable'
+
+const REPO_ROOT = path.resolve(__dirname, '..')
+const TEMPLATE_PATH = path.join(REPO_ROOT, 'docs/guide/built-in-functions.tmpl.md')
+const DOC_PATH = path.join(REPO_ROOT, 'docs/guide/built-in-functions.md')
+const LANGUAGE = 'enGB'
+
+/** Reads the committed template and returns the page with the generated table region spliced in. */
+function buildUpdatedFile(): string {
+ const entries = HyperFormula.getAvailableFunctions(LANGUAGE)
+ const detailsFor = (canonicalName: string) => HyperFormula.getFunctionDetails(canonicalName, LANGUAGE)
+ const generated = renderBuiltinFunctionsTable(entries, detailsFor)
+ const template = fs.readFileSync(TEMPLATE_PATH, 'utf8')
+ return spliceFunctionsTable(template, generated)
+}
+
+/** Generates the guide page from the template + current catalogue. */
+function main(): void {
+ fs.writeFileSync(DOC_PATH, buildUpdatedFile(), 'utf8')
+ process.stdout.write('built-in-functions.md generated from template.\n')
+}
+
+main()
diff --git a/scripts/hf249-migrate-function-docs.ts b/scripts/hf249-migrate-function-docs.ts
deleted file mode 100644
index 6b40bb8fe..000000000
--- a/scripts/hf249-migrate-function-docs.ts
+++ /dev/null
@@ -1,276 +0,0 @@
-/**
- * @license
- * Copyright (c) 2025 Handsoncode. All rights reserved.
- */
-
-/**
- * HF-249 — function metadata migration tool (dev-only; never shipped, because `tsconfig.json` `include`
- * is restricted to `["src"]`).
- *
- * Regenerates the per-category `FunctionDoc` catalogue files under
- * `src/interpreter/functionMetadata/categories/` (and their `index.ts` barrel) from
- * `docs/guide/built-in-functions.md`:
- *
- * - the canonical id set is taken from `implementedFunctions` (aliases and protected ids are excluded);
- * - `category` and `shortDescription` come from the doc table's "Function ID" section and "Description" column;
- * - parameter names come from the "Syntax" column, but their COUNT and optionality are governed by the
- * implementation arity — the syntax column is only a (sometimes dirty) source of human-readable names.
- *
- * Run with: `npm run tsnode scripts/hf249-migrate-function-docs.ts`
- */
-
-import * as fs from 'fs'
-import * as path from 'path'
-import {HyperFormula} from '../src'
-import {FUNCTION_CATEGORIES, FunctionCategory} from '../src/interpreter/functionMetadata/FunctionDescription'
-
-const REPO_ROOT = path.resolve(__dirname, '..')
-const DOC_PATH = path.join(REPO_ROOT, 'docs/guide/built-in-functions.md')
-const CATEGORIES_DIR = path.join(REPO_ROOT, 'src/interpreter/functionMetadata/categories')
-const INDEX_PATH = path.join(REPO_ROOT, 'src/interpreter/functionMetadata/index.ts')
-
-/** Parameter names the documentation under-specifies relative to the implementation arity (already snake_case). */
-const PARAMETER_NAME_OVERRIDES: Record = {
- 'T.TEST': ['array1', 'array2', 'tails', 'type'],
-}
-
-/**
- * Word-segmentation fixes for parameter tokens the documentation writes as a single run-on word (e.g. `Sumrange`),
- * so they read consistently with their multi-word siblings (`Sum_Range` -> `sum_range`). Keyed by the mechanically
- * snake-cased token; the value is the corrected snake_case name.
- */
-const SNAKE_CASE_SEGMENTATION: Record = {
- sumrange: 'sum_range',
- searchcriterion: 'search_criterion',
- logicalvalue: 'logical_value',
- datestring: 'date_string',
- timestring: 'time_string',
- startdate: 'start_date',
- minimumlength: 'minimum_length',
- lowerbound: 'lower_bound',
- upperbound: 'upper_bound',
- numberx: 'number_x',
- numbery: 'number_y',
-}
-
-/**
- * Converts a raw parameter name to snake_case: lowercase words joined by underscores, with camelCase/PascalCase and
- * acronym boundaries split, existing separators unified, and trailing digits kept attached to their word
- * (`Number1`, `Number_1` -> `number1`). A small segmentation table then fixes run-on tokens the docs left unsplit.
- *
- * @param {string} name - the raw parameter name from the syntax column
- */
-export function toSnakeCase(name: string): string {
- const mechanical = name
- .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
- .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
- .replace(/[\s.\-]+/g, '_')
- .toLowerCase()
- .replace(/_+(\d)/g, '$1')
- .replace(/_+/g, '_')
- .replace(/^_|_$/g, '')
- return SNAKE_CASE_SEGMENTATION[mechanical] ?? mechanical
-}
-
-interface DocRow {
- category: FunctionCategory,
- description: string,
- syntax: string,
-}
-
-/** Maps each documented function id to its category, description and raw syntax (first occurrence wins). */
-function parseDoc(): Map {
- const markdown = fs.readFileSync(DOC_PATH, 'utf8')
- const rows = new Map()
- let category: FunctionCategory | null = null
- for (const line of markdown.split('\n')) {
- const header = /^### (.+?)\s*$/.exec(line)
- if (header) {
- category = (FUNCTION_CATEGORIES as readonly string[]).includes(header[1]) ? header[1] as FunctionCategory : null
- continue
- }
- if (category && line.startsWith('|') && !/^\|\s*:?-+/.test(line)) {
- const cells = line.split('|').map(cell => cell.trim())
- const id = cells[1]
- if (!id || id === 'Function ID' || rows.has(id)) {
- continue
- }
- rows.set(id, {category, description: cells[2] ?? '', syntax: cells[3] ?? ''})
- }
- }
- return rows
-}
-
-/** Extracts trimmed parameter names from a syntax string, dropping optional brackets, quotes and ellipsis markers. */
-function parseSyntaxNames(syntax: string): string[] {
- const open = syntax.indexOf('(')
- const close = syntax.lastIndexOf(')')
- if (open < 0 || close < 0) {
- return []
- }
- const inner = syntax.slice(open + 1, close).replace(/[[\]]/g, '')
- return inner
- .split(',')
- .map(part => part.trim().replace(/^"|"$/g, '').replace(/^\.+/, '').trim())
- .filter(part => part.length > 0)
-}
-
-/** Disambiguates repeated names (e.g. `[Number, Number]` -> `[Number1, Number2]`) so every name is unique. */
-function uniquify(names: string[]): string[] {
- const totals: Record = {}
- for (const name of names) {
- totals[name] = (totals[name] ?? 0) + 1
- }
- const seen: Record = {}
- return names.map(name => {
- if (totals[name] > 1) {
- seen[name] = (seen[name] ?? 0) + 1
- return `${name}${seen[name]}`
- }
- return name
- })
-}
-
-/**
- * Produces exactly `arity` unique, non-empty snake_case parameter names. The syntax column lists
- * `Name1, Name2, ...NameN` for repeating groups, so the first `arity` names already collapse those groups onto the
- * implementation arity; each is normalized to snake_case and any shortfall is padded with `arg1`, `arg2`, ...
- */
-function deriveParameterNames(id: string, syntax: string, arity: number): string[] {
- const override = PARAMETER_NAME_OVERRIDES[id]
- if (override !== undefined) {
- if (override.length !== arity) {
- throw new Error(`Override for ${id} has ${override.length} names but the implementation arity is ${arity}`)
- }
- return override
- }
- const names = parseSyntaxNames(syntax).slice(0, arity).map(toSnakeCase)
- while (names.length < arity) {
- names.push(`arg${names.length + 1}`)
- }
- return uniquify(names)
-}
-
-/** The category's file name, e.g. `'Math and trigonometry'` -> `'math-and-trigonometry'`. */
-function kebabCase(category: string): string {
- return category.toLowerCase().replace(/\s+/g, '-')
-}
-
-/** The category's exported constant name, e.g. `'Math and trigonometry'` -> `'MATH_AND_TRIGONOMETRY_DOCS'`. */
-function constName(category: string): string {
- return `${category.toUpperCase().replace(/\s+/g, '_')}_DOCS`
-}
-
-/** Renders a single-quoted TypeScript string literal, escaping backslashes and single quotes. */
-function asStringLiteral(value: string): string {
- return `'${value.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}'`
-}
-
-/** Renders an object key: a bare identifier where valid, otherwise a quoted string (e.g. `'HF.ADD'`). */
-function asKey(id: string): string {
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(id) ? id : asStringLiteral(id)
-}
-
-const LICENSE = `/**
- * @license
- * Copyright (c) 2025 Handsoncode. All rights reserved.
- */`
-
-interface Entry {
- id: string,
- description: string,
- names: string[],
-}
-
-/** Renders the source of one `categories/.ts` file, with entries sorted by canonical id. */
-function emitCategoryFile(category: FunctionCategory, entries: Entry[]): string {
- const body = [...entries].sort((a, b) => a.id.localeCompare(b.id)).map(entry => {
- const params = entry.names.map(name => `{name: ${asStringLiteral(name)}, description: ''}`).join(', ')
- return ` ${asKey(entry.id)}: {
- category: ${asStringLiteral(category)},
- shortDescription: ${asStringLiteral(entry.description)},
- parameters: [${params}],
- },`
- }).join('\n')
- return `${LICENSE}
-
-import {FunctionDoc} from '../FunctionDescription'
-
-/**
- * Catalogue entries for the "${category}" category. Generated from \`docs/guide/built-in-functions.md\` by
- * \`scripts/hf249-migrate-function-docs.ts\`; parameter descriptions are authored in a later phase.
- */
-export const ${constName(category)}: Record = {
-${body}
-}
-`
-}
-
-/** Renders the source of the `index.ts` barrel that composes every category file into `FUNCTION_DOCS`. */
-function emitIndex(categories: FunctionCategory[]): string {
- const ordered = [...categories].sort((a, b) => kebabCase(a).localeCompare(kebabCase(b)))
- const imports = ordered.map(category => `import {${constName(category)}} from './categories/${kebabCase(category)}'`).join('\n')
- const spreads = ordered.map(category => ` ...${constName(category)},`).join('\n')
- return `${LICENSE}
-
-import {FunctionDoc} from './FunctionDescription'
-${imports}
-
-export * from './FunctionDescription'
-
-/**
- * Canonical-id-keyed catalogue of human-readable function metadata, composed from the per-category files.
- * Generated by \`scripts/hf249-migrate-function-docs.ts\`. Coverage of the whole canonical set is enforced by test.
- */
-export const FUNCTION_DOCS: Record = {
-${spreads}
-}
-`
-}
-
-/** Reads the docs and implementation arity, derives every entry, and writes the category files and barrel. */
-function main(): void {
- const arityById = new Map()
- for (const plugin of HyperFormula.getAllFunctionPlugins()) {
- const impl = plugin.implementedFunctions
- for (const id of Object.keys(impl)) {
- arityById.set(id, (impl[id].parameters ?? []).length)
- }
- }
-
- const docRows = parseDoc()
- const byCategory = new Map()
- const missing: string[] = []
-
- for (const id of arityById.keys()) {
- const row = docRows.get(id)
- if (row === undefined) {
- missing.push(id)
- continue
- }
- const names = deriveParameterNames(id, row.syntax, arityById.get(id) as number)
- const list = byCategory.get(row.category) ?? []
- list.push({id, description: row.description, names})
- byCategory.set(row.category, list)
- }
-
- if (missing.length > 0) {
- throw new Error(`No documentation row for canonical ids: ${missing.join(', ')}`)
- }
-
- const categories = [...byCategory.keys()]
- for (const category of categories) {
- const file = path.join(CATEGORIES_DIR, `${kebabCase(category)}.ts`)
- fs.writeFileSync(file, emitCategoryFile(category, byCategory.get(category) as Entry[]))
- console.log(`wrote ${path.relative(REPO_ROOT, file)} (${(byCategory.get(category) as Entry[]).length})`)
- }
- fs.writeFileSync(INDEX_PATH, emitIndex(categories))
-
- const total = categories.reduce((sum, category) => sum + (byCategory.get(category) as Entry[]).length, 0)
- console.log(`wrote ${path.relative(REPO_ROOT, INDEX_PATH)}; ${total} functions across ${categories.length} categories`)
-}
-
-// Only regenerate when executed directly; guarded so helpers (e.g. `toSnakeCase`) can be imported without side effects.
-if (require.main === module) {
- main()
-}
diff --git a/src/HyperFormula.ts b/src/HyperFormula.ts
index 7bf5f9051..195a35335 100644
--- a/src/HyperFormula.ts
+++ b/src/HyperFormula.ts
@@ -48,6 +48,7 @@ import {FunctionPluginDefinition} from './interpreter'
import {FUNCTION_DOCS} from './interpreter/functionMetadata'
import {buildCustomFunctionDetails, buildCustomFunctionListEntry, buildFunctionDetails, buildFunctionListEntry, StructuralMetadata} from './interpreter/functionMetadata/buildFunctionDescriptions'
import {FunctionDetails, FunctionDoc, FunctionListEntry} from './interpreter/functionMetadata/FunctionDescription'
+import {PROTECTED_FUNCTION_METADATA} from './interpreter/functionMetadata/protectedFunctionMetadata'
import {FunctionRegistry, FunctionTranslationsPackage} from './interpreter/FunctionRegistry'
import {FormatInfo} from './interpreter/InterpreterValue'
import {LazilyTransformingAstService} from './LazilyTransformingAstService'
@@ -713,7 +714,16 @@ export class HyperFormula implements TypedEmitter {
validateArgToType(code, 'string', 'code')
const plugin = FunctionRegistry.getFunctionPlugin(canonicalName)
if (plugin === undefined) {
- return undefined
+ // Protected ids (VERSION, OFFSET) have no registered plugin by design (`getFunctionPlugin` always returns
+ // `undefined` for them), but the metadata API still describes them — resolved from the authored catalogue
+ // doc, so getFunctionDetails agrees with getAvailableFunctions. Anything else with no plugin is genuinely
+ // unknown. `buildFunctionDetailsFor` returns `undefined` for a protected id without a catalogue doc, so the
+ // list and the details stay consistent for those too.
+ if (!FunctionRegistry.functionIsProtected(canonicalName)) {
+ return undefined
+ }
+ const language = this.getLanguage(code)
+ return HyperFormula.buildFunctionDetailsFor(canonicalName, undefined, language)
}
// Resolve aliases to their canonical target id before checking built-in ownership
const canonicalId = plugin.aliases?.[canonicalName] ?? canonicalName
@@ -738,6 +748,16 @@ export class HyperFormula implements TypedEmitter {
* @param {FunctionPluginDefinition | undefined} plugin - the plugin registered for `functionId`, or `undefined`
*/
private static resolveFunctionMetadata(functionId: string, plugin: FunctionPluginDefinition | undefined): { doc: FunctionDoc | undefined, metadata: StructuralMetadata } | undefined {
+ // Protected ids (VERSION, OFFSET) are excluded from the plugin registry by design (`getFunctionPlugin` always
+ // returns `undefined` for them), so they would otherwise fall straight into the `plugin === undefined` case
+ // below and disappear from the metadata API. Kuba decided (HF-249) that they must still be described, because
+ // a user can call them from a formula: resolve them here from the authored catalogue doc and structural
+ // metadata instead of from a plugin. A protected id stays unlisted unless BOTH are authored — requiring the
+ // structural metadata keeps `buildFunctionDetails` from reading `repeatLastArgs`/`parameters` off `undefined`
+ // if the two maps ever drift (fail-safe: the function is omitted rather than crashing the metadata API).
+ if (FunctionRegistry.functionIsProtected(functionId) && FUNCTION_DOCS[functionId] !== undefined && PROTECTED_FUNCTION_METADATA[functionId] !== undefined) {
+ return {doc: FUNCTION_DOCS[functionId], metadata: PROTECTED_FUNCTION_METADATA[functionId]}
+ }
if (plugin === undefined) {
return undefined
}
diff --git a/src/interpreter/FunctionRegistry.ts b/src/interpreter/FunctionRegistry.ts
index 9fdf38993..b4285e010 100644
--- a/src/interpreter/FunctionRegistry.ts
+++ b/src/interpreter/FunctionRegistry.ts
@@ -162,9 +162,13 @@ export class FunctionRegistry {
}
/**
- * Returns the ids of all registered, non-protected built-in functions, including aliases. Used by the static
- * function-metadata API to list every built-in function reachable in a formula. Custom functions registered via
- * registerFunctionPlugin are excluded; they appear only in the instance method variant.
+ * Returns the ids of all functions the function-metadata API (`getAvailableFunctions`/`getFunctionDetails`)
+ * should describe: every registered, non-protected built-in function (canonical ids plus their aliases), plus
+ * the protected ids (e.g. `VERSION`, `OFFSET`). Custom functions registered via registerFunctionPlugin are
+ * excluded; they appear only in the instance method variant. Protected ids are excluded from registration
+ * (`this.plugins`) so they can never be unregistered or shadowed, but a user can still call them from a formula,
+ * so the metadata API surfaces them too (HF-249). Safe to include here: this method is consumed only by the
+ * metadata API, never by anything that would let a caller register/unregister against a protected id.
*/
public static getListableFunctionIds(): string[] {
const ids: string[] = []
@@ -175,6 +179,9 @@ export class FunctionRegistry {
ids.push(functionId)
}
}
+ // Surface protected ids (e.g. VERSION, OFFSET): excluded from `this.plugins` so they can't be
+ // unregistered or shadowed, but callable from a formula, so the metadata API lists them too (HF-249).
+ ids.push(...this._protectedPlugins.keys())
return ids
}
@@ -292,11 +299,18 @@ export class FunctionRegistry {
}
/**
- * Returns the ids of all functions registered in this instance, including aliases and any custom (user-registered)
- * functions, excluding protected ids. Used by the instance-level function-metadata API.
+ * Returns the ids of all functions the instance-level function-metadata API should describe: every function
+ * registered in this instance (aliases and any custom/user-registered functions included), plus the protected
+ * ids (e.g. `VERSION`, `OFFSET`). `instancePlugins` already contains `VERSION` (the constructor loads any
+ * protected id that has a plugin, unprotected, so it can be executed), but not `OFFSET` (it has no plugin at
+ * all — it is transformed at parse time). Appending `FunctionRegistry._protectedPlugins`'s keys explicitly, the
+ * same way the static {@link FunctionRegistry.getListableFunctionIds} does, covers both uniformly instead of
+ * relying on the constructor's incidental loading; de-duplicated via `Set` because `VERSION` would otherwise be
+ * listed twice (once from `instancePlugins`, once from `_protectedPlugins`). A user can see these ids via
+ * `getAvailableFunctions`/`getFunctionDetails` even though they can never be unregistered (HF-249).
*/
public getListableFunctionIds(): string[] {
- return Array.from(this.instancePlugins.keys()).filter(id => !FunctionRegistry.functionIsProtected(id))
+ return Array.from(new Set([...this.instancePlugins.keys(), ...FunctionRegistry._protectedPlugins.keys()]))
}
public getFunction(functionId: string): Maybe {
diff --git a/src/interpreter/functionMetadata/categories/array-manipulation.ts b/src/interpreter/functionMetadata/categories/array-manipulation.ts
index bf03946af..c94d8d414 100644
--- a/src/interpreter/functionMetadata/categories/array-manipulation.ts
+++ b/src/interpreter/functionMetadata/categories/array-manipulation.ts
@@ -7,7 +7,7 @@ import {FunctionDoc} from '../FunctionDescription'
/**
* Catalogue entries for the "Array manipulation" category. Generated from `docs/guide/built-in-functions.md` by
- * `scripts/hf249-migrate-function-docs.ts`; parameter descriptions are authored in a later phase.
+ * a one-time migration script (since removed); parameter descriptions are authored in a later phase.
*/
export const ARRAY_MANIPULATION_DOCS: Record = {
ARRAY_CONSTRAIN: {
diff --git a/src/interpreter/functionMetadata/categories/database.ts b/src/interpreter/functionMetadata/categories/database.ts
index 4a2d3e1d7..3163404ee 100644
--- a/src/interpreter/functionMetadata/categories/database.ts
+++ b/src/interpreter/functionMetadata/categories/database.ts
@@ -7,7 +7,7 @@ import {FunctionDoc} from '../FunctionDescription'
/**
* Catalogue entries for the "Database" category. Generated from `docs/guide/built-in-functions.md` by
- * `scripts/hf249-migrate-function-docs.ts`; parameter descriptions are authored in a later phase.
+ * a one-time migration script (since removed); parameter descriptions are authored in a later phase.
*/
export const DATABASE_DOCS: Record = {
DAVERAGE: {
diff --git a/src/interpreter/functionMetadata/categories/date-and-time.ts b/src/interpreter/functionMetadata/categories/date-and-time.ts
index fce033bb0..18e86a649 100644
--- a/src/interpreter/functionMetadata/categories/date-and-time.ts
+++ b/src/interpreter/functionMetadata/categories/date-and-time.ts
@@ -7,7 +7,7 @@ import {FunctionDoc} from '../FunctionDescription'
/**
* Catalogue entries for the "Date and time" category. Generated from `docs/guide/built-in-functions.md` by
- * `scripts/hf249-migrate-function-docs.ts`; parameter descriptions are authored in a later phase.
+ * a one-time migration script (since removed); parameter descriptions are authored in a later phase.
*/
export const DATE_AND_TIME_DOCS: Record = {
DATE: {
diff --git a/src/interpreter/functionMetadata/categories/engineering.ts b/src/interpreter/functionMetadata/categories/engineering.ts
index eb15286fc..766de9dbb 100644
--- a/src/interpreter/functionMetadata/categories/engineering.ts
+++ b/src/interpreter/functionMetadata/categories/engineering.ts
@@ -7,7 +7,7 @@ import {FunctionDoc} from '../FunctionDescription'
/**
* Catalogue entries for the "Engineering" category. Generated from `docs/guide/built-in-functions.md` by
- * `scripts/hf249-migrate-function-docs.ts`; parameter descriptions are authored in a later phase.
+ * a one-time migration script (since removed); parameter descriptions are authored in a later phase.
*/
export const ENGINEERING_DOCS: Record = {
BIN2DEC: {
diff --git a/src/interpreter/functionMetadata/categories/financial.ts b/src/interpreter/functionMetadata/categories/financial.ts
index 3eb88d999..5ebac4378 100644
--- a/src/interpreter/functionMetadata/categories/financial.ts
+++ b/src/interpreter/functionMetadata/categories/financial.ts
@@ -7,7 +7,7 @@ import {FunctionDoc} from '../FunctionDescription'
/**
* Catalogue entries for the "Financial" category. Generated from `docs/guide/built-in-functions.md` by
- * `scripts/hf249-migrate-function-docs.ts`; parameter descriptions are authored in a later phase.
+ * a one-time migration script (since removed); parameter descriptions are authored in a later phase.
*/
export const FINANCIAL_DOCS: Record = {
CUMIPMT: {
diff --git a/src/interpreter/functionMetadata/categories/information.ts b/src/interpreter/functionMetadata/categories/information.ts
index 1799f73a6..c6bf113d9 100644
--- a/src/interpreter/functionMetadata/categories/information.ts
+++ b/src/interpreter/functionMetadata/categories/information.ts
@@ -6,8 +6,9 @@
import {FunctionDoc} from '../FunctionDescription'
/**
- * Catalogue entries for the "Information" category. Generated from `docs/guide/built-in-functions.md` by
- * `scripts/hf249-migrate-function-docs.ts`; parameter descriptions are authored in a later phase.
+ * Catalogue entries for the "Information" category. Most entries were migrated from `docs/guide/built-in-functions.md`
+ * by a one-time migration script (since removed); some (e.g. the protected `VERSION` function) are hand-authored.
+ * Parameter descriptions are authored in a later phase.
*/
export const INFORMATION_DOCS: Record = {
ISBINARY: {
@@ -90,4 +91,12 @@ export const INFORMATION_DOCS: Record = {
shortDescription: 'Returns number of sheet of a given reference or number of all sheets in workbook when no argument is provided.',
parameters: [{name: 'value', description: ''}],
},
+ // VERSION is a protected function (its plugin, VersionPlugin, is kept out of the general registry so it can
+ // never be unregistered; see `FunctionRegistry._protectedPlugins`). Its structural metadata (parameters,
+ // repeatLastArgs) is authored separately in `PROTECTED_FUNCTION_METADATA`.
+ VERSION: {
+ category: 'Information',
+ shortDescription: 'Returns the version number of HyperFormula.',
+ parameters: [],
+ },
}
diff --git a/src/interpreter/functionMetadata/categories/logical.ts b/src/interpreter/functionMetadata/categories/logical.ts
index 87a20b9ec..ee34d42eb 100644
--- a/src/interpreter/functionMetadata/categories/logical.ts
+++ b/src/interpreter/functionMetadata/categories/logical.ts
@@ -7,7 +7,7 @@ import {FunctionDoc} from '../FunctionDescription'
/**
* Catalogue entries for the "Logical" category. Generated from `docs/guide/built-in-functions.md` by
- * `scripts/hf249-migrate-function-docs.ts`; parameter descriptions are authored in a later phase.
+ * a one-time migration script (since removed); parameter descriptions are authored in a later phase.
*/
export const LOGICAL_DOCS: Record = {
AND: {
diff --git a/src/interpreter/functionMetadata/categories/lookup-and-reference.ts b/src/interpreter/functionMetadata/categories/lookup-and-reference.ts
index 456e76265..8e91f1f52 100644
--- a/src/interpreter/functionMetadata/categories/lookup-and-reference.ts
+++ b/src/interpreter/functionMetadata/categories/lookup-and-reference.ts
@@ -6,8 +6,9 @@
import {FunctionDoc} from '../FunctionDescription'
/**
- * Catalogue entries for the "Lookup and reference" category. Generated from `docs/guide/built-in-functions.md` by
- * `scripts/hf249-migrate-function-docs.ts`; parameter descriptions are authored in a later phase.
+ * Catalogue entries for the "Lookup and reference" category. Most entries were migrated from
+ * `docs/guide/built-in-functions.md` by a one-time migration script (since removed); some (e.g. the protected `OFFSET`
+ * function) are hand-authored. Parameter descriptions are authored in a later phase.
*/
export const LOOKUP_AND_REFERENCE_DOCS: Record = {
ADDRESS: {
@@ -55,6 +56,14 @@ export const LOOKUP_AND_REFERENCE_DOCS: Record = {
shortDescription: 'Returns the relative position of an item in an array that matches a specified value.',
parameters: [{name: 'search_criterion', description: ''}, {name: 'lookup_array', description: ''}, {name: 'match_type', description: ''}],
},
+ // OFFSET is a protected function (parse-time transformed into a cell/range reference, so it has no plugin or
+ // implementation metadata; see `FunctionRegistry._protectedPlugins`). Its structural metadata (parameter
+ // optionality, repeatLastArgs) is authored separately in `PROTECTED_FUNCTION_METADATA`.
+ OFFSET: {
+ category: 'Lookup and reference',
+ shortDescription: 'Returns the value of a cell offset by a certain number of rows and columns from a given reference point.',
+ parameters: [{name: 'reference', description: ''}, {name: 'rows', description: ''}, {name: 'columns', description: ''}, {name: 'height', description: ''}, {name: 'width', description: ''}],
+ },
ROW: {
category: 'Lookup and reference',
shortDescription: 'Returns row number of a given reference or formula reference if argument not provided.',
diff --git a/src/interpreter/functionMetadata/categories/math-and-trigonometry.ts b/src/interpreter/functionMetadata/categories/math-and-trigonometry.ts
index 29f51f8f7..6e22ff39c 100644
--- a/src/interpreter/functionMetadata/categories/math-and-trigonometry.ts
+++ b/src/interpreter/functionMetadata/categories/math-and-trigonometry.ts
@@ -7,7 +7,7 @@ import {FunctionDoc} from '../FunctionDescription'
/**
* Catalogue entries for the "Math and trigonometry" category. Generated from `docs/guide/built-in-functions.md` by
- * `scripts/hf249-migrate-function-docs.ts`; parameter descriptions are authored in a later phase.
+ * a one-time migration script (since removed); parameter descriptions are authored in a later phase.
*/
export const MATH_AND_TRIGONOMETRY_DOCS: Record = {
ABS: {
@@ -326,9 +326,9 @@ export const MATH_AND_TRIGONOMETRY_DOCS: Record = {
parameters: [{name: 'function', description: ''}, {name: 'number1', description: ''}],
},
// HAND-AUTHORED reference functions (HF-249): SUM and SUMIF carry real parameter descriptions, examples and a
- // documentationUrl so the Formula Builder team can test rendering of populated metadata. The migration generator
- // (scripts/hf249-migrate-function-docs.ts) does NOT emit `examples`/`documentationUrl`, so DO NOT re-run it over
- // this file without merge-preserving these two entries, or they will be silently reset to empty.
+ // documentationUrl so the Formula Builder team can test rendering of populated metadata. They are the only
+ // entries with `examples`/`documentationUrl` populated, so preserve them if this catalogue is ever re-seeded in
+ // bulk from the guide, or they will be silently reset to empty.
SUM: {
category: 'Math and trigonometry',
shortDescription: 'Sums up the values of the specified cells.',
diff --git a/src/interpreter/functionMetadata/categories/matrix-functions.ts b/src/interpreter/functionMetadata/categories/matrix-functions.ts
index fc3f4ded3..5ab891059 100644
--- a/src/interpreter/functionMetadata/categories/matrix-functions.ts
+++ b/src/interpreter/functionMetadata/categories/matrix-functions.ts
@@ -7,7 +7,7 @@ import {FunctionDoc} from '../FunctionDescription'
/**
* Catalogue entries for the "Matrix functions" category. Generated from `docs/guide/built-in-functions.md` by
- * `scripts/hf249-migrate-function-docs.ts`; parameter descriptions are authored in a later phase.
+ * a one-time migration script (since removed); parameter descriptions are authored in a later phase.
*/
export const MATRIX_FUNCTIONS_DOCS: Record = {
MAXPOOL: {
diff --git a/src/interpreter/functionMetadata/categories/operator.ts b/src/interpreter/functionMetadata/categories/operator.ts
index d7a7258b8..aa7b5306c 100644
--- a/src/interpreter/functionMetadata/categories/operator.ts
+++ b/src/interpreter/functionMetadata/categories/operator.ts
@@ -7,7 +7,7 @@ import {FunctionDoc} from '../FunctionDescription'
/**
* Catalogue entries for the "Operator" category. Generated from `docs/guide/built-in-functions.md` by
- * `scripts/hf249-migrate-function-docs.ts`; parameter descriptions are authored in a later phase.
+ * a one-time migration script (since removed); parameter descriptions are authored in a later phase.
*/
export const OPERATOR_DOCS: Record = {
'HF.ADD': {
diff --git a/src/interpreter/functionMetadata/categories/statistical.ts b/src/interpreter/functionMetadata/categories/statistical.ts
index 91751b9e8..055a9761e 100644
--- a/src/interpreter/functionMetadata/categories/statistical.ts
+++ b/src/interpreter/functionMetadata/categories/statistical.ts
@@ -7,7 +7,7 @@ import {FunctionDoc} from '../FunctionDescription'
/**
* Catalogue entries for the "Statistical" category. Generated from `docs/guide/built-in-functions.md` by
- * `scripts/hf249-migrate-function-docs.ts`; parameter descriptions are authored in a later phase.
+ * a one-time migration script (since removed); parameter descriptions are authored in a later phase.
*/
export const STATISTICAL_DOCS: Record = {
AVEDEV: {
diff --git a/src/interpreter/functionMetadata/categories/text.ts b/src/interpreter/functionMetadata/categories/text.ts
index b07c2347f..90b6e31ef 100644
--- a/src/interpreter/functionMetadata/categories/text.ts
+++ b/src/interpreter/functionMetadata/categories/text.ts
@@ -7,7 +7,7 @@ import {FunctionDoc} from '../FunctionDescription'
/**
* Catalogue entries for the "Text" category. Generated from `docs/guide/built-in-functions.md` by
- * `scripts/hf249-migrate-function-docs.ts`; parameter descriptions are authored in a later phase.
+ * a one-time migration script (since removed); parameter descriptions are authored in a later phase.
*/
export const TEXT_DOCS: Record = {
CHAR: {
diff --git a/src/interpreter/functionMetadata/formatFunctionSyntax.ts b/src/interpreter/functionMetadata/formatFunctionSyntax.ts
new file mode 100644
index 000000000..3e233e6c9
--- /dev/null
+++ b/src/interpreter/functionMetadata/formatFunctionSyntax.ts
@@ -0,0 +1,25 @@
+/**
+ * @license
+ * Copyright (c) 2025 Handsoncode. All rights reserved.
+ */
+
+/**
+ * Builds the human-readable syntax string for a function from its parameter list, e.g. `SUM(Number1, ...)`.
+ * Reuses the convention previously shipped as `generateSyntax`: each optional parameter is wrapped in its own
+ * `[brackets]`, and a single `, ...` suffix denotes that the trailing arguments repeat. Internal to the
+ * functionMetadata module (docs generator + future consumers); not exported from the package index.
+ *
+ * @param {string} localizedName - the function name as shown to the user, e.g. `'SUMIF'`
+ * @param {{ name: string, optional: boolean }[]} parameters - ordered parameters with optionality
+ * @param {number} repeatLastArgs - number of trailing parameters that repeat; `> 0` adds the `, ...` suffix
+ * @returns {string} the syntax string, e.g. `'SUMIF(Range, Criteria, [Sumrange])'`
+ */
+export function formatFunctionSyntax(
+ localizedName: string,
+ parameters: { name: string, optional: boolean }[],
+ repeatLastArgs: number,
+): string {
+ const rendered = parameters.map(parameter => parameter.optional ? `[${parameter.name}]` : parameter.name)
+ const repeatSuffix = repeatLastArgs > 0 ? ', ...' : ''
+ return `${localizedName}(${rendered.join(', ')}${repeatSuffix})`
+}
diff --git a/src/interpreter/functionMetadata/index.ts b/src/interpreter/functionMetadata/index.ts
index fe2c5d0d6..ddcc7174d 100644
--- a/src/interpreter/functionMetadata/index.ts
+++ b/src/interpreter/functionMetadata/index.ts
@@ -22,7 +22,7 @@ export * from './FunctionDescription'
/**
* Canonical-id-keyed catalogue of human-readable function metadata, composed from the per-category files.
- * Generated by `scripts/hf249-migrate-function-docs.ts`. Coverage of the whole canonical set is enforced by test.
+ * Generated by a one-time migration script (since removed). Coverage of the whole canonical set is enforced by test.
*/
export const FUNCTION_DOCS: Record = {
...ARRAY_MANIPULATION_DOCS,
diff --git a/src/interpreter/functionMetadata/protectedFunctionMetadata.ts b/src/interpreter/functionMetadata/protectedFunctionMetadata.ts
new file mode 100644
index 000000000..a5cd77f34
--- /dev/null
+++ b/src/interpreter/functionMetadata/protectedFunctionMetadata.ts
@@ -0,0 +1,40 @@
+/**
+ * @license
+ * Copyright (c) 2025 Handsoncode. All rights reserved.
+ */
+
+import {FunctionArgumentType} from '../plugin/FunctionPlugin'
+import {StructuralMetadata} from './buildFunctionDescriptions'
+
+/**
+ * Authored structural metadata (parameter optionality, `repeatLastArgs`) for the protected built-in functions
+ * (`FunctionRegistry._protectedPlugins`), keyed by canonical id.
+ *
+ * Protected ids are not registered as ordinary plugins, so they carry no `implementedFunctions` entry for
+ * {@link HyperFormula.resolveFunctionMetadata} to read arity/optionality from:
+ * - `OFFSET` is transformed at parse time into a cell/range reference and has no plugin at all.
+ * - `VERSION` has a plugin (`VersionPlugin`), but it is deliberately excluded from the general registry so it can
+ * never be unregistered; reusing its `implementedFunctions` entry would require importing the protected plugin
+ * into the metadata builders, which this module avoids.
+ *
+ * Each entry's `parameters.length` MUST equal the corresponding `FunctionDoc.parameters.length` in `FUNCTION_DOCS`.
+ * Unlike plugin-backed functions, `HyperFormula.resolveFunctionMetadata`'s arity cross-check does not run for
+ * protected ids (they return early, before that check), so this invariant is not enforced at runtime — it must be
+ * kept true by hand when editing either map, otherwise `buildFunctionDetails` throws for the mismatched id.
+ */
+export const PROTECTED_FUNCTION_METADATA: Record = {
+ OFFSET: {
+ parameters: [
+ {argumentType: FunctionArgumentType.ANY},
+ {argumentType: FunctionArgumentType.NUMBER},
+ {argumentType: FunctionArgumentType.NUMBER},
+ {argumentType: FunctionArgumentType.NUMBER, optionalArg: true},
+ {argumentType: FunctionArgumentType.NUMBER, optionalArg: true},
+ ],
+ repeatLastArgs: 0,
+ },
+ VERSION: {
+ parameters: [],
+ repeatLastArgs: 0,
+ },
+}
diff --git a/src/interpreter/functionMetadata/renderBuiltinFunctionsTable.ts b/src/interpreter/functionMetadata/renderBuiltinFunctionsTable.ts
new file mode 100644
index 000000000..7219fda80
--- /dev/null
+++ b/src/interpreter/functionMetadata/renderBuiltinFunctionsTable.ts
@@ -0,0 +1,98 @@
+/**
+ * @license
+ * Copyright (c) 2025 Handsoncode. All rights reserved.
+ */
+
+import {FUNCTION_CATEGORIES, FunctionCategory, FunctionDetails, FunctionListEntry} from './FunctionDescription'
+import {formatFunctionSyntax} from './formatFunctionSyntax'
+
+/** Opening marker; the generated table region begins on the next line. */
+export const FUNCTIONS_TABLE_START = ''
+/** Closing marker; everything after it (e.g. footnote definitions) is hand-maintained. */
+export const FUNCTIONS_TABLE_END = ''
+
+/**
+ * Deterministic, environment-independent ordering for function names (never ambient locale). Intentionally
+ * pinned to English: the generated table is English-only, so ordering must not depend on the host locale. (The
+ * public API's list ordering is language-derived; this renderer's is deliberately English-only.)
+ */
+const COLLATOR = new Intl.Collator('en', {sensitivity: 'variant', caseFirst: 'upper'})
+
+/** Escapes only the table-breaking pipe; all other markdown (links, code, `
`, footnotes) is verbatim. */
+function escapeCell(text: string): string {
+ return text.replace(/\r?\n/g, '
').replace(/\|/g, '\\|')
+}
+
+/**
+ * Renders the built-in functions table as markdown: one `### Category` section per category (in the canonical
+ * `FUNCTION_CATEGORIES` order), each a 3-column table (Function ID | Description | Syntax). Rows are sorted by a
+ * pinned collator for deterministic, byte-idempotent output, with the canonical name as a stable tiebreaker for
+ * entries that share a localized name (mirrors `HyperFormula.buildAvailableFunctions`). Reads only the passed
+ * entries + details provider, so it is independent of how the catalogue is stored (forward-compatible with a
+ * per-function file split).
+ *
+ * @param {FunctionListEntry[]} entries - the function set to document (e.g. `HyperFormula.getAvailableFunctions`)
+ * @param {(canonicalName: string) => FunctionDetails | undefined} detailsFor - resolves a function's details
+ * @returns {string} the markdown for the table region (no surrounding markers, LF, trailing newline)
+ * @throws {Error} when a listed entry has no resolvable details
+ */
+export function renderBuiltinFunctionsTable(
+ entries: FunctionListEntry[],
+ detailsFor: (canonicalName: string) => FunctionDetails | undefined,
+): string {
+ const byCategory = new Map()
+ for (const entry of entries) {
+ if (entry.category === undefined) {
+ continue
+ }
+ const bucket = byCategory.get(entry.category) ?? []
+ bucket.push(entry)
+ byCategory.set(entry.category, bucket)
+ }
+
+ const sections: string[] = []
+ for (const category of FUNCTION_CATEGORIES) {
+ const bucket = byCategory.get(category)
+ if (bucket === undefined || bucket.length === 0) {
+ continue
+ }
+ bucket.sort((a, b) => COLLATOR.compare(a.localizedName, b.localizedName) || COLLATOR.compare(a.canonicalName, b.canonicalName))
+ const rows = bucket.map(entry => {
+ const details = detailsFor(entry.canonicalName)
+ if (details === undefined) {
+ throw new Error(`No details for listed function "${entry.canonicalName}"`)
+ }
+ const syntax = formatFunctionSyntax(details.localizedName, details.parameters, details.repeatLastArgs)
+ const anchor = ``
+ return `| ${anchor}${escapeCell(entry.localizedName)} | ${escapeCell(entry.shortDescription)} | ${escapeCell(syntax)} |`
+ })
+ sections.push(`### ${category}\n\n| Function ID | Description | Syntax |\n|:---|:---|:---|\n${rows.join('\n')}`)
+ }
+
+ return sections.join('\n\n') + '\n'
+}
+
+/**
+ * Replaces the content between the autogenerated markers in a markdown file, preserving everything outside them
+ * (intro prose, footnote definitions). Fails loud on a malformed marker state rather than clobbering the file.
+ *
+ * @param {string} fileContent - the full current file content
+ * @param {string} generatedSection - the markdown to place between the markers (from renderBuiltinFunctionsTable)
+ * @returns {string} the updated file content
+ * @throws {Error} when either marker is missing, duplicated, or out of order
+ */
+export function spliceFunctionsTable(fileContent: string, generatedSection: string): string {
+ const startCount = fileContent.split(FUNCTIONS_TABLE_START).length - 1
+ const endCount = fileContent.split(FUNCTIONS_TABLE_END).length - 1
+ if (startCount !== 1 || endCount !== 1) {
+ throw new Error(`Expected exactly one START and one END marker, found ${startCount}/${endCount}`)
+ }
+ const startIdx = fileContent.indexOf(FUNCTIONS_TABLE_START)
+ const endIdx = fileContent.indexOf(FUNCTIONS_TABLE_END)
+ if (endIdx < startIdx) {
+ throw new Error('END marker appears before START marker')
+ }
+ const before = fileContent.slice(0, startIdx + FUNCTIONS_TABLE_START.length)
+ const after = fileContent.slice(endIdx)
+ return `${before}\n${generatedSection}\n${after}`
+}