Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

### Fixed

- Fixed lookup functions rejecting array tables produced by formulas such as `IF`.
- Fixed a memory leak in `LazilyTransformingAstService` where the transformations array grew unboundedly, causing increasing memory usage over time. [#1629](https://github.com/handsontable/hyperformula/issues/1629)
- Fixed a memory leak in `UndoRedo` where `oldData` entries for evicted undo stack entries were never cleaned up, causing increasing memory usage over time. [#1629](https://github.com/handsontable/hyperformula/issues/1629)
- Fixed the IRR function returning `#NUM!` error when the initial investment significantly exceeds the sum of returns. [#1628](https://github.com/handsontable/hyperformula/issues/1628)
Expand Down
9 changes: 9 additions & 0 deletions src/interpreter/plugin/BooleanPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import {CellError, ErrorType} from '../../Cell'
import {ErrorMessage} from '../../error-message'
import {ProcedureAst} from '../../parser'
import {SimpleRangeValue} from '../../SimpleRangeValue'
import {InterpreterState} from '../InterpreterState'
import {InternalNoErrorScalarValue, InternalScalarValue, InterpreterValue} from '../InterpreterValue'
import {FunctionArgumentType, FunctionPlugin, FunctionPluginTypecheck, ImplementedFunctions} from './FunctionPlugin'
Expand Down Expand Up @@ -135,6 +136,14 @@ export class BooleanPlugin extends FunctionPlugin implements FunctionPluginTypec
* @param state
*/
public conditionalIf(ast: ProcedureAst, state: InterpreterState): InterpreterValue {
const conditionValue = this.evaluateAst(ast.args[0], state)
if (conditionValue instanceof SimpleRangeValue && conditionValue.isAdHoc() && conditionValue.numberOfElements() > 1) {
const vectorizedState = new InterpreterState(state.formulaAddress, true, state.formulaVertex)
return this.runFunction(ast.args, vectorizedState, this.metadata('IF'), (condition, arg2, arg3) => {
return condition ? arg2 : arg3
})
}

return this.runFunction(ast.args, state, this.metadata('IF'), (condition, arg2, arg3) => {
return condition ? arg2 : arg3
})
Expand Down
15 changes: 2 additions & 13 deletions src/interpreter/plugin/LookupPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,11 @@ export class LookupPlugin extends FunctionPlugin implements FunctionPluginTypech
*/
public vlookup(ast: ProcedureAst, state: InterpreterState): InterpreterValue {
return this.runFunction(ast.args, state, this.metadata('VLOOKUP'), (key: RawNoErrorScalarValue, rangeValue: SimpleRangeValue, index: number, sorted: boolean) => {
const range = rangeValue.range

if (range === undefined) {
return new CellError(ErrorType.VALUE, ErrorMessage.WrongType)
}

if (index < 1) {
return new CellError(ErrorType.VALUE, ErrorMessage.LessThanOne)
}

if (index > range.width()) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lookup accepts scalar table

Low Severity

Removing the range === undefined guard lets VLOOKUP and HLOOKUP run on 1×1 ad-hoc tables from coerceToRange when the second argument is a scalar. Excel returns #VALUE! for a non-table lookup range; these formulas may return a value instead.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 349df04. Configure here.

if (index > rangeValue.width()) {
return new CellError(ErrorType.REF, ErrorMessage.IndexLarge)
}

Expand All @@ -105,16 +99,11 @@ export class LookupPlugin extends FunctionPlugin implements FunctionPluginTypech
*/
public hlookup(ast: ProcedureAst, state: InterpreterState): InterpreterValue {
return this.runFunction(ast.args, state, this.metadata('HLOOKUP'), (key: RawNoErrorScalarValue, rangeValue: SimpleRangeValue, index: number, sorted: boolean) => {
const range = rangeValue.range
if (range === undefined) {
return new CellError(ErrorType.VALUE, ErrorMessage.WrongType)
}

if (index < 1) {
return new CellError(ErrorType.VALUE, ErrorMessage.LessThanOne)
}

if (index > range.height()) {
if (index > rangeValue.height()) {
return new CellError(ErrorType.REF, ErrorMessage.IndexLarge)
}

Expand Down
25 changes: 24 additions & 1 deletion test/smoke.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {HyperFormula} from '../src'
import {HyperFormula, Sheet} from '../src'
import {SimpleCellAddress, simpleCellAddress} from '../src/Cell'

const adr = (stringAddress: string, sheet: number = 0): SimpleCellAddress => {
Expand Down Expand Up @@ -103,4 +103,27 @@ describe('HyperFormula', () => {

hf.destroy()
})

it('should allow VLOOKUP over IF array-constructed table', () => {
const data: Sheet = Array.from({length: 112}, () => Array<null>(64).fill(null))

data[108][55] = 'K1'
data[109][55] = 'K2'
data[110][55] = 'K3'
data[111][55] = 'K4'

data[108][47] = 100
data[109][47] = 200
data[110][47] = 300
data[111][47] = 400

data[108][57] = 'K2'
data[108][63] = '=VLOOKUP(BF109,IF({1,0},BD109:BD112,AV109:AV112),2,0)'

const hf = HyperFormula.buildFromArray(data, {licenseKey: 'gpl-v3'})

expect(hf.getCellValue({sheet: 0, row: 108, col: 63})).toBe(200)

hf.destroy()
})
})