@@ -1481,3 +1481,173 @@ describe('Permutation Matching Optimization', () => {
14811481 } ) ;
14821482 } ) ;
14831483} ) ;
1484+
1485+ // ============================================================================
1486+ // REPEATED WILDCARDS IN NESTED CONTEXTS (TODO #3)
1487+ // ============================================================================
1488+
1489+ describe ( 'Repeated Wildcards in Nested Contexts' , ( ) => {
1490+ // Non-canonical matching helper
1491+ const matchNonCanonical = ( pattern , expr ) => {
1492+ const boxedPattern = ce . box ( pattern , { canonical : false } ) ;
1493+ const boxedExpr = ce . box ( expr , { canonical : false } ) ;
1494+ return boxedExpr . match ( boxedPattern ) ;
1495+ } ;
1496+
1497+ describe ( 'Simple repeated wildcards (flat structure)' , ( ) => {
1498+ test ( '_a appears twice - same value should match' , ( ) => {
1499+ const result = matchNonCanonical (
1500+ [ 'Add' , '_a' , '_a' ] ,
1501+ [ 'Add' , 'x' , 'x' ]
1502+ ) ;
1503+ expect ( result ) . not . toBeNull ( ) ;
1504+ expect ( result ?. _a ?. toString ( ) ) . toBe ( 'x' ) ;
1505+ } ) ;
1506+
1507+ test ( '_a appears twice - different values should not match' , ( ) => {
1508+ const result = matchNonCanonical (
1509+ [ 'Add' , '_a' , '_a' ] ,
1510+ [ 'Add' , 'x' , 'y' ]
1511+ ) ;
1512+ expect ( result ) . toBeNull ( ) ;
1513+ } ) ;
1514+ } ) ;
1515+
1516+ describe ( 'Repeated wildcards in nested function arguments' , ( ) => {
1517+ test ( '_x in direct arg and inside Ln - same value' , ( ) => {
1518+ const result = matchNonCanonical (
1519+ [ 'Multiply' , '_x' , [ 'Ln' , '_x' ] ] ,
1520+ [ 'Multiply' , 'x' , [ 'Ln' , 'x' ] ]
1521+ ) ;
1522+ expect ( result ) . not . toBeNull ( ) ;
1523+ expect ( result ?. _x ?. toString ( ) ) . toBe ( 'x' ) ;
1524+ } ) ;
1525+
1526+ test ( '_x in direct arg and inside Ln - different values should not match' , ( ) => {
1527+ const result = matchNonCanonical (
1528+ [ 'Multiply' , '_x' , [ 'Ln' , '_x' ] ] ,
1529+ [ 'Multiply' , 'x' , [ 'Ln' , 'y' ] ]
1530+ ) ;
1531+ expect ( result ) . toBeNull ( ) ;
1532+ } ) ;
1533+
1534+ test ( '_x appears at 3 different nesting levels' , ( ) => {
1535+ const result = matchNonCanonical (
1536+ [ 'Add' , '_x' , [ 'Multiply' , '_x' , [ 'Power' , '_x' , 2 ] ] ] ,
1537+ [ 'Add' , 'x' , [ 'Multiply' , 'x' , [ 'Power' , 'x' , 2 ] ] ]
1538+ ) ;
1539+ expect ( result ) . not . toBeNull ( ) ;
1540+ expect ( result ?. _x ?. toString ( ) ) . toBe ( 'x' ) ;
1541+ } ) ;
1542+
1543+ test ( 'exp(x)*sin(x) with repeated _x' , ( ) => {
1544+ const result = matchNonCanonical (
1545+ [ 'Multiply' , [ 'Exp' , '_x' ] , [ 'Sin' , '_x' ] ] ,
1546+ [ 'Multiply' , [ 'Exp' , 'x' ] , [ 'Sin' , 'x' ] ]
1547+ ) ;
1548+ expect ( result ) . not . toBeNull ( ) ;
1549+ expect ( result ?. _x ?. toString ( ) ) . toBe ( 'x' ) ;
1550+ } ) ;
1551+ } ) ;
1552+
1553+ describe ( 'Repeated wildcards with Divide patterns' , ( ) => {
1554+ test ( '1/(x*ln(x)) pattern - the TODO #3 example' , ( ) => {
1555+ const result = matchNonCanonical (
1556+ [ 'Divide' , 1 , [ 'Multiply' , '_x' , [ 'Ln' , '_x' ] ] ] ,
1557+ [ 'Divide' , 1 , [ 'Multiply' , 'x' , [ 'Ln' , 'x' ] ] ]
1558+ ) ;
1559+ expect ( result ) . not . toBeNull ( ) ;
1560+ expect ( result ?. _x ?. toString ( ) ) . toBe ( 'x' ) ;
1561+ } ) ;
1562+
1563+ test ( '1/(x*ln(x)) with different variables should not match' , ( ) => {
1564+ const result = matchNonCanonical (
1565+ [ 'Divide' , 1 , [ 'Multiply' , '_x' , [ 'Ln' , '_x' ] ] ] ,
1566+ [ 'Divide' , 1 , [ 'Multiply' , 'x' , [ 'Ln' , 'y' ] ] ]
1567+ ) ;
1568+ expect ( result ) . toBeNull ( ) ;
1569+ } ) ;
1570+ } ) ;
1571+
1572+ describe ( 'Repeated wildcards matching complex expressions' , ( ) => {
1573+ test ( '_x should match the same complex expression everywhere' , ( ) => {
1574+ const result = matchNonCanonical (
1575+ [ 'Add' , '_x' , [ 'Power' , '_x' , 2 ] ] ,
1576+ [ 'Add' , [ 'Add' , 'a' , 1 ] , [ 'Power' , [ 'Add' , 'a' , 1 ] , 2 ] ]
1577+ ) ;
1578+ expect ( result ) . not . toBeNull ( ) ;
1579+ // _x should match ['Add', 'a', 1]
1580+ } ) ;
1581+
1582+ test ( '_x matching complex expr - mismatch should fail' , ( ) => {
1583+ const result = matchNonCanonical (
1584+ [ 'Add' , '_x' , [ 'Power' , '_x' , 2 ] ] ,
1585+ [ 'Add' , [ 'Add' , 'a' , 1 ] , [ 'Power' , [ 'Add' , 'a' , 2 ] , 2 ] ]
1586+ ) ;
1587+ expect ( result ) . toBeNull ( ) ;
1588+ } ) ;
1589+ } ) ;
1590+
1591+ describe ( 'Repeated wildcards with commutative reordering' , ( ) => {
1592+ test ( '_x + ln(_x) with Add being commutative' , ( ) => {
1593+ const result = matchNonCanonical (
1594+ [ 'Add' , '_x' , [ 'Ln' , '_x' ] ] ,
1595+ [ 'Add' , [ 'Ln' , 'x' ] , 'x' ]
1596+ ) ;
1597+ // Add is commutative, so this should match with _x = x
1598+ expect ( result ) . not . toBeNull ( ) ;
1599+ expect ( result ?. _x ?. toString ( ) ) . toBe ( 'x' ) ;
1600+ } ) ;
1601+
1602+ test ( '_x * sin(_x) with Multiply being commutative' , ( ) => {
1603+ const result = matchNonCanonical (
1604+ [ 'Multiply' , '_x' , [ 'Sin' , '_x' ] ] ,
1605+ [ 'Multiply' , [ 'Sin' , 'x' ] , 'x' ]
1606+ ) ;
1607+ expect ( result ) . not . toBeNull ( ) ;
1608+ expect ( result ?. _x ?. toString ( ) ) . toBe ( 'x' ) ;
1609+ } ) ;
1610+ } ) ;
1611+
1612+ describe ( 'Repeated wildcards with CANONICAL expressions' , ( ) => {
1613+ test ( '1/(x*ln(x)) with canonical expression' , ( ) => {
1614+ // Pattern is non-canonical (structural)
1615+ const pattern = ce . box ( [ 'Divide' , 1 , [ 'Multiply' , '_x' , [ 'Ln' , '_x' ] ] ] , {
1616+ canonical : false ,
1617+ } ) ;
1618+ // Expression is canonical (what you get from parsing)
1619+ const expr = ce . parse ( '\\frac{1}{x \\ln x}' ) ;
1620+
1621+ const result = expr . match ( pattern ) ;
1622+ expect ( result ) . not . toBeNull ( ) ;
1623+ expect ( result ?. _x ?. toString ( ) ) . toBe ( 'x' ) ;
1624+ } ) ;
1625+
1626+ test ( 'exp(x)*sin(x) canonical expression' , ( ) => {
1627+ // e^x becomes Power(ExponentialE, x), which doesn't match Exp(_x)
1628+ // So this pattern needs to account for the canonical form
1629+ const pattern = ce . box (
1630+ [ 'Multiply' , [ 'Power' , 'ExponentialE' , '_x' ] , [ 'Sin' , '_x' ] ] ,
1631+ { canonical : false }
1632+ ) ;
1633+ const expr = ce . parse ( 'e^x \\sin x' ) ;
1634+
1635+ const result = expr . match ( pattern ) ;
1636+ expect ( result ) . not . toBeNull ( ) ;
1637+ expect ( result ?. _x ?. toString ( ) ) . toBe ( 'x' ) ;
1638+ } ) ;
1639+
1640+ test ( 'Repeated wildcard fails when canonical order differs' , ( ) => {
1641+ // Multiply may reorder operands, so [x, ln(x)] might become [ln(x), x]
1642+ // The pattern matching should still work due to commutativity handling
1643+ const pattern = ce . box ( [ 'Multiply' , '_x' , [ 'Ln' , '_x' ] ] , {
1644+ canonical : false ,
1645+ } ) ;
1646+ const expr = ce . parse ( 'x \\ln x' ) ;
1647+
1648+ const result = expr . match ( pattern ) ;
1649+ expect ( result ) . not . toBeNull ( ) ;
1650+ expect ( result ?. _x ?. toString ( ) ) . toBe ( 'x' ) ;
1651+ } ) ;
1652+ } ) ;
1653+ } ) ;
0 commit comments