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}` +}