@trebco/treb 29.6.2 → 29.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/treb-spreadsheet-light.mjs +10 -10
- package/dist/treb-spreadsheet.mjs +15 -15
- package/dist/treb.d.ts +93 -87
- package/package.json +1 -1
- package/treb-base-types/src/cells.ts +34 -0
- package/treb-base-types/src/evaluate-options.ts +22 -1
- package/treb-base-types/src/union.ts +0 -11
- package/treb-base-types/src/value-type.ts +3 -3
- package/treb-calculator/src/calculator.ts +34 -29
- package/treb-calculator/src/descriptors.ts +2 -0
- package/treb-calculator/src/expression-calculator.ts +31 -173
- package/treb-calculator/src/function-error.ts +16 -21
- package/treb-charts/src/util.ts +3 -36
- package/treb-data-model/src/sheet.ts +7 -3
- package/treb-embed/src/embedded-spreadsheet.ts +128 -267
- package/treb-export/src/xml-utils.ts +20 -8
- package/treb-grid/src/index.ts +1 -0
- package/treb-grid/src/types/clipboard_data2.ts +85 -0
- package/treb-grid/src/types/grid.ts +226 -6
- package/treb-parser/src/parser-types.ts +3 -0
- package/treb-parser/src/parser.ts +86 -2
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of TREB.
|
|
3
|
+
*
|
|
4
|
+
* TREB is free software: you can redistribute it and/or modify it under the
|
|
5
|
+
* terms of the GNU General Public License as published by the Free Software
|
|
6
|
+
* Foundation, either version 3 of the License, or (at your option) any
|
|
7
|
+
* later version.
|
|
8
|
+
*
|
|
9
|
+
* TREB is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
10
|
+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
11
|
+
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
12
|
+
* details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License along
|
|
15
|
+
* with TREB. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*
|
|
17
|
+
* Copyright 2022-2024 trebco, llc.
|
|
18
|
+
* info@treb.app
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { CellValue, IArea, CellStyle } from 'treb-base-types';
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* this is a structure for copy/paste data. clipboard data may include
|
|
27
|
+
* relative formauls and resolved styles, so it's suitable for pasting into
|
|
28
|
+
* other areas of the spreadsheet.
|
|
29
|
+
*
|
|
30
|
+
* @privateRemarks
|
|
31
|
+
* work in progress. atm we're not using the system clipboard, although it
|
|
32
|
+
* might be useful to merge this with grid copy/paste routines in the future.
|
|
33
|
+
*
|
|
34
|
+
* if it hits the clipboard this should use mime type `application/x-treb-data`
|
|
35
|
+
*
|
|
36
|
+
*/
|
|
37
|
+
export interface ClipboardDataElement {
|
|
38
|
+
|
|
39
|
+
/** calculated cell value */
|
|
40
|
+
calculated: CellValue,
|
|
41
|
+
|
|
42
|
+
/** the actual cell value or formula */
|
|
43
|
+
value: CellValue,
|
|
44
|
+
|
|
45
|
+
/** cell style. this may include row/column styles from the copy source */
|
|
46
|
+
style?: CellStyle,
|
|
47
|
+
|
|
48
|
+
/** area. if this cell is part of an array, this is the array range */
|
|
49
|
+
area?: IArea,
|
|
50
|
+
|
|
51
|
+
/* TODO: merge, like area */
|
|
52
|
+
|
|
53
|
+
/* TODO: table */
|
|
54
|
+
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** clipboard data is a 2d array */
|
|
58
|
+
export type ClipboardData = ClipboardDataElement[][];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* optional paste options. we can paste formulas or values, and we
|
|
62
|
+
* can use the source style, target style, or just use the source
|
|
63
|
+
* number formats.
|
|
64
|
+
*/
|
|
65
|
+
export interface PasteOptions {
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* when clipboard data includes formulas, optionally paste calculated
|
|
69
|
+
* values instead of the original formulas. defaults to false.
|
|
70
|
+
*/
|
|
71
|
+
values?: boolean;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* when pasting data from the clipboard, we can copy formatting/style
|
|
75
|
+
* from the original data, or we can retain the target range formatting
|
|
76
|
+
* and just paste data. a third option allows pasting source number
|
|
77
|
+
* formats but dropping other style information.
|
|
78
|
+
*
|
|
79
|
+
* defaults to "source", meaning paste source styles.
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
formatting?: 'source'|'target'|'number-formats'
|
|
83
|
+
|
|
84
|
+
}
|
|
85
|
+
|
|
@@ -116,6 +116,7 @@ import type { ClipboardCellData } from './clipboard_data';
|
|
|
116
116
|
|
|
117
117
|
import type { ExternalEditorConfig } from './external_editor_config';
|
|
118
118
|
import { ExternalEditor } from '../editors/external_editor';
|
|
119
|
+
import type { ClipboardData, PasteOptions } from './clipboard_data2';
|
|
119
120
|
|
|
120
121
|
interface DoubleClickData {
|
|
121
122
|
timeout?: number;
|
|
@@ -416,6 +417,227 @@ export class Grid extends GridBase {
|
|
|
416
417
|
|
|
417
418
|
}
|
|
418
419
|
|
|
420
|
+
|
|
421
|
+
// --- Copy/paste API methods ------------------------------------------------
|
|
422
|
+
//
|
|
423
|
+
// moving here with a view towards (eventually) merging with the UI/browser
|
|
424
|
+
// copy/paste routines (in grid)
|
|
425
|
+
//
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* internal composite for cut/copy. mostly identical except we
|
|
430
|
+
* read data as A1 for cut, so it will retain references. also
|
|
431
|
+
* cut clears the data.
|
|
432
|
+
*
|
|
433
|
+
* FIXME: merge with grid cut/copy/paste routines. we already
|
|
434
|
+
* handle recycling and relative addressing, the only thing missing
|
|
435
|
+
* is alternate formats.
|
|
436
|
+
*/
|
|
437
|
+
public CopyArea(resolved: Area, semantics: 'cut'|'copy' = 'copy'): ClipboardData {
|
|
438
|
+
|
|
439
|
+
// resolve range so we can use it later -> Area
|
|
440
|
+
const sheet = (resolved.start.sheet_id ? this.model.sheets.Find(resolved.start.sheet_id) : this.active_sheet) || this.active_sheet;
|
|
441
|
+
|
|
442
|
+
// get style data, !apply theme but do apply r/c styles
|
|
443
|
+
const style_data = sheet.GetCellStyle(resolved, false);
|
|
444
|
+
|
|
445
|
+
// flag we want R1C1 (copy)
|
|
446
|
+
const r1c1 = (semantics !== 'cut');
|
|
447
|
+
|
|
448
|
+
// NOTE: we're losing arrays here. need to fix. also think
|
|
449
|
+
// about merges? we'll reimplement what grid does (only in part)
|
|
450
|
+
|
|
451
|
+
const data: ClipboardData = [];
|
|
452
|
+
|
|
453
|
+
for (const { cell, row, column } of sheet.cells.IterateRC(resolved)) {
|
|
454
|
+
|
|
455
|
+
// raw value
|
|
456
|
+
let value = cell.value;
|
|
457
|
+
|
|
458
|
+
// seems like we're using a loop function unecessarily
|
|
459
|
+
if (r1c1 && value && cell.type === ValueType.formula) {
|
|
460
|
+
value = this.FormatR1C1(value, { row, column })[0][0];
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const r = row - resolved.start.row;
|
|
464
|
+
const c = column - resolved.start.column;
|
|
465
|
+
|
|
466
|
+
if (!data[r]) {
|
|
467
|
+
data[r] = [];
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
let array_head: IArea|undefined;
|
|
471
|
+
if (cell.area) {
|
|
472
|
+
|
|
473
|
+
// scrubbing to just area (and unlinking)
|
|
474
|
+
array_head = {
|
|
475
|
+
start: {
|
|
476
|
+
row: cell.area.start.row - resolved.start.row,
|
|
477
|
+
column: cell.area.start.column - resolved.start.column,
|
|
478
|
+
},
|
|
479
|
+
end: {
|
|
480
|
+
row: cell.area.end.row - resolved.start.row,
|
|
481
|
+
column: cell.area.end.column - resolved.start.column,
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
data[r][c] = {
|
|
488
|
+
value,
|
|
489
|
+
calculated: cell.calculated,
|
|
490
|
+
style: style_data[r][c],
|
|
491
|
+
area: array_head,
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// EmbeddedSpreadsheet.clipboard = structuredClone(data);
|
|
497
|
+
|
|
498
|
+
if (semantics === 'cut') {
|
|
499
|
+
this.SetRange(resolved, undefined, { recycle: true }); // clear
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return data;
|
|
503
|
+
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* paste clipboard data into a target range. this method does not use
|
|
508
|
+
* the system clipboard; pass in clipboard data returned from the Cut or
|
|
509
|
+
* Copy method.
|
|
510
|
+
*
|
|
511
|
+
* @param target - the target to paste data into. this can be larger
|
|
512
|
+
* than the clipboard data, in which case values will be recycled in
|
|
513
|
+
* blocks. if the target is smaller than the source data, we will expand
|
|
514
|
+
* it to fit the data.
|
|
515
|
+
*
|
|
516
|
+
* @param data - clipboard data to paste.
|
|
517
|
+
*
|
|
518
|
+
* @privateRemarks LLM API
|
|
519
|
+
*
|
|
520
|
+
* @privateRemarks this was async when we were thinking of using the
|
|
521
|
+
* system clipboard, but that's pretty broken so we're not going to
|
|
522
|
+
* bother atm.
|
|
523
|
+
*/
|
|
524
|
+
public PasteArea(resolved: Area, data: ClipboardData, options: PasteOptions = {}): void {
|
|
525
|
+
|
|
526
|
+
if (!data) {
|
|
527
|
+
throw new Error('no clipboad data');
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// paste has some special semantics. if the target smaller than the
|
|
531
|
+
// source data, we write the full data irrespective of size (similar
|
|
532
|
+
// to "spill"). otherwise, we recycle in blocks.
|
|
533
|
+
|
|
534
|
+
// the setrange method will recycle, but we also need to recycle styles.
|
|
535
|
+
|
|
536
|
+
// start with data length
|
|
537
|
+
|
|
538
|
+
const rows = data.length;
|
|
539
|
+
const columns = data[0]?.length || 0;
|
|
540
|
+
|
|
541
|
+
// target -> block size
|
|
542
|
+
|
|
543
|
+
resolved.Resize(
|
|
544
|
+
Math.max(1, Math.floor(resolved.rows / rows)) * rows,
|
|
545
|
+
Math.max(1, Math.floor(resolved.columns / columns)) * columns );
|
|
546
|
+
|
|
547
|
+
const sheet = (resolved.start.sheet_id ? this.model.sheets.Find(resolved.start.sheet_id) : this.active_sheet) || this.active_sheet;
|
|
548
|
+
|
|
549
|
+
const values: CellValue[][] = [];
|
|
550
|
+
|
|
551
|
+
// optionally collect calculated values, instead of raw values
|
|
552
|
+
|
|
553
|
+
if (options.values) {
|
|
554
|
+
for (const [index, row] of data.entries()) {
|
|
555
|
+
values[index] = [];
|
|
556
|
+
for (const cell of row) {
|
|
557
|
+
values[index].push(typeof cell.calculated === 'undefined' ? cell.value : cell.calculated);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// this is to resolve the reference in the callback,
|
|
563
|
+
// but we should copy -- there's a possibility that
|
|
564
|
+
// this points to the static member, which could get
|
|
565
|
+
// overwritten. FIXME
|
|
566
|
+
|
|
567
|
+
const local = data;
|
|
568
|
+
|
|
569
|
+
// batch to limit events, sync up undo
|
|
570
|
+
|
|
571
|
+
const events = this.Batch(() => {
|
|
572
|
+
|
|
573
|
+
// this needs to change to support arrays (and potentially merges...)
|
|
574
|
+
|
|
575
|
+
// actually we could leave as is for just calculated values
|
|
576
|
+
|
|
577
|
+
if (options.values) {
|
|
578
|
+
this.SetRange(resolved, values, {
|
|
579
|
+
r1c1: true, recycle: true,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
|
|
584
|
+
// so this is for formulas only now
|
|
585
|
+
|
|
586
|
+
// start by clearing... (but leave styles as-is for now)
|
|
587
|
+
|
|
588
|
+
// probably a better way to do this
|
|
589
|
+
this.SetRange(resolved, undefined, { recycle: true });
|
|
590
|
+
|
|
591
|
+
for (const address of resolved) {
|
|
592
|
+
const r = (address.row - resolved.start.row) % rows;
|
|
593
|
+
const c = (address.column - resolved.start.column) % columns;
|
|
594
|
+
|
|
595
|
+
const cell_data = local[r][c];
|
|
596
|
+
|
|
597
|
+
if (cell_data.area) {
|
|
598
|
+
// only the head
|
|
599
|
+
if (cell_data.area.start.row === r && cell_data.area.start.column === c) {
|
|
600
|
+
const array_target = new Area(cell_data.area.start, cell_data.area.end);
|
|
601
|
+
array_target.Shift(resolved.start.row, resolved.start.column);
|
|
602
|
+
this.SetRange(array_target, cell_data.value, { r1c1: true, array: true });
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
else if (cell_data.value) {
|
|
606
|
+
this.SetRange(new Area(address), cell_data.value, { r1c1: true });
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (options.formatting === 'number-formats') {
|
|
614
|
+
|
|
615
|
+
// number format only, and apply delta
|
|
616
|
+
|
|
617
|
+
for (const address of resolved) {
|
|
618
|
+
const r = (address.row - resolved.start.row) % rows;
|
|
619
|
+
const c = (address.column - resolved.start.column) % columns;
|
|
620
|
+
const number_format = (local[r][c].style || {}).number_format;
|
|
621
|
+
sheet.UpdateCellStyle(address, { number_format }, true);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
else if (options.formatting !== 'target') {
|
|
625
|
+
|
|
626
|
+
// use source formatting (default)
|
|
627
|
+
for (const address of resolved) {
|
|
628
|
+
const r = (address.row - resolved.start.row) % rows;
|
|
629
|
+
const c = (address.column - resolved.start.column) % columns;
|
|
630
|
+
sheet.UpdateCellStyle(address, local[r][c].style || {}, false);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
}, true);
|
|
636
|
+
|
|
637
|
+
this.grid_events.Publish(events);
|
|
638
|
+
|
|
639
|
+
}
|
|
640
|
+
|
|
419
641
|
// --- public methods --------------------------------------------------------
|
|
420
642
|
|
|
421
643
|
/**
|
|
@@ -1883,23 +2105,19 @@ export class Grid extends GridBase {
|
|
|
1883
2105
|
let convert = false;
|
|
1884
2106
|
|
|
1885
2107
|
if (options.argument_separator === ',' && this.parser.argument_separator !== ArgumentSeparatorType.Comma) {
|
|
1886
|
-
// this.parser.argument_separator = ArgumentSeparatorType.Comma;
|
|
1887
|
-
// this.parser.decimal_mark = DecimalMarkType.Period;
|
|
1888
2108
|
this.parser.SetLocaleSettings(DecimalMarkType.Period);
|
|
1889
|
-
|
|
1890
2109
|
convert = true;
|
|
1891
2110
|
}
|
|
1892
2111
|
|
|
1893
2112
|
if (options.argument_separator === ';' && this.parser.argument_separator !== ArgumentSeparatorType.Semicolon) {
|
|
1894
|
-
// this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
|
|
1895
|
-
// this.parser.decimal_mark = DecimalMarkType.Comma;
|
|
1896
2113
|
this.parser.SetLocaleSettings(DecimalMarkType.Comma);
|
|
1897
|
-
|
|
1898
2114
|
convert = true;
|
|
1899
2115
|
}
|
|
1900
2116
|
|
|
1901
2117
|
if (convert) {
|
|
1902
2118
|
|
|
2119
|
+
this.parser.flags.r1c1 = r1c1;
|
|
2120
|
+
|
|
1903
2121
|
const Convert = (value: CellValue): CellValue => {
|
|
1904
2122
|
if (typeof value === 'string' && value[0] === '=') {
|
|
1905
2123
|
const result = this.parser.Parse(value);
|
|
@@ -1908,8 +2126,10 @@ export class Grid extends GridBase {
|
|
|
1908
2126
|
missing: '',
|
|
1909
2127
|
convert_decimal: current.decimal_mark,
|
|
1910
2128
|
convert_argument_separator: current.argument_separator,
|
|
2129
|
+
pass_through_addresses: true,
|
|
1911
2130
|
});
|
|
1912
2131
|
}
|
|
2132
|
+
// console.info("CVT", this.parser.flags, result.expression, value);
|
|
1913
2133
|
}
|
|
1914
2134
|
return value;
|
|
1915
2135
|
};
|
|
@@ -411,6 +411,9 @@ export interface RenderOptions {
|
|
|
411
411
|
/** base for offsetting relative R1C1 addresses */
|
|
412
412
|
r1c1_base?: UnitAddress;
|
|
413
413
|
|
|
414
|
+
/** if we're just translating, don't have to render addresses */
|
|
415
|
+
pass_through_addresses?: boolean;
|
|
416
|
+
|
|
414
417
|
missing: string;
|
|
415
418
|
convert_decimal: DecimalMarkType;
|
|
416
419
|
convert_argument_separator: ArgumentSeparatorType;
|
|
@@ -34,6 +34,7 @@ import type {
|
|
|
34
34
|
ParserFlags,
|
|
35
35
|
UnitStructuredReference,
|
|
36
36
|
RenderOptions,
|
|
37
|
+
BaseExpressionUnit,
|
|
37
38
|
} from './parser-types';
|
|
38
39
|
import {
|
|
39
40
|
ArgumentSeparatorType,
|
|
@@ -334,6 +335,77 @@ export class Parser {
|
|
|
334
335
|
}
|
|
335
336
|
}
|
|
336
337
|
|
|
338
|
+
/**
|
|
339
|
+
* recursive tree walk that allows substitution. this should be
|
|
340
|
+
* a drop-in replacement for the original Walk function but I'm
|
|
341
|
+
* keeping it separate temporarily just in case it breaks something.
|
|
342
|
+
*
|
|
343
|
+
* @param func - in this version function can return `true` (continue
|
|
344
|
+
* walking subtree), `false` (don't walk subtree), or an ExpressionUnit.
|
|
345
|
+
* in the last case, we'll replace the original unit with the substitution.
|
|
346
|
+
* obviously in that case we don't recurse.
|
|
347
|
+
*/
|
|
348
|
+
public Walk2(unit: ExpressionUnit, func: (unit: ExpressionUnit) => boolean|ExpressionUnit|undefined): ExpressionUnit {
|
|
349
|
+
|
|
350
|
+
const result = func(unit);
|
|
351
|
+
if (typeof result === 'object') {
|
|
352
|
+
return result;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
switch (unit.type) {
|
|
356
|
+
case 'address':
|
|
357
|
+
case 'missing':
|
|
358
|
+
case 'literal':
|
|
359
|
+
case 'complex':
|
|
360
|
+
case 'identifier':
|
|
361
|
+
case 'operator':
|
|
362
|
+
case 'structured-reference':
|
|
363
|
+
break;
|
|
364
|
+
|
|
365
|
+
case 'dimensioned':
|
|
366
|
+
if (result) {
|
|
367
|
+
unit.expression = this.Walk2(unit.expression, func) as BaseExpressionUnit; // could be an issue
|
|
368
|
+
unit.unit = this.Walk2(unit.unit, func) as UnitIdentifier; // could be an issue
|
|
369
|
+
}
|
|
370
|
+
break;
|
|
371
|
+
|
|
372
|
+
case 'range':
|
|
373
|
+
if (func(unit)) {
|
|
374
|
+
unit.start = this.Walk2(unit.start, func) as UnitAddress; // could be an issue
|
|
375
|
+
unit.end = this.Walk2(unit.end, func) as UnitAddress; // could be an issue
|
|
376
|
+
}
|
|
377
|
+
break;
|
|
378
|
+
|
|
379
|
+
case 'binary':
|
|
380
|
+
if (func(unit)) {
|
|
381
|
+
unit.left = this.Walk2(unit.left, func);
|
|
382
|
+
unit.right = this.Walk2(unit.right, func);
|
|
383
|
+
}
|
|
384
|
+
break;
|
|
385
|
+
|
|
386
|
+
case 'unary':
|
|
387
|
+
if (func(unit)) {
|
|
388
|
+
unit.operand = this.Walk2(unit.operand, func);
|
|
389
|
+
}
|
|
390
|
+
break;
|
|
391
|
+
|
|
392
|
+
case 'group':
|
|
393
|
+
if (func(unit)) {
|
|
394
|
+
unit.elements = unit.elements.map(source => this.Walk2(source, func));
|
|
395
|
+
}
|
|
396
|
+
break;
|
|
397
|
+
|
|
398
|
+
case 'call':
|
|
399
|
+
if (func(unit)) {
|
|
400
|
+
unit.args = unit.args.map(source => this.Walk2(source, func));
|
|
401
|
+
}
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return unit;
|
|
406
|
+
|
|
407
|
+
}
|
|
408
|
+
|
|
337
409
|
/**
|
|
338
410
|
* recursive tree walk.
|
|
339
411
|
*
|
|
@@ -382,13 +454,19 @@ export class Parser {
|
|
|
382
454
|
|
|
383
455
|
case 'group':
|
|
384
456
|
if (func(unit)) {
|
|
385
|
-
unit.elements.forEach((element) => this.Walk(element, func));
|
|
457
|
+
// unit.elements.forEach((element) => this.Walk(element, func));
|
|
458
|
+
for (const element of unit.elements) {
|
|
459
|
+
this.Walk(element, func);
|
|
460
|
+
}
|
|
386
461
|
}
|
|
387
462
|
return;
|
|
388
463
|
|
|
389
464
|
case 'call':
|
|
390
465
|
if (func(unit)) {
|
|
391
|
-
|
|
466
|
+
for (const arg of unit.args) {
|
|
467
|
+
this.Walk(arg, func);
|
|
468
|
+
}
|
|
469
|
+
// unit.args.forEach((arg) => this.Walk(arg, func));
|
|
392
470
|
}
|
|
393
471
|
}
|
|
394
472
|
}
|
|
@@ -489,9 +567,15 @@ export class Parser {
|
|
|
489
567
|
|
|
490
568
|
switch (unit.type) {
|
|
491
569
|
case 'address':
|
|
570
|
+
if (options.pass_through_addresses) {
|
|
571
|
+
return unit.label;
|
|
572
|
+
}
|
|
492
573
|
return options.r1c1 ? this.R1C1Label(unit, options.r1c1_base) : this.AddressLabel(unit, offset);
|
|
493
574
|
|
|
494
575
|
case 'range':
|
|
576
|
+
if (options.pass_through_addresses) {
|
|
577
|
+
return unit.label;
|
|
578
|
+
}
|
|
495
579
|
return options.r1c1 ?
|
|
496
580
|
this.R1C1Label(unit.start, options.r1c1_base) + ':' + this.R1C1Label(unit.end, options.r1c1_base) :
|
|
497
581
|
this.AddressLabel(unit.start, offset) + ':' + this.AddressLabel(unit.end, offset);
|