fable 3.1.62 → 3.1.64
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/docs/services/expression-parser.md +59 -0
- package/example_applications/mathematical_playground/AppData.json +36 -1
- package/example_applications/mathematical_playground/Math-Solver-Harness.js +107 -0
- package/package.json +12 -11
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ExpressionTokenizer-DirectiveMutation.js +156 -2
- package/source/services/Fable-Service-ExpressionParser.js +143 -0
- package/test/ExpressionParser_tests.js +769 -0
- package/.babelrc +0 -3
- package/.browserslistrc +0 -1
- package/dist/fable.js +0 -5210
- package/dist/fable.js.map +0 -1
- package/dist/fable.min.js +0 -12
- package/dist/fable.min.js.map +0 -1
- package/dist/indoctrinate_content_staging/Indoctrinate-Catalog-AppData.json +0 -10514
- /package/docs/{cover.md → _cover.md} +0 -0
|
@@ -1384,5 +1384,774 @@ suite
|
|
|
1384
1384
|
);
|
|
1385
1385
|
}
|
|
1386
1386
|
);
|
|
1387
|
+
suite
|
|
1388
|
+
(
|
|
1389
|
+
'Multi-Row Map Expressions',
|
|
1390
|
+
function ()
|
|
1391
|
+
{
|
|
1392
|
+
test
|
|
1393
|
+
(
|
|
1394
|
+
'Test basic MULTIROWMAP with current row',
|
|
1395
|
+
(fDone) =>
|
|
1396
|
+
{
|
|
1397
|
+
let testFable = new libFable();
|
|
1398
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
1399
|
+
let tmpManifest = testFable.newManyfest();
|
|
1400
|
+
|
|
1401
|
+
let tmpDataSourceObject = {
|
|
1402
|
+
Rows: [
|
|
1403
|
+
{ Width: 10, Height: 20 },
|
|
1404
|
+
{ Width: 30, Height: 40 },
|
|
1405
|
+
{ Width: 50, Height: 60 },
|
|
1406
|
+
]
|
|
1407
|
+
};
|
|
1408
|
+
let tmpDataDestinationObject = {};
|
|
1409
|
+
|
|
1410
|
+
// Simple: reference current row values (OFFSET 0 is default)
|
|
1411
|
+
let tmpResult = _Parser.solve(
|
|
1412
|
+
'Areas = MULTIROWMAP ROWS FROM Rows VAR w FROM Width VAR h FROM Height : w * h',
|
|
1413
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
1414
|
+
|
|
1415
|
+
Expect(tmpResult).to.be.an('array');
|
|
1416
|
+
Expect(tmpResult.length).to.equal(3);
|
|
1417
|
+
Expect(tmpResult[0]).to.equal('200');
|
|
1418
|
+
Expect(tmpResult[1]).to.equal('1200');
|
|
1419
|
+
Expect(tmpResult[2]).to.equal('3000');
|
|
1420
|
+
|
|
1421
|
+
return fDone();
|
|
1422
|
+
}
|
|
1423
|
+
);
|
|
1424
|
+
test
|
|
1425
|
+
(
|
|
1426
|
+
'Test MULTIROWMAP with previous row lookback (OFFSET -1)',
|
|
1427
|
+
(fDone) =>
|
|
1428
|
+
{
|
|
1429
|
+
let testFable = new libFable();
|
|
1430
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
1431
|
+
let tmpManifest = testFable.newManyfest();
|
|
1432
|
+
|
|
1433
|
+
let tmpDataSourceObject = {
|
|
1434
|
+
Rows: [
|
|
1435
|
+
{ Value: 100 },
|
|
1436
|
+
{ Value: 200 },
|
|
1437
|
+
{ Value: 300 },
|
|
1438
|
+
{ Value: 400 },
|
|
1439
|
+
]
|
|
1440
|
+
};
|
|
1441
|
+
let tmpDataDestinationObject = {};
|
|
1442
|
+
|
|
1443
|
+
// Reference the current row and the previous row's value
|
|
1444
|
+
let tmpResult = _Parser.solve(
|
|
1445
|
+
'Deltas = MULTIROWMAP ROWS FROM Rows VAR Current FROM Value OFFSET 0 VAR Previous FROM Value OFFSET -1 DEFAULT 0 : Current - Previous',
|
|
1446
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
1447
|
+
|
|
1448
|
+
Expect(tmpResult).to.be.an('array');
|
|
1449
|
+
Expect(tmpResult.length).to.equal(4);
|
|
1450
|
+
// Row 0: 100 - 0 (default) = 100
|
|
1451
|
+
Expect(tmpResult[0]).to.equal('100');
|
|
1452
|
+
// Row 1: 200 - 100 = 100
|
|
1453
|
+
Expect(tmpResult[1]).to.equal('100');
|
|
1454
|
+
// Row 2: 300 - 200 = 100
|
|
1455
|
+
Expect(tmpResult[2]).to.equal('100');
|
|
1456
|
+
// Row 3: 400 - 300 = 100
|
|
1457
|
+
Expect(tmpResult[3]).to.equal('100');
|
|
1458
|
+
|
|
1459
|
+
return fDone();
|
|
1460
|
+
}
|
|
1461
|
+
);
|
|
1462
|
+
test
|
|
1463
|
+
(
|
|
1464
|
+
'Test MULTIROWMAP with two-row lookback (OFFSET -2)',
|
|
1465
|
+
(fDone) =>
|
|
1466
|
+
{
|
|
1467
|
+
let testFable = new libFable();
|
|
1468
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
1469
|
+
let tmpManifest = testFable.newManyfest();
|
|
1470
|
+
|
|
1471
|
+
let tmpDataSourceObject = {
|
|
1472
|
+
Rows: [
|
|
1473
|
+
{ Value: 5 },
|
|
1474
|
+
{ Value: 10 },
|
|
1475
|
+
{ Value: 20 },
|
|
1476
|
+
{ Value: 35 },
|
|
1477
|
+
{ Value: 55 },
|
|
1478
|
+
]
|
|
1479
|
+
};
|
|
1480
|
+
let tmpDataDestinationObject = {};
|
|
1481
|
+
|
|
1482
|
+
// Second derivative: current - 2*previous + two-back
|
|
1483
|
+
let tmpResult = _Parser.solve(
|
|
1484
|
+
'SecondDeriv = MULTIROWMAP ROWS FROM Rows VAR Current FROM Value OFFSET 0 VAR Prev FROM Value OFFSET -1 DEFAULT 0 VAR TwoBack FROM Value OFFSET -2 DEFAULT 0 : Current - 2 * Prev + TwoBack',
|
|
1485
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
1486
|
+
|
|
1487
|
+
Expect(tmpResult).to.be.an('array');
|
|
1488
|
+
Expect(tmpResult.length).to.equal(5);
|
|
1489
|
+
// Row 0: 5 - 2*0 + 0 = 5
|
|
1490
|
+
Expect(tmpResult[0]).to.equal('5');
|
|
1491
|
+
// Row 1: 10 - 2*5 + 0 = 0
|
|
1492
|
+
Expect(tmpResult[1]).to.equal('0');
|
|
1493
|
+
// Row 2: 20 - 2*10 + 5 = 5
|
|
1494
|
+
Expect(tmpResult[2]).to.equal('5');
|
|
1495
|
+
// Row 3: 35 - 2*20 + 10 = 5
|
|
1496
|
+
Expect(tmpResult[3]).to.equal('5');
|
|
1497
|
+
// Row 4: 55 - 2*35 + 20 = 5
|
|
1498
|
+
Expect(tmpResult[4]).to.equal('5');
|
|
1499
|
+
|
|
1500
|
+
return fDone();
|
|
1501
|
+
}
|
|
1502
|
+
);
|
|
1503
|
+
test
|
|
1504
|
+
(
|
|
1505
|
+
'Test MULTIROWMAP with three-row lookback (OFFSET -3)',
|
|
1506
|
+
(fDone) =>
|
|
1507
|
+
{
|
|
1508
|
+
let testFable = new libFable();
|
|
1509
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
1510
|
+
let tmpManifest = testFable.newManyfest();
|
|
1511
|
+
|
|
1512
|
+
let tmpDataSourceObject = {
|
|
1513
|
+
Rows: [
|
|
1514
|
+
{ Value: 1 },
|
|
1515
|
+
{ Value: 2 },
|
|
1516
|
+
{ Value: 4 },
|
|
1517
|
+
{ Value: 8 },
|
|
1518
|
+
{ Value: 16 },
|
|
1519
|
+
]
|
|
1520
|
+
};
|
|
1521
|
+
let tmpDataDestinationObject = {};
|
|
1522
|
+
|
|
1523
|
+
// Sum of current + three rows back
|
|
1524
|
+
let tmpResult = _Parser.solve(
|
|
1525
|
+
'ThreeBackSum = MULTIROWMAP ROWS FROM Rows VAR Current FROM Value VAR ThreeBack FROM Value OFFSET -3 DEFAULT 0 : Current + ThreeBack',
|
|
1526
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
1527
|
+
|
|
1528
|
+
Expect(tmpResult).to.be.an('array');
|
|
1529
|
+
Expect(tmpResult.length).to.equal(5);
|
|
1530
|
+
// Row 0: 1 + 0 (default, no row at -3) = 1
|
|
1531
|
+
Expect(tmpResult[0]).to.equal('1');
|
|
1532
|
+
// Row 1: 2 + 0 (default, no row at -2) = 2
|
|
1533
|
+
Expect(tmpResult[1]).to.equal('2');
|
|
1534
|
+
// Row 2: 4 + 0 (default, no row at -1) = 4
|
|
1535
|
+
Expect(tmpResult[2]).to.equal('4');
|
|
1536
|
+
// Row 3: 8 + 1 (row 0) = 9
|
|
1537
|
+
Expect(tmpResult[3]).to.equal('9');
|
|
1538
|
+
// Row 4: 16 + 2 (row 1) = 18
|
|
1539
|
+
Expect(tmpResult[4]).to.equal('18');
|
|
1540
|
+
|
|
1541
|
+
return fDone();
|
|
1542
|
+
}
|
|
1543
|
+
);
|
|
1544
|
+
test
|
|
1545
|
+
(
|
|
1546
|
+
'Test MULTIROWMAP Fibonacci sequence',
|
|
1547
|
+
(fDone) =>
|
|
1548
|
+
{
|
|
1549
|
+
let testFable = new libFable();
|
|
1550
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
1551
|
+
let tmpManifest = testFable.newManyfest();
|
|
1552
|
+
|
|
1553
|
+
// Fibonacci: each row = sum of two previous rows
|
|
1554
|
+
// Seed: first two rows have the starting values
|
|
1555
|
+
let tmpDataSourceObject = {
|
|
1556
|
+
FibRows: [
|
|
1557
|
+
{ Fib: 0 },
|
|
1558
|
+
{ Fib: 1 },
|
|
1559
|
+
{ Fib: 1 },
|
|
1560
|
+
{ Fib: 2 },
|
|
1561
|
+
{ Fib: 3 },
|
|
1562
|
+
{ Fib: 5 },
|
|
1563
|
+
{ Fib: 8 },
|
|
1564
|
+
{ Fib: 13 },
|
|
1565
|
+
]
|
|
1566
|
+
};
|
|
1567
|
+
let tmpDataDestinationObject = {};
|
|
1568
|
+
|
|
1569
|
+
// Verify: for each row, check that Current == Prev1 + Prev2 (except seed rows)
|
|
1570
|
+
let tmpResult = _Parser.solve(
|
|
1571
|
+
'FibCheck = MULTIROWMAP ROWS FROM FibRows VAR Current FROM Fib VAR Prev1 FROM Fib OFFSET -1 DEFAULT 0 VAR Prev2 FROM Fib OFFSET -2 DEFAULT 0 : Current - (Prev1 + Prev2)',
|
|
1572
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
1573
|
+
|
|
1574
|
+
Expect(tmpResult).to.be.an('array');
|
|
1575
|
+
Expect(tmpResult.length).to.equal(8);
|
|
1576
|
+
// Row 0: 0 - (0 + 0) = 0 (seed)
|
|
1577
|
+
Expect(tmpResult[0]).to.equal('0');
|
|
1578
|
+
// Row 1: 1 - (0 + 0) = 1 (seed)
|
|
1579
|
+
Expect(tmpResult[1]).to.equal('1');
|
|
1580
|
+
// Row 2 onwards should all be 0 since each is the sum of previous two
|
|
1581
|
+
Expect(tmpResult[2]).to.equal('0');
|
|
1582
|
+
Expect(tmpResult[3]).to.equal('0');
|
|
1583
|
+
Expect(tmpResult[4]).to.equal('0');
|
|
1584
|
+
Expect(tmpResult[5]).to.equal('0');
|
|
1585
|
+
Expect(tmpResult[6]).to.equal('0');
|
|
1586
|
+
Expect(tmpResult[7]).to.equal('0');
|
|
1587
|
+
|
|
1588
|
+
return fDone();
|
|
1589
|
+
}
|
|
1590
|
+
);
|
|
1591
|
+
test
|
|
1592
|
+
(
|
|
1593
|
+
'Test MULTIROWMAP with custom default values',
|
|
1594
|
+
(fDone) =>
|
|
1595
|
+
{
|
|
1596
|
+
let testFable = new libFable();
|
|
1597
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
1598
|
+
let tmpManifest = testFable.newManyfest();
|
|
1599
|
+
|
|
1600
|
+
let tmpDataSourceObject = {
|
|
1601
|
+
Rows: [
|
|
1602
|
+
{ Value: 10 },
|
|
1603
|
+
{ Value: 20 },
|
|
1604
|
+
{ Value: 30 },
|
|
1605
|
+
]
|
|
1606
|
+
};
|
|
1607
|
+
let tmpDataDestinationObject = {};
|
|
1608
|
+
|
|
1609
|
+
// Use negative default for previous row
|
|
1610
|
+
let tmpResult = _Parser.solve(
|
|
1611
|
+
'WithDefaults = MULTIROWMAP ROWS FROM Rows VAR Current FROM Value VAR Previous FROM Value OFFSET -1 DEFAULT -1 : Current + Previous',
|
|
1612
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
1613
|
+
|
|
1614
|
+
Expect(tmpResult).to.be.an('array');
|
|
1615
|
+
Expect(tmpResult.length).to.equal(3);
|
|
1616
|
+
// Row 0: 10 + (-1) = 9
|
|
1617
|
+
Expect(tmpResult[0]).to.equal('9');
|
|
1618
|
+
// Row 1: 20 + 10 = 30
|
|
1619
|
+
Expect(tmpResult[1]).to.equal('30');
|
|
1620
|
+
// Row 2: 30 + 20 = 50
|
|
1621
|
+
Expect(tmpResult[2]).to.equal('50');
|
|
1622
|
+
|
|
1623
|
+
return fDone();
|
|
1624
|
+
}
|
|
1625
|
+
);
|
|
1626
|
+
test
|
|
1627
|
+
(
|
|
1628
|
+
'Test MULTIROWMAP with multiple properties from same row offset',
|
|
1629
|
+
(fDone) =>
|
|
1630
|
+
{
|
|
1631
|
+
let testFable = new libFable();
|
|
1632
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
1633
|
+
let tmpManifest = testFable.newManyfest();
|
|
1634
|
+
|
|
1635
|
+
let tmpDataSourceObject = {
|
|
1636
|
+
Measurements: [
|
|
1637
|
+
{ Width: 10, Height: 5, Depth: 2 },
|
|
1638
|
+
{ Width: 20, Height: 10, Depth: 4 },
|
|
1639
|
+
{ Width: 30, Height: 15, Depth: 6 },
|
|
1640
|
+
{ Width: 40, Height: 20, Depth: 8 },
|
|
1641
|
+
]
|
|
1642
|
+
};
|
|
1643
|
+
let tmpDataDestinationObject = {};
|
|
1644
|
+
|
|
1645
|
+
// Compare volume change: current volume minus previous volume
|
|
1646
|
+
let tmpResult = _Parser.solve(
|
|
1647
|
+
'VolumeDelta = MULTIROWMAP ROWS FROM Measurements VAR w FROM Width VAR h FROM Height VAR d FROM Depth VAR pw FROM Width OFFSET -1 DEFAULT 0 VAR ph FROM Height OFFSET -1 DEFAULT 0 VAR pd FROM Depth OFFSET -1 DEFAULT 0 : (w * h * d) - (pw * ph * pd)',
|
|
1648
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
1649
|
+
|
|
1650
|
+
Expect(tmpResult).to.be.an('array');
|
|
1651
|
+
Expect(tmpResult.length).to.equal(4);
|
|
1652
|
+
// Row 0: (10*5*2) - (0*0*0) = 100 - 0 = 100
|
|
1653
|
+
Expect(tmpResult[0]).to.equal('100');
|
|
1654
|
+
// Row 1: (20*10*4) - (10*5*2) = 800 - 100 = 700
|
|
1655
|
+
Expect(tmpResult[1]).to.equal('700');
|
|
1656
|
+
// Row 2: (30*15*6) - (20*10*4) = 2700 - 800 = 1900
|
|
1657
|
+
Expect(tmpResult[2]).to.equal('1900');
|
|
1658
|
+
// Row 3: (40*20*8) - (30*15*6) = 6400 - 2700 = 3700
|
|
1659
|
+
Expect(tmpResult[3]).to.equal('3700');
|
|
1660
|
+
|
|
1661
|
+
return fDone();
|
|
1662
|
+
}
|
|
1663
|
+
);
|
|
1664
|
+
test
|
|
1665
|
+
(
|
|
1666
|
+
'Test MULTIROWMAP with stepIndex variable',
|
|
1667
|
+
(fDone) =>
|
|
1668
|
+
{
|
|
1669
|
+
let testFable = new libFable();
|
|
1670
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
1671
|
+
let tmpManifest = testFable.newManyfest();
|
|
1672
|
+
|
|
1673
|
+
let tmpDataSourceObject = {
|
|
1674
|
+
Rows: [
|
|
1675
|
+
{ Value: 100 },
|
|
1676
|
+
{ Value: 200 },
|
|
1677
|
+
{ Value: 300 },
|
|
1678
|
+
]
|
|
1679
|
+
};
|
|
1680
|
+
let tmpDataDestinationObject = {};
|
|
1681
|
+
|
|
1682
|
+
// Use stepIndex (row index) in the expression
|
|
1683
|
+
let tmpResult = _Parser.solve(
|
|
1684
|
+
'Indexed = MULTIROWMAP ROWS FROM Rows VAR v FROM Value : v + stepIndex * 1000',
|
|
1685
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
1686
|
+
|
|
1687
|
+
Expect(tmpResult).to.be.an('array');
|
|
1688
|
+
Expect(tmpResult.length).to.equal(3);
|
|
1689
|
+
Expect(tmpResult[0]).to.equal('100');
|
|
1690
|
+
Expect(tmpResult[1]).to.equal('1200');
|
|
1691
|
+
Expect(tmpResult[2]).to.equal('2300');
|
|
1692
|
+
|
|
1693
|
+
return fDone();
|
|
1694
|
+
}
|
|
1695
|
+
);
|
|
1696
|
+
test
|
|
1697
|
+
(
|
|
1698
|
+
'Test MULTIROWMAP moving average',
|
|
1699
|
+
(fDone) =>
|
|
1700
|
+
{
|
|
1701
|
+
let testFable = new libFable();
|
|
1702
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
1703
|
+
let tmpManifest = testFable.newManyfest();
|
|
1704
|
+
|
|
1705
|
+
let tmpDataSourceObject = {
|
|
1706
|
+
Prices: [
|
|
1707
|
+
{ Close: 100 },
|
|
1708
|
+
{ Close: 110 },
|
|
1709
|
+
{ Close: 105 },
|
|
1710
|
+
{ Close: 115 },
|
|
1711
|
+
{ Close: 120 },
|
|
1712
|
+
]
|
|
1713
|
+
};
|
|
1714
|
+
let tmpDataDestinationObject = {};
|
|
1715
|
+
|
|
1716
|
+
// 3-period moving average: (current + prev + two-back) / 3
|
|
1717
|
+
let tmpResult = _Parser.solve(
|
|
1718
|
+
'MA3 = MULTIROWMAP ROWS FROM Prices VAR c0 FROM Close VAR c1 FROM Close OFFSET -1 DEFAULT 0 VAR c2 FROM Close OFFSET -2 DEFAULT 0 : (c0 + c1 + c2) / 3',
|
|
1719
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
1720
|
+
|
|
1721
|
+
Expect(tmpResult).to.be.an('array');
|
|
1722
|
+
Expect(tmpResult.length).to.equal(5);
|
|
1723
|
+
// Row 2 onwards has three valid values for a true 3-period MA
|
|
1724
|
+
// Row 2: (105 + 110 + 100) / 3 = 105
|
|
1725
|
+
Expect(tmpResult[2]).to.equal('105');
|
|
1726
|
+
// Row 3: (115 + 105 + 110) / 3 = 110
|
|
1727
|
+
Expect(tmpResult[3]).to.equal('110');
|
|
1728
|
+
// Row 4: (120 + 115 + 105) / 3 = 113.33...
|
|
1729
|
+
Expect(Number(tmpResult[4])).to.be.closeTo(113.333, 0.01);
|
|
1730
|
+
|
|
1731
|
+
return fDone();
|
|
1732
|
+
}
|
|
1733
|
+
);
|
|
1734
|
+
test
|
|
1735
|
+
(
|
|
1736
|
+
'Test MULTIROWMAP returns undefined for invalid rows address',
|
|
1737
|
+
(fDone) =>
|
|
1738
|
+
{
|
|
1739
|
+
let testFable = new libFable();
|
|
1740
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
1741
|
+
let tmpManifest = testFable.newManyfest();
|
|
1742
|
+
|
|
1743
|
+
let tmpDataSourceObject = {};
|
|
1744
|
+
let tmpDataDestinationObject = {};
|
|
1745
|
+
|
|
1746
|
+
// No valid rows
|
|
1747
|
+
let tmpResult = _Parser.solve(
|
|
1748
|
+
'Bad = MULTIROWMAP ROWS FROM NonExistent VAR v FROM Value : v + 1',
|
|
1749
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
1750
|
+
|
|
1751
|
+
Expect(tmpResult).to.be.undefined;
|
|
1752
|
+
|
|
1753
|
+
return fDone();
|
|
1754
|
+
}
|
|
1755
|
+
);
|
|
1756
|
+
test
|
|
1757
|
+
(
|
|
1758
|
+
'Test MULTIROWMAP with forward row lookback (positive OFFSET)',
|
|
1759
|
+
(fDone) =>
|
|
1760
|
+
{
|
|
1761
|
+
let testFable = new libFable();
|
|
1762
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
1763
|
+
let tmpManifest = testFable.newManyfest();
|
|
1764
|
+
|
|
1765
|
+
let tmpDataSourceObject = {
|
|
1766
|
+
Rows: [
|
|
1767
|
+
{ Value: 10 },
|
|
1768
|
+
{ Value: 20 },
|
|
1769
|
+
{ Value: 30 },
|
|
1770
|
+
{ Value: 40 },
|
|
1771
|
+
{ Value: 50 },
|
|
1772
|
+
]
|
|
1773
|
+
};
|
|
1774
|
+
let tmpDataDestinationObject = {};
|
|
1775
|
+
|
|
1776
|
+
// Look ahead: current value minus next row's value
|
|
1777
|
+
let tmpResult = _Parser.solve(
|
|
1778
|
+
'ForwardDelta = MULTIROWMAP ROWS FROM Rows VAR Current FROM Value VAR Next FROM Value OFFSET 1 DEFAULT 0 : Next - Current',
|
|
1779
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
1780
|
+
|
|
1781
|
+
Expect(tmpResult).to.be.an('array');
|
|
1782
|
+
Expect(tmpResult.length).to.equal(5);
|
|
1783
|
+
// Row 0: 20 - 10 = 10
|
|
1784
|
+
Expect(tmpResult[0]).to.equal('10');
|
|
1785
|
+
// Row 1: 30 - 20 = 10
|
|
1786
|
+
Expect(tmpResult[1]).to.equal('10');
|
|
1787
|
+
// Row 2: 40 - 30 = 10
|
|
1788
|
+
Expect(tmpResult[2]).to.equal('10');
|
|
1789
|
+
// Row 3: 50 - 40 = 10
|
|
1790
|
+
Expect(tmpResult[3]).to.equal('10');
|
|
1791
|
+
// Row 4: 0 (default) - 50 = -50
|
|
1792
|
+
Expect(tmpResult[4]).to.equal('-50');
|
|
1793
|
+
|
|
1794
|
+
return fDone();
|
|
1795
|
+
}
|
|
1796
|
+
);
|
|
1797
|
+
test
|
|
1798
|
+
(
|
|
1799
|
+
'Test MULTIROWMAP with forward lookback two rows ahead (OFFSET 2)',
|
|
1800
|
+
(fDone) =>
|
|
1801
|
+
{
|
|
1802
|
+
let testFable = new libFable();
|
|
1803
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
1804
|
+
let tmpManifest = testFable.newManyfest();
|
|
1805
|
+
|
|
1806
|
+
let tmpDataSourceObject = {
|
|
1807
|
+
Rows: [
|
|
1808
|
+
{ Value: 1 },
|
|
1809
|
+
{ Value: 2 },
|
|
1810
|
+
{ Value: 3 },
|
|
1811
|
+
{ Value: 4 },
|
|
1812
|
+
{ Value: 5 },
|
|
1813
|
+
]
|
|
1814
|
+
};
|
|
1815
|
+
let tmpDataDestinationObject = {};
|
|
1816
|
+
|
|
1817
|
+
// Look two ahead
|
|
1818
|
+
let tmpResult = _Parser.solve(
|
|
1819
|
+
'TwoAhead = MULTIROWMAP ROWS FROM Rows VAR Current FROM Value VAR Future FROM Value OFFSET 2 DEFAULT 99 : Future + Current',
|
|
1820
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
1821
|
+
|
|
1822
|
+
Expect(tmpResult).to.be.an('array');
|
|
1823
|
+
Expect(tmpResult.length).to.equal(5);
|
|
1824
|
+
// Row 0: 3 + 1 = 4
|
|
1825
|
+
Expect(tmpResult[0]).to.equal('4');
|
|
1826
|
+
// Row 1: 4 + 2 = 6
|
|
1827
|
+
Expect(tmpResult[1]).to.equal('6');
|
|
1828
|
+
// Row 2: 5 + 3 = 8
|
|
1829
|
+
Expect(tmpResult[2]).to.equal('8');
|
|
1830
|
+
// Row 3: 99 (default) + 4 = 103
|
|
1831
|
+
Expect(tmpResult[3]).to.equal('103');
|
|
1832
|
+
// Row 4: 99 (default) + 5 = 104
|
|
1833
|
+
Expect(tmpResult[4]).to.equal('104');
|
|
1834
|
+
|
|
1835
|
+
return fDone();
|
|
1836
|
+
}
|
|
1837
|
+
);
|
|
1838
|
+
test
|
|
1839
|
+
(
|
|
1840
|
+
'Test MULTIROWMAP with SERIESSTART to skip initial rows',
|
|
1841
|
+
(fDone) =>
|
|
1842
|
+
{
|
|
1843
|
+
let testFable = new libFable();
|
|
1844
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
1845
|
+
let tmpManifest = testFable.newManyfest();
|
|
1846
|
+
|
|
1847
|
+
let tmpDataSourceObject = {
|
|
1848
|
+
Rows: [
|
|
1849
|
+
{ Value: 100 },
|
|
1850
|
+
{ Value: 200 },
|
|
1851
|
+
{ Value: 300 },
|
|
1852
|
+
{ Value: 400 },
|
|
1853
|
+
{ Value: 500 },
|
|
1854
|
+
]
|
|
1855
|
+
};
|
|
1856
|
+
let tmpDataDestinationObject = {};
|
|
1857
|
+
|
|
1858
|
+
// Start from row 2
|
|
1859
|
+
let tmpResult = _Parser.solve(
|
|
1860
|
+
'FromRow2 = MULTIROWMAP ROWS FROM Rows SERIESSTART 2 VAR v FROM Value : v + 0',
|
|
1861
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
1862
|
+
|
|
1863
|
+
Expect(tmpResult).to.be.an('array');
|
|
1864
|
+
Expect(tmpResult.length).to.equal(3);
|
|
1865
|
+
Expect(tmpResult[0]).to.equal('300');
|
|
1866
|
+
Expect(tmpResult[1]).to.equal('400');
|
|
1867
|
+
Expect(tmpResult[2]).to.equal('500');
|
|
1868
|
+
|
|
1869
|
+
return fDone();
|
|
1870
|
+
}
|
|
1871
|
+
);
|
|
1872
|
+
test
|
|
1873
|
+
(
|
|
1874
|
+
'Test MULTIROWMAP with negative SERIESSTART (count from end)',
|
|
1875
|
+
(fDone) =>
|
|
1876
|
+
{
|
|
1877
|
+
let testFable = new libFable();
|
|
1878
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
1879
|
+
let tmpManifest = testFable.newManyfest();
|
|
1880
|
+
|
|
1881
|
+
let tmpDataSourceObject = {
|
|
1882
|
+
Rows: [
|
|
1883
|
+
{ Value: 10 },
|
|
1884
|
+
{ Value: 20 },
|
|
1885
|
+
{ Value: 30 },
|
|
1886
|
+
{ Value: 40 },
|
|
1887
|
+
{ Value: 50 },
|
|
1888
|
+
]
|
|
1889
|
+
};
|
|
1890
|
+
let tmpDataDestinationObject = {};
|
|
1891
|
+
|
|
1892
|
+
// Start from 2 rows before the end
|
|
1893
|
+
let tmpResult = _Parser.solve(
|
|
1894
|
+
'LastTwo = MULTIROWMAP ROWS FROM Rows SERIESSTART -2 VAR v FROM Value : v + 0',
|
|
1895
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
1896
|
+
|
|
1897
|
+
Expect(tmpResult).to.be.an('array');
|
|
1898
|
+
Expect(tmpResult.length).to.equal(2);
|
|
1899
|
+
Expect(tmpResult[0]).to.equal('40');
|
|
1900
|
+
Expect(tmpResult[1]).to.equal('50');
|
|
1901
|
+
|
|
1902
|
+
return fDone();
|
|
1903
|
+
}
|
|
1904
|
+
);
|
|
1905
|
+
test
|
|
1906
|
+
(
|
|
1907
|
+
'Test MULTIROWMAP with SERIESSTEP -1 (iterate backwards)',
|
|
1908
|
+
(fDone) =>
|
|
1909
|
+
{
|
|
1910
|
+
let testFable = new libFable();
|
|
1911
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
1912
|
+
let tmpManifest = testFable.newManyfest();
|
|
1913
|
+
|
|
1914
|
+
let tmpDataSourceObject = {
|
|
1915
|
+
Rows: [
|
|
1916
|
+
{ Value: 10 },
|
|
1917
|
+
{ Value: 20 },
|
|
1918
|
+
{ Value: 30 },
|
|
1919
|
+
{ Value: 40 },
|
|
1920
|
+
{ Value: 50 },
|
|
1921
|
+
]
|
|
1922
|
+
};
|
|
1923
|
+
let tmpDataDestinationObject = {};
|
|
1924
|
+
|
|
1925
|
+
// Iterate backwards from the last row
|
|
1926
|
+
let tmpResult = _Parser.solve(
|
|
1927
|
+
'Reversed = MULTIROWMAP ROWS FROM Rows SERIESSTART -1 SERIESSTEP -1 VAR v FROM Value : v + 0',
|
|
1928
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
1929
|
+
|
|
1930
|
+
Expect(tmpResult).to.be.an('array');
|
|
1931
|
+
Expect(tmpResult.length).to.equal(5);
|
|
1932
|
+
Expect(tmpResult[0]).to.equal('50');
|
|
1933
|
+
Expect(tmpResult[1]).to.equal('40');
|
|
1934
|
+
Expect(tmpResult[2]).to.equal('30');
|
|
1935
|
+
Expect(tmpResult[3]).to.equal('20');
|
|
1936
|
+
Expect(tmpResult[4]).to.equal('10');
|
|
1937
|
+
|
|
1938
|
+
return fDone();
|
|
1939
|
+
}
|
|
1940
|
+
);
|
|
1941
|
+
test
|
|
1942
|
+
(
|
|
1943
|
+
'Test MULTIROWMAP with SERIESSTEP 2 (skip every other row)',
|
|
1944
|
+
(fDone) =>
|
|
1945
|
+
{
|
|
1946
|
+
let testFable = new libFable();
|
|
1947
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
1948
|
+
let tmpManifest = testFable.newManyfest();
|
|
1949
|
+
|
|
1950
|
+
let tmpDataSourceObject = {
|
|
1951
|
+
Rows: [
|
|
1952
|
+
{ Value: 10 },
|
|
1953
|
+
{ Value: 20 },
|
|
1954
|
+
{ Value: 30 },
|
|
1955
|
+
{ Value: 40 },
|
|
1956
|
+
{ Value: 50 },
|
|
1957
|
+
{ Value: 60 },
|
|
1958
|
+
]
|
|
1959
|
+
};
|
|
1960
|
+
let tmpDataDestinationObject = {};
|
|
1961
|
+
|
|
1962
|
+
// Every other row
|
|
1963
|
+
let tmpResult = _Parser.solve(
|
|
1964
|
+
'EveryOther = MULTIROWMAP ROWS FROM Rows SERIESSTEP 2 VAR v FROM Value : v + 0',
|
|
1965
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
1966
|
+
|
|
1967
|
+
Expect(tmpResult).to.be.an('array');
|
|
1968
|
+
Expect(tmpResult.length).to.equal(3);
|
|
1969
|
+
Expect(tmpResult[0]).to.equal('10');
|
|
1970
|
+
Expect(tmpResult[1]).to.equal('30');
|
|
1971
|
+
Expect(tmpResult[2]).to.equal('50');
|
|
1972
|
+
|
|
1973
|
+
return fDone();
|
|
1974
|
+
}
|
|
1975
|
+
);
|
|
1976
|
+
test
|
|
1977
|
+
(
|
|
1978
|
+
'Test MULTIROWMAP backwards with lookback still works',
|
|
1979
|
+
(fDone) =>
|
|
1980
|
+
{
|
|
1981
|
+
let testFable = new libFable();
|
|
1982
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
1983
|
+
let tmpManifest = testFable.newManyfest();
|
|
1984
|
+
|
|
1985
|
+
let tmpDataSourceObject = {
|
|
1986
|
+
Rows: [
|
|
1987
|
+
{ Value: 10 },
|
|
1988
|
+
{ Value: 20 },
|
|
1989
|
+
{ Value: 30 },
|
|
1990
|
+
{ Value: 40 },
|
|
1991
|
+
]
|
|
1992
|
+
};
|
|
1993
|
+
let tmpDataDestinationObject = {};
|
|
1994
|
+
|
|
1995
|
+
// Going backwards, previous row (OFFSET -1) means the row with a LOWER index
|
|
1996
|
+
// which is actually the next row in reverse iteration order
|
|
1997
|
+
let tmpResult = _Parser.solve(
|
|
1998
|
+
'BackwardsWithLook = MULTIROWMAP ROWS FROM Rows SERIESSTART -1 SERIESSTEP -1 VAR Current FROM Value VAR Prev FROM Value OFFSET -1 DEFAULT 0 : Current - Prev',
|
|
1999
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
2000
|
+
|
|
2001
|
+
Expect(tmpResult).to.be.an('array');
|
|
2002
|
+
Expect(tmpResult.length).to.equal(4);
|
|
2003
|
+
// Row 3 (first in reverse): 40 - 30 = 10
|
|
2004
|
+
Expect(tmpResult[0]).to.equal('10');
|
|
2005
|
+
// Row 2: 30 - 20 = 10
|
|
2006
|
+
Expect(tmpResult[1]).to.equal('10');
|
|
2007
|
+
// Row 1: 20 - 10 = 10
|
|
2008
|
+
Expect(tmpResult[2]).to.equal('10');
|
|
2009
|
+
// Row 0: 10 - 0 (default) = 10
|
|
2010
|
+
Expect(tmpResult[3]).to.equal('10');
|
|
2011
|
+
|
|
2012
|
+
return fDone();
|
|
2013
|
+
}
|
|
2014
|
+
);
|
|
2015
|
+
test
|
|
2016
|
+
(
|
|
2017
|
+
'Test MULTIROWMAP with SERIESSTART and SERIESSTEP combined',
|
|
2018
|
+
(fDone) =>
|
|
2019
|
+
{
|
|
2020
|
+
let testFable = new libFable();
|
|
2021
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
2022
|
+
let tmpManifest = testFable.newManyfest();
|
|
2023
|
+
|
|
2024
|
+
let tmpDataSourceObject = {
|
|
2025
|
+
Rows: [
|
|
2026
|
+
{ Value: 10 },
|
|
2027
|
+
{ Value: 20 },
|
|
2028
|
+
{ Value: 30 },
|
|
2029
|
+
{ Value: 40 },
|
|
2030
|
+
{ Value: 50 },
|
|
2031
|
+
{ Value: 60 },
|
|
2032
|
+
{ Value: 70 },
|
|
2033
|
+
{ Value: 80 },
|
|
2034
|
+
]
|
|
2035
|
+
};
|
|
2036
|
+
let tmpDataDestinationObject = {};
|
|
2037
|
+
|
|
2038
|
+
// Start at row 1, step by 3
|
|
2039
|
+
let tmpResult = _Parser.solve(
|
|
2040
|
+
'SteppedStart = MULTIROWMAP ROWS FROM Rows SERIESSTART 1 SERIESSTEP 3 VAR v FROM Value : v + 0',
|
|
2041
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
2042
|
+
|
|
2043
|
+
Expect(tmpResult).to.be.an('array');
|
|
2044
|
+
// Rows: 1, 4, 7 => Values: 20, 50, 80
|
|
2045
|
+
Expect(tmpResult.length).to.equal(3);
|
|
2046
|
+
Expect(tmpResult[0]).to.equal('20');
|
|
2047
|
+
Expect(tmpResult[1]).to.equal('50');
|
|
2048
|
+
Expect(tmpResult[2]).to.equal('80');
|
|
2049
|
+
|
|
2050
|
+
return fDone();
|
|
2051
|
+
}
|
|
2052
|
+
);
|
|
2053
|
+
test
|
|
2054
|
+
(
|
|
2055
|
+
'Test MULTIROWMAP with both forward and backward lookback simultaneously',
|
|
2056
|
+
(fDone) =>
|
|
2057
|
+
{
|
|
2058
|
+
let testFable = new libFable();
|
|
2059
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
2060
|
+
let tmpManifest = testFable.newManyfest();
|
|
2061
|
+
|
|
2062
|
+
let tmpDataSourceObject = {
|
|
2063
|
+
Rows: [
|
|
2064
|
+
{ Value: 10 },
|
|
2065
|
+
{ Value: 20 },
|
|
2066
|
+
{ Value: 30 },
|
|
2067
|
+
{ Value: 40 },
|
|
2068
|
+
{ Value: 50 },
|
|
2069
|
+
]
|
|
2070
|
+
};
|
|
2071
|
+
let tmpDataDestinationObject = {};
|
|
2072
|
+
|
|
2073
|
+
// Central difference: (next - previous) / 2
|
|
2074
|
+
let tmpResult = _Parser.solve(
|
|
2075
|
+
'CentralDiff = MULTIROWMAP ROWS FROM Rows VAR Prev FROM Value OFFSET -1 DEFAULT 0 VAR Next FROM Value OFFSET 1 DEFAULT 0 : (Next - Prev) / 2',
|
|
2076
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
2077
|
+
|
|
2078
|
+
Expect(tmpResult).to.be.an('array');
|
|
2079
|
+
Expect(tmpResult.length).to.equal(5);
|
|
2080
|
+
// Row 0: (20 - 0) / 2 = 10
|
|
2081
|
+
Expect(tmpResult[0]).to.equal('10');
|
|
2082
|
+
// Row 1: (30 - 10) / 2 = 10
|
|
2083
|
+
Expect(tmpResult[1]).to.equal('10');
|
|
2084
|
+
// Row 2: (40 - 20) / 2 = 10
|
|
2085
|
+
Expect(tmpResult[2]).to.equal('10');
|
|
2086
|
+
// Row 3: (50 - 30) / 2 = 10
|
|
2087
|
+
Expect(tmpResult[3]).to.equal('10');
|
|
2088
|
+
// Row 4: (0 - 40) / 2 = -20
|
|
2089
|
+
Expect(tmpResult[4]).to.equal('-20');
|
|
2090
|
+
|
|
2091
|
+
return fDone();
|
|
2092
|
+
}
|
|
2093
|
+
);
|
|
2094
|
+
test
|
|
2095
|
+
(
|
|
2096
|
+
'Test MULTIROWMAP with rowIndex and stepIndex variables',
|
|
2097
|
+
(fDone) =>
|
|
2098
|
+
{
|
|
2099
|
+
let testFable = new libFable();
|
|
2100
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
2101
|
+
let tmpManifest = testFable.newManyfest();
|
|
2102
|
+
|
|
2103
|
+
let tmpDataSourceObject = {
|
|
2104
|
+
Rows: [
|
|
2105
|
+
{ Value: 100 },
|
|
2106
|
+
{ Value: 200 },
|
|
2107
|
+
{ Value: 300 },
|
|
2108
|
+
{ Value: 400 },
|
|
2109
|
+
{ Value: 500 },
|
|
2110
|
+
]
|
|
2111
|
+
};
|
|
2112
|
+
let tmpDataDestinationObject = {};
|
|
2113
|
+
|
|
2114
|
+
// With SERIESSTART, rowIndex is the actual array index, stepIndex is the iteration count
|
|
2115
|
+
let tmpResult = _Parser.solve(
|
|
2116
|
+
'IndexTest = MULTIROWMAP ROWS FROM Rows SERIESSTART 2 VAR v FROM Value : v + rowIndex * 1000 + stepIndex',
|
|
2117
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
2118
|
+
|
|
2119
|
+
Expect(tmpResult).to.be.an('array');
|
|
2120
|
+
Expect(tmpResult.length).to.equal(3);
|
|
2121
|
+
// stepIndex=0, rowIndex=2: 300 + 2000 + 0 = 2300
|
|
2122
|
+
Expect(tmpResult[0]).to.equal('2300');
|
|
2123
|
+
// stepIndex=1, rowIndex=3: 400 + 3000 + 1 = 3401
|
|
2124
|
+
Expect(tmpResult[1]).to.equal('3401');
|
|
2125
|
+
// stepIndex=2, rowIndex=4: 500 + 4000 + 2 = 4502
|
|
2126
|
+
Expect(tmpResult[2]).to.equal('4502');
|
|
2127
|
+
|
|
2128
|
+
return fDone();
|
|
2129
|
+
}
|
|
2130
|
+
);
|
|
2131
|
+
test
|
|
2132
|
+
(
|
|
2133
|
+
'Test MULTIROWMAP SERIESSTEP zero returns undefined',
|
|
2134
|
+
(fDone) =>
|
|
2135
|
+
{
|
|
2136
|
+
let testFable = new libFable();
|
|
2137
|
+
let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
2138
|
+
let tmpManifest = testFable.newManyfest();
|
|
2139
|
+
|
|
2140
|
+
let tmpDataSourceObject = {
|
|
2141
|
+
Rows: [{ Value: 1 }]
|
|
2142
|
+
};
|
|
2143
|
+
let tmpDataDestinationObject = {};
|
|
2144
|
+
|
|
2145
|
+
let tmpResult = _Parser.solve(
|
|
2146
|
+
'BadStep = MULTIROWMAP ROWS FROM Rows SERIESSTEP 0 VAR v FROM Value : v + 0',
|
|
2147
|
+
tmpDataSourceObject, {}, tmpManifest, tmpDataDestinationObject);
|
|
2148
|
+
|
|
2149
|
+
Expect(tmpResult).to.be.undefined;
|
|
2150
|
+
|
|
2151
|
+
return fDone();
|
|
2152
|
+
}
|
|
2153
|
+
);
|
|
2154
|
+
}
|
|
2155
|
+
);
|
|
1387
2156
|
}
|
|
1388
2157
|
);
|