
    B>qj                         d Z ddlZdgZ ej                  d      Z ej                  d      Z ej                  d      ZdZdZ G d	 d
      Z	ddZ
y)ao  
Translate web-search query syntax into a SQLite FTS5 query.

Search-box grammar:

    query    := or
    or       := and (OR and)*
    and      := unary+                    # juxtaposition means AND
    unary    := ['-'] atom                # '-' excludes matching documents
    atom     := [colspec ':'] phrase
              | [colspec ':'] '(' query ')'
    colspec  := colname | '{' colname ... '}'
    phrase   := '"' text '"' ['*'] | word ['*']

Translation to FTS5:

    phrase        -> "escaped text"[*]
    colspec: x    -> colspec: x       (x parenthesized when compound)
    and(a, b)     -> a AND b          (fts5 implicit AND is phrase-only)
    or(a, b)      -> a OR b
    -x            -> hoisted to the enclosing AND: (rest) NOT x
    ( query )     -> preserved

Anything that does not fit the grammar is treated as literal text, so the
translation always produces a valid query. Unbalanced parentheses and quotes
are repaired, operators with nothing to operate on are dropped, and nesting
deeper than MAX_DEPTH is flattened.

An exclusion only applies to the terms it is AND-ed with, as fts5 has no way
to express it otherwise. In "a OR -b" the exclusion has nothing to apply to
and is dropped, leaving "a".

Note fts5 reads "-colname:" as "every column except colname" and rejects a
bare "-phrase". Web-search "-" excludes the document, so it is always
translated to NOT.
    Nparsez(?:(\w+)|\{([^}]*)\})\s*:zG(?:"(?P<phrase>[^"]*)"?|(?P<word>[^\s"()]+?))(?P<star>\*)?(?=[\s"()]|$)z\w)ANDORNOT    c                   P    e Zd Zd Zd Zd Zd Zd Zd Zd Z	d Z
d	 Zdd
Zd Zy)_Parserc                 ^    t        |      | _        | j                  |      | _        d| _        y Nr   )setcolumns_scantokenspos)selfqueryr   s      S/opt/rentech/trading_bot/.venv/lib/python3.12/site-packages/playhouse/fts_parser.py__init__z_Parser.__init__4   s$    7|jj'    c                 <    g }d}|t        |      k  r||   }|j                         r|dz  }*|dv r|j                  |d f       |dz  }G|dk(  r0||dz   |dz    j                         r|j                  d       |dz  }|t        j                  ||      }||j                  dd      \  }}|r|gn|j                         }|rtt         fd|D              r`|D 	cg c]  }	d|	j                  d	d
      z   }
}	|j                  d|r|
d   nddj                  |
      z  f       |j                         }4t        j                  ||      }||dz  }S|j                         }|j                  ddd      \  }}}|>|s<|t        v r4|dk(  r|r|d   dk(  r|j                          |j                  d|f       ||n|}t        j!                  |      r|j                  dd	|d	|xs df       |t        |      k  r j#                  |      S c c}	w )Nr      z()-   )negNc              3   :   K   | ]  }|j                   v   y wN)r   .0nr   s     r   	<genexpr>z _Parser._scan.<locals>.<genexpr>M   s      Bqdll!2 Bs   z"%s""""colz{%s} phrasewordstarr   )opr   r)   term )lenisspaceappendstrip
COLSPEC_REmatchgroupsplitallreplacejoinendTERM_RE	OPERATORSpopWORD_REsearch_balance)r   r   r   r   charr1   namer2   namesr   quotedr%   r&   r'   texts   `              r   r   z_Parser._scan9   s   CJ:D||~qt|tTl+qs{uS1WS1W5;;=m,q$$UC0E #kk!Q/e"&EKKMS BE BBEJKfqyyd';;KFKMM5t&)#)CHHV,<#<#> ?))+CMM%-E}q))+C!&Xvv!FFD$~dty/@5=Vr
m0KJJLtTl+!>4vD~~d#v4'DEFK CJL }}V$$' Ls   Hc                     dx}}g }|D ]H  }|d   dk(  r|t         k(  r|dz  }|dz  }n|d   dk(  r|r|dz  }0|s3|dz  }|j                  |       J |dg|z  z   S )Nr   (r   )rE   N)	MAX_DEPTHr.   )r   r   depthdroppedaccumtokens         r   r=   z_Parser._balanced   s      	 EQx3I%qLG
qSqLG
LL	  }u,,,r   c                     | j                   t        | j                        k  r| j                  | j                      d   S d S r   )r   r,   r   )r   s    r   _peekz_Parser._peeky   s3    +/88c$++6F+Ft{{488$Q'PDPr   c                 j    | j                         dk(  xr | j                  | j                     d   |k(  S )Nr)   r   )rM   r   r   )r   r)   s     r   _is_opz_Parser._is_op|   s/    

$GTXX)>q)AR)GHr   c                 4   | j                         g}| j                  d      rF| xj                  dz  c_        |j                  | j                                | j                  d      rF|D cg c]  }|s|	 }}|sy t	        |      dk(  r|d   S d|fS c c}w )Nr   r   r   or)
_parse_andrO   r   r.   r,   )r   nodesnodes      r   	_parse_orz_Parser._parse_or   s    "#kk$HHMHLL*+ kk$ #(0$400u:?uQx=u= 1s   -B5Bc                 ,   g g }}	 | j                         }|dk(  r(| j                  d      rnF| xj                  dz  c_        >|dv rn+| j                         \  }}|r|r|n|j	                  |       m|sy t        |      dk(  r|d   nd|f}|rd||fS |S )Nr)   r   r   rF   r   andnot)rM   rO   r   
_parse_notr.   r,   )r   positivenegativerK   rT   negates         r   rR   z_Parser._parse_and   s    (JJLE};;t$A#??,LD&#99$?  !(mq0x{uh6G*2tX&<<r   c                     | j                         \  }}| j                  d      rD| xj                  dz  c_        | j                         \  }}|r|rd||gf}| j                  d      rD||fS )Nr   r   rX   )_parse_atomrO   r   )r   rT   r\   right_s        r   rY   z_Parser._parse_not   sl    '')fkk% HHMH'')HE1teW-	 kk% 
 V|r   c                 :   | j                         dk(  }|r| xj                  dz  c_        d }| j                         dk(  r1| j                  | j                     d   }| xj                  dz  c_        | j                         }|dk(  r4d| j                  | j                     d   f}| xj                  dz  c_        nT|dk(  rN| xj                  dz  c_        | j                         }| j                         dk(  r| xj                  dz  c_        ny|r	|rd||f|fS ||fS )	Nr   r   r#   r*   trD   rE   )NF)rM   r   r   rU   )r   r\   colspecrK   rT   s        r   r^   z_Parser._parse_atom   s    &HHMH::<5 kk$((+A.GHHMH

