import type { ExpressionUnit, UnitAddress, UnitIdentifier, UnitRange, DependencyList, ParseResult, UnitLiteral, ParserFlags, UnitStructuredReference, RenderOptions } from './parser-types'; import { ArgumentSeparatorType, DecimalMarkType } from './parser-types'; /** * regex determines if a sheet name requires quotes. centralizing * this to simplify maintenance and reduce overlap/errors */ export declare const QuotedSheetNameRegex: RegExp; /** * similarly, illegal sheet name. we don't actually handle this in * the parser, but it seems like a reasonable place to keep this * definition. */ export declare const IllegalSheetNameRegex: RegExp; /** * parser for spreadsheet language. * * FIXME: this is stateless, think about exporting a singleton. * * (there is internal state, but it's only used during a Parse() call, * which runs synchronously). one benefit of using a singleton would be * consistency in decimal mark, we'd only have to set once. * * FIXME: the internal state is starting to grate. there's no reason for * it and it just confuses things because parsing is stateless (except for * configuration). internal state just keeps results from the last parse * operation. we should refactor so parsing is clean and returns all * results directly, caller can store if necessary. * * FIXME: split rendering into a separate class? would be a little cleaner. * * FIXME: we don't currently handle full-width punctuation. it would be * simple to parse, a little more complicated to keep track of if we wanted * to be able to rewrite. TODO/FIXME. * */ export declare class Parser { /** * accessor replacing old field. the actual value is moved to flags, * and should be set via the SetLocaleSettings method. */ get argument_separator(): ArgumentSeparatorType; /** * accessor replacing old field. the actual value is moved to flags, * and should be set via the SetLocaleSettings method. */ get decimal_mark(): DecimalMarkType; /** * unifying flags */ flags: ParserFlags; /** * FIXME: why is this a class member? at a minimum it could be static * FIXME: why are we doing this with a regex? */ protected r1c1_regex: RegExp; /** * internal argument separator, as a number. this is set internally on * parse call, following the argument_separator value. */ protected argument_separator_char: number; /** * internal decimal mark, as a number. */ protected decimal_mark_char: number; /** * imaginary number value. this is "i", except for those EE weirdos who * use "j". although I guess those guys put it in front, so it won't really * work anyway... let's stick with "i" for now. */ protected imaginary_char: 0x69 | 0x6A; /** * imaginary number as text for matching */ protected imaginary_number: 'i' | 'j'; /** * internal counter for incrementing IDs */ protected id_counter: number; protected expression: string; protected data: number[]; protected index: number; protected length: number; /** success flag */ protected valid: boolean; /** rolling error state */ protected error_position: number | undefined; /** rolling error state */ protected error: string | undefined; protected dependencies: DependencyList; protected address_refcount: { [index: string]: number; }; /** * full list of referenced addresses and ranges. we're adding this * to support highlighting, for which we need multiple instances * of a single address. the original dep list was used for graph dependencies, * so we compressed the list. * * FIXME: use a single list, i.e. something like * * address -> [instance, instance] * * because that's a big API change it's going to have to wait. for now, * use a second list. * * UPDATE: adding (otherwise unused) tokens, which could be named ranges. * in the future we may pass in a list of names at parse time, and resolve * them; for now we are just listing names. */ protected full_reference_list: Array; /** * cache for storing/restoring parser state, if we toggle it */ protected parser_state_cache: string[]; /** * step towards protecting these values and setting them in one * operation. * * UPDATE: switch order. argument separator is optional and implied. */ SetLocaleSettings(decimal_mark: DecimalMarkType, argument_separator?: ArgumentSeparatorType): void; /** * save local configuration to a buffer, so it can be restored. we're doing * this because in a lot of places we're caching parser flagss, changing * them, and then restoring them. that's become repetitive, fragile to * changes or new flags, and annoying. * * config is managed in a list with push/pop semantics. we store it as * JSON so there's no possibility we'll accidentally mutate. * * FIXME: while we're at it why not migrate the separators -> flags, so * there's a single location for this kind of state? (...TODO) * */ Save(): void; /** * restore persisted config * @see Save */ Restore(): void; /** * recursive tree walk that allows substitution. this should be * a drop-in replacement for the original Walk function but I'm * keeping it separate temporarily just in case it breaks something. * * @param func - in this version function can return `true` (continue * walking subtree), `false` (don't walk subtree), or an ExpressionUnit. * in the last case, we'll replace the original unit with the substitution. * obviously in that case we don't recurse. */ Walk2(unit: ExpressionUnit, func: (unit: ExpressionUnit) => boolean | ExpressionUnit | undefined): ExpressionUnit; /** * recursive tree walk. * * @param func function called on each node. for nodes that have children * (operations, calls, groups) return false to skip the subtree, or true to * traverse. */ Walk(unit: ExpressionUnit, func: (unit: ExpressionUnit) => boolean): void; /** utility: transpose array */ Transpose(arr: Array>): Array>; /** * renders the passed expression as a string. * @param unit base expression * @param offset offset for addresses, used to offset relative addresses * (and ranges). this is for copy-and-paste or move operations. * @param missing string to represent missing values (can be '', for functions) * * FIXME: we're accumulating too many arguments. need to switch to an * options object. do that after the structured reference stuff merges. * */ Render(unit: ExpressionUnit, options?: Partial): string; /** * parses expression and returns the root of the parse tree, plus a * list of dependencies (addresses and ranges) found in the expression. * * NOTE that in the new address parsing structure, we will overlap ranges * and addresses (range corners). this is OK because ranges are mapped * to individual address dependencies. it's just sloppy (FIXME: refcount?) */ Parse(expression: string): ParseResult; /** generates column label ("A") from column index (0-based) */ protected ColumnLabel(column: number): string; /** * generates absolute or relative R1C1 address * * FIXME: not supporting relative (offset) addresses atm? I'd like to * change this but I don't want to break anything... */ protected R1C1Label(address: UnitAddress, options: Partial): string; /** * generates address label ("C3") from address (0-based). * * @param offset - offset by some number of rows or columns * @param r1c1 - if set, return data in R1C1 format. */ protected AddressLabel(address: UnitAddress, offset: { rows: number; columns: number; }): string; /** * base parse routine; may recurse inside parens (either as grouped * operations or in function arguments). * * @param exit exit on specific characters */ protected ParseGeneric(exit?: number[], explicit_group?: boolean): ExpressionUnit | null; /** * helper function, @see BinaryToRange * @param unit * @returns */ protected UnitToAddress(unit: UnitLiteral | UnitIdentifier): UnitAddress | undefined; /** * rewrite of binary to range. this version operates on the initial stream, * which should be OK because range has the highest precedence so we would * never reorder a range. * * ACTUALLY this will break in the case of * * -15:16 * * (I think that's the only case). we can fix that though. this should * not impact the case of `2-15:16`, because in that case the - will look * like an operator and not part of the number. the same goes for a leading * `+` which will get dropped implicitly but has no effect (we might want * to preserve it for consistency though). * * NOTE: that error existed in the old version, too, and this way is perhaps * better for fixing it. we should merge this into main. * * * old version comments: * --- * * converts binary operations with a colon operator to ranges. this also * validates that there are no colon operations with non-address operands * (which is why it's called after precendence reordering; colon has the * highest preference). recursive only over binary ops AND unary ops. * * NOTE: there are other legal arguments to a colon operator. specifically: * * (1) two numbers, in either order * * 15:16 * 16:16 * 16:15 * * (2) with one or both optionally having a $ * * 15:$16 * $16:$16 * * (3) two column identifiers, in either order * * A:F * B:A * * (4) and the same with $ * * $A:F * $A:$F * * because none of these are legal in any other context, we leave the * default treatment of them UNLESS they are arguments to the colon * operator, in which case we will grab them. that does mean we parse * them twice, but (...) * * FIXME: will need some updated to rendering these, we don't have any * handler for rendering infinity */ protected BinaryToRange2(stream: ExpressionUnit[]): ExpressionUnit[]; /** * we've now come full circle. we started with handling ranges as * binary operators; then we added complex composition as a first-pass * function; then we moved ranges to a first-pass function; and now we're * moving complex composition to a lower-level restructuring of binary * operations. * * that allows better precedence handling for (potentially) ambiguous * constructions like =B3 * 2 + 3i. we do have parens, so. * * @param unit * @returns */ protected BinaryToComplex(unit: ExpressionUnit): ExpressionUnit; /** * reorders operations for precendence * * this method was written with the assumption that groups were * always an error. that's no longer true, with implicit calls. * we should still error if it's not an _explicit_ group, i.e. there's * just a bunch of naked tokens. * */ protected ArrangeUnits(stream: ExpressionUnit[]): ExpressionUnit; /** * parses literals and tokens from the stream, ignoring whitespace, * and stopping on unexpected tokens (generally operators or parens). * * @param naked treat -/+ as signs (part of numbers) rather than operators. */ protected ParseNext(naked?: boolean): ExpressionUnit | number; protected ConsumeArray(): ExpressionUnit; protected ConsumeOperator(): ExpressionUnit | null; /** consume function arguments, which can be of any type */ protected ConsumeArguments(): ExpressionUnit[]; /** * consume token. also checks for function call, because parens * have a different meaning (grouping/precedence) when they appear * not immediately after a token. * * regarding periods: as long as there's no intervening whitespace * or operator, period should be a valid token character. tokens * cannot start with a period. * * NOTE: that's true irrespective of decimal mark type. * * you can have tokens (addresses) with single quotes; these are used * to escape sheet names with spaces (which is a bad idea, but hey). this * should only be legal if the token starts with a single quote, and only * for one (closing) quote. * * R1C1 relative notation uses square brackets, like =R2C[-1] or =R[-1]C[-2]. * that's pretty easy to see. there's also regular R1C1, like =R1C1. * * "structured references" use square brackets. they can start with * square brackets -- in that case the table source is implicit (has to * be in the table). otherwise they look like =TableName[@ColumnName]. that * @ is optional and (I think) means don't spill. * */ protected ConsumeToken(initial_char: number): ExpressionUnit; /** * like ConsumeAddress, look for a structured reference. */ protected ConsumeStructuredReference(token: string, position: number): UnitStructuredReference | undefined; /** * consumes address. this is outside of the normal parse flow; * we already have a token, here we're checking if it's an address. * * this used to check for ranges as well, but we now treat ranges as * an operation on two addresses; that supports whitespace between the * tokens. * * FIXME: that means we can now inline the column/row routines, since * they are not called more than once */ protected ConsumeAddress(token: string, position: number): UnitAddress | null; /** * consumes a row, possibly absolute ($). returns the numeric row * (0-based) and metadata. * * note that something like "X0" is a legal token, because 0 is not * a valid row. but at the same time it can't have a $ in it. although * maybe "X$0" is a token but not a valid name? dunno */ protected ConsumeAddressRow(position: number): { absolute: boolean; row: number; position: number; spill?: boolean; } | false; /** * consumes a column, possibly absolute ($). returns the numeric * column (0-based) and metadata */ protected ConsumeAddressColumn(position: number): { absolute: boolean; column: number; position: number; } | false; /** * consumes number. supported formats (WIP): * * -3 * +3 * 100.9 * 10.0% * 1e-2.2 * * ~1,333,123.22~ * * UPDATE: commas (separators) are not acceptable in numbers passed * in formulae, can't distinguish between them and function argument * separators. * * regarding the above, a couple of rules: * * 1. +/- is only legal in position 0 or immediately after e/E * 2. only one decimal point is allowed. * 3. any number of separators, in any position, are legal, but * only before the decimal point. * 4. only one % is allowed, and only in the last position * * NOTE: this is probably going to break on unfinished strings that * end in - or +... if they're not treated as operators... * * FIXME: find test cases for that so we can fix it * * UPDATE: exporting original text string for preservation/insertion. * this function now returns a tuple of [value, text]. * * UPDATE: we now (at least in a branch) consume complex numbers. the last * element of the return array is a boolean which is set if the value is an * imaginary number. when parsing, we will only see the imaginary part; * we'll use a separate step to put complex numbers together. * * */ protected ConsumeNumber(): ExpressionUnit; /** * in spreadsheet language ONLY double-quoted strings are legal. there * are no escape characters, and a backslash is a legal character. to * embed a quotation mark, use "" (double-double quote); that's an escaped * double-quote. */ protected ConsumeString(): string; /** run through any intervening whitespace */ protected ConsumeWhiteSpace(): void; }