You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gristlabs_grist-core/app/common/Formula.ts

73 lines
2.4 KiB

/**
*
* This represents a formula supported under SQL for on-demand tables. This is currently
* a very small subset of the formulas supported by the data engine for regular tables.
*
* The following kinds of formula are supported:
* $refColId.colId [where colId is not itself a formula]
* $colId [where colId is not itself a formula]
* NNN [a non-negative integer]
* TODO: support a broader range of formula, by adding a parser or reusing Python parser.
* An argument for reusing Python parser: wwe already do substantial parsing of the formula code.
* E.g. Python does such amazing things as handle updating the formula when any of the columns
* referred to in Foo.lookup(bar=$baz).blah get updated.
*
*/
export type Formula = LiteralNumberFormula | ColumnFormula | ForeignColumnFormula | FormulaError;
// A simple copy of another column. E.g. "$Person"
export interface ColumnFormula {
kind: 'column';
colId: string;
}
// A copy of a column in another table (via a reference column). E.g. "$Person.FirstName"
export interface ForeignColumnFormula {
kind: 'foreignColumn';
colId: string;
refColId: string;
}
export interface LiteralNumberFormula {
kind: 'literalNumber';
value: number;
}
// A formula that couldn't be parsed.
export interface FormulaError {
kind: 'error';
msg: string;
}
/**
* Convert a string to a parsed formula. Regexes are adequate for the very few
* supported formulas, but once the syntax is at all flexible a proper parser will
* be needed. In principle, it might make sense to support python syntax, for
* compatibility with the data engine, but compatibility in corner cases will be
* fiddly given underlying differences between sqlite and python.
*/
export function parseFormula(txt: string): Formula {
// Formula of form: $x.y
let m = txt.match(/^\$([a-z]\w*)\.([a-z]\w*)$/i);
if (m) {
return {kind: 'foreignColumn', refColId: m[1], colId: m[2]};
}
// Formula of form: $x
m = txt.match(/^\$([a-z][a-z_0-9]*)$/i);
if (m) {
return {kind: 'column', colId: m[1]};
}
// Formula of form: NNN
m = txt.match(/^[0-9]+$/);
if (m) {
const value = parseInt(txt, 10);
if (isNaN(value)) { return {kind: 'error', msg: 'Cannot parse integer'}; }
return {kind: 'literalNumber', value};
}
// Everything else is an error.
return {kind: 'error', msg: 'Formula not supported'};
}