F?TXX.q12DHHMHc\HHMH>>#Dzz|s"A *.7&vMMvMMr   c                     |d   }|dk(  r|d   S |dk(  r&|d   d j                  |d   d      }|rd|z  S |S |d	k(  rd
j                   fd|d   D              }nX|dk(  rdj                   fd|d   D              }n5 j                  |d   d      ddj                   fd|d   D              }|rd|z  S |S )Nr   rb   r   r#   z: r   Tz(%s)rW   z AND c              3   B   K   | ]  }j                  |d         ywTN_renderr   s     r   r    z"_Parser._render.<locals>.<genexpr>   s     Ft||At4F   rQ   z OR c              3   B   K   | ]  }j                  |d         ywrf   rg   r   s     r   r    z"_Parser._render.<locals>.<genexpr>   s     Edll1d3Eri   z NOT c              3   B   K   | ]  }j                  |d         ywrf   rg   r   s     r   r    z"_Parser._render.<locals>.<genexpr>   s     DqT\\!T2Dri   )rh   r6   )r   rT   nestedkindsqls   `    r   rh   z_Parser._render   s    Aw3;7N5="1gt||DGT'BCC#)6C<2s25=,,Fd1gFFCT\++ET!WEEC T!Wd+DDGDDFC  &v|.3.r   c                    g }| j                   t        | j                        k  rv| j                   }| j                         }|r|j	                  |       | j                   |k(  r| xj                   dz  c_         | j                   t        | j                        k  rv|syt        |      dk(  r|d   nd|f}| j                  |      S )Nr   r"   r   rW   )r   r,   r   rU   r.   rh   )r   rS   r   rT   s       r   	translatez_Parser.translate   s    hhT[[))((C>>#DT"xx3A hhT[[)) u:?uQx||D!!r   N)F)__name__
__module____qualname__r   r   r=   rM   rO   rU   rR   rY   r^   rh   rp    r   r   r	   r	   3   s=    
)%V-*QI>=&N,/""r   r	   c                 >    t        | |xs d      j                         S )a#  
    Translate a search-box query into an FTS5 query. Column filters are only
    honored for the given column names, so unknown ones become search terms.

    A query with no searchable terms in it, including an empty string,
    translates to '""', which is valid and matches no rows.
    rt   )r	   rp   )r   r   s     r   r   r      s     5'-R(2244r   r   )__doc__re__all__compiler0   r8   r;   r9   rG   r	   r   rt   r   r   <module>rz      sm   #H 
 )RZZ45

"** & ' "**U
 		e" e"P5r   