o
    ðT·jÅ  ã                   @   sT   d Z ddlZdgZe d¡Ze d¡Ze d¡ZdZdZG d	d
„ d
ƒZ	ddd„Z
dS )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                   @   sf   e Zd Zdd„ Zdd„ Zdd„ Zdd„ Zd	d
„ Zdd„ Zdd„ Z	dd„ Z
dd„ Zddd„Zdd„ ZdS )Ú_Parserc                 C   s    t |ƒ| _|  |¡| _d| _d S ©Nr   )ÚsetÚcolumnsÚ_scanÚtokensÚpos)ÚselfÚqueryr
   © r   úW/home/dinkstrade/pdmp-scanner/venv/lib/python3.10/site-packages/playhouse/fts_parser.pyÚ__init__4   s   

z_Parser.__init__c                    sÖ  g }d}|t |ƒk ræ|| }| ¡ r|d7 }q|dv r'| |d f¡ |d7 }q|dkrA||d |d …  ¡ rA| d¡ |d7 }qt ||¡}|d ur‡| dd¡\}}|rX|gn| ¡ }|r‡t‡ fdd„|D ƒƒr‡d	d
„ |D ƒ}	| d|ry|	d ndd 	|	¡ f¡ | 
¡ }qt ||¡}|d u r–|d7 }q| 
¡ }| ddd¡\}
}}|
d u rÆ|sÆ|tv rÆ|dkr¾|r¾|d dkr¾| ¡  | d|f¡ q|
d u rÌ|n|
}t |¡rà| dd||pÛdf f¡ |t |ƒk s
ˆ  |¡S )Nr   é   z()ú-é   )ÚnegNc                 3   s   � | ]}|ˆ j v V  qd S ©N)r
   ©Ú.0Ún©r   r   r   Ú	<genexpr>M   s   € z _Parser._scan.<locals>.<genexpr>c                 S   s   g | ]
}d |  dd¡ ‘qS )z"%s"ú"ú"")Úreplacer   r   r   r   Ú
<listcomp>O   s    z!_Parser._scan.<locals>.<listcomp>Úcolz{%s}ú ÚphraseÚwordÚstarr   éÿÿÿÿ)Úopr   r'   Útermz"%s"%sÚ )ÚlenÚisspaceÚappendÚstripÚ
COLSPEC_REÚmatchÚgroupÚsplitÚallÚjoinÚendÚTERM_REÚ	OPERATORSÚpopÚWORD_REÚsearchÚ_balance)r   r   r   r   Úcharr/   Únamer0   ÚnamesÚquotedr#   r$   r%   Útextr   r   r   r   9   sT    
ÿ
Û
&z_Parser._scanc                 C   s~   d }}g }|D ]/}|d dkr|t kr|d7 }q|d7 }n|d dkr2|r+|d8 }q|s.q|d8 }| |¡ q|dg|  S )Nr   ú(r   ú)©rA   N)Ú	MAX_DEPTHr,   )r   r   ÚdepthÚdroppedÚaccumÚtokenr   r   r   r:   d   s"   
z_Parser._balancec                 C   s$   | j t| jƒk r| j| j  d S d S r   )r   r*   r   r   r   r   r   Ú_peeky   s   $z_Parser._peekc                 C   s    |   ¡ dko| j| j d |kS )Nr'   r   )rH   r   r   )r   r'   r   r   r   Ú_is_op|   s    z_Parser._is_opc                 C   sl   |   ¡ g}|  d¡r|  jd7  _| |   ¡ ¡ |  d¡s
dd„ |D ƒ}|s(d S t|ƒdkr2|d S d|fS )Nr   r   c                 S   s   g | ]}|r|‘qS r   r   )r   Únoder   r   r   r    „   s    z%_Parser._parse_or.<locals>.<listcomp>r   Úor)Ú
_parse_andrI   r   r,   r*   )r   Únodesr   r   r   Ú	_parse_or   s   


þz_Parser._parse_orc                 C   sœ   g g }}	 |   ¡ }|dkr|  d¡rn|  jd7  _q|dv r!n|  ¡ \}}|r2|r-|n| |¡ q|s7d S t|ƒdkrA|d nd|f}|rLd||fS |S )	NTr'   r   r   rB   r   ÚandÚnot)rH   rI   r   Ú
_parse_notr,   r*   )r   ÚpositiveÚnegativerG   rJ   Únegater   r   r   rL   ‰   s$   

õz_Parser._parse_andc                 C   sV   |   ¡ \}}|  d¡r'|  jd7  _|   ¡ \}}|r"|r"d||gf}|  d¡s||fS )Nr   r   rP   )Ú_parse_atomrI   r   )r   rJ   rT   ÚrightÚ_r   r   r   rQ   œ   s   

üz_Parser._parse_notc                 C   sÜ   |   ¡ dk}|r|  jd7  _d }|   ¡ dkr&| j| j d }|  jd7  _|   ¡ }|dkr@d| j| j d f}|  jd7  _n|dkr]|  jd7  _|  ¡ }|   ¡ dkr\|  jd7  _ndS |rj|rjd||f|fS ||fS )	Nr   r   r!   r(   Útr@   rA   )NF)rH   r   r   rN   )r   rT   ÚcolspecrG   rJ   r   r   r   rU   ¥   s&   €z_Parser._parse_atomFc                    sØ   |d }|dkr|d S |dkr&d|d ˆ   |d d¡f }|r$d| S |S |d	kr9d
 ‡ fdd„|d D ƒ¡}n+|dkrLd ‡ fdd„|d D ƒ¡}ndˆ   |d d¡d ‡ fdd„|d D ƒ¡f }|rjd| S |S )Nr   rX   r   r!   z%s: %sr   Tz(%s)rO   z AND c                 3   ó   � | ]	}ˆ   |d ¡V  qdS ©TN©Ú_renderr   r   r   r   r   Ã   ó   € z"_Parser._render.<locals>.<genexpr>rK   z OR c                 3   rZ   r[   r\   r   r   r   r   r   Å   r^   z	%s NOT %sz NOT c                 3   rZ   r[   r\   r   r   r   r   r   É   r^   )r]   r3   )r   rJ   ÚnestedÚkindÚsqlr   r   r   r]   »   s   þz_Parser._renderc                 C   s†   g }| j t| jƒk r,| j }|  ¡ }|r| |¡ | j |kr$|  j d7  _ | j t| jƒk s
|s0dS t|ƒdkr:|d nd|f}|  |¡S )Nr   r   r   rO   )r   r*   r   rN   r,   r]   )r   rM   r   rJ   r   r   r   Ú	translateÌ   s   

ú
z_Parser.translateN)F)Ú__name__Ú
__module__Ú__qualname__r   r   r:   rH   rI   rN   rL   rQ   rU   r]   rb   r   r   r   r   r   3   s    +
	
r   c                 C   s   t | |pdƒ ¡ 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.
    r   )r   rb   )r   r
   r   r   r   r   Û   s   r   )Ú__doc__ÚreÚ__all__Úcompiler.   r5   r8   r6   rC   r   r   r   r   r   r   Ú<module>   s    $


 )