mirror of
https://github.com/inkarkat/vim-ingo-library.git
synced 2026-10-17 02:49:02 +02:00
182 lines
6.4 KiB
VimL
182 lines
6.4 KiB
VimL
" ingo/regexp/split.vim: Functions to split a regular expression.
|
|
"
|
|
" DEPENDENCIES:
|
|
" - ingo/collections.vim autoload script
|
|
" - ingo/regexp/length.vim autoload script
|
|
" - ingo/regexp/magic.vim autoload script
|
|
"
|
|
" Copyright: (C) 2017-2018 Ingo Karkat
|
|
" The VIM LICENSE applies to this script; see ':help copyright'.
|
|
"
|
|
" Maintainer: Ingo Karkat <ingo@karkat.de>
|
|
|
|
function! ingo#regexp#split#TopLevelBranches( pattern )
|
|
"******************************************************************************
|
|
"* PURPOSE:
|
|
" Split a:pattern on "\|" - separated branches, keeping nested \(...\|...\)
|
|
" branches inside (non-)capture groups together. If the complete a:pattern is
|
|
" wrapped in a group, it is treated as one toplevel branch, too.
|
|
"* ASSUMPTIONS / PRECONDITIONS:
|
|
" Does not consider "very magic" (/\v)-style syntax (...|...). If you may have
|
|
" this, convert via ingo#regexp#magic#Normalize() first.
|
|
"* EFFECTS / POSTCONDITIONS:
|
|
" None.
|
|
"* INPUTS:
|
|
" a:pattern regular expression
|
|
"* RETURN VALUES:
|
|
" List of regular expression branch fragments.
|
|
"******************************************************************************
|
|
let l:rawBranches = split(a:pattern, '\%(\%(^\|[^\\]\)\%(\\\\\)*\\\)\@<!\\|', 1)
|
|
let l:openGroupCnt = 0
|
|
let l:branches = []
|
|
|
|
let l:currentBranch = ''
|
|
while ! empty(l:rawBranches)
|
|
let l:currentBranch = remove(l:rawBranches, 0)
|
|
let l:currentOpenGroupCnt = l:openGroupCnt
|
|
|
|
let l:count = 1
|
|
while 1
|
|
let l:match = matchstr(l:currentBranch, '\%(\%(^\|[^\\]\)\%(\\\\\)*\\\)\@<!\\\%(%\?(\|)\)', 0, l:count)
|
|
if empty(l:match)
|
|
break
|
|
elseif l:match == '\)'
|
|
let l:openGroupCnt = max([0, l:openGroupCnt - 1])
|
|
else
|
|
let l:openGroupCnt += 1
|
|
endif
|
|
let l:count += 1
|
|
endwhile
|
|
|
|
if l:currentOpenGroupCnt == 0
|
|
call add(l:branches, l:currentBranch)
|
|
else
|
|
if empty(l:branches)
|
|
let l:branches = ['']
|
|
endif
|
|
let l:branches[-1] .= '\|' . l:currentBranch
|
|
endif
|
|
endwhile
|
|
|
|
return l:branches
|
|
endfunction
|
|
|
|
function! ingo#regexp#split#PrefixGroupsSuffix( pattern )
|
|
"******************************************************************************
|
|
"* PURPOSE:
|
|
" Split a:pattern into a \(...\) group (capture or non-capture), and any
|
|
" preceding / trailing regular expression parts.
|
|
"* ASSUMPTIONS / PRECONDITIONS:
|
|
" Does not consider "very magic" (/\v)-style syntax (...|...). If you may have
|
|
" this, convert via ingo#regexp#magic#Normalize() first.
|
|
"* EFFECTS / POSTCONDITIONS:
|
|
" None.
|
|
"* INPUTS:
|
|
" a:pattern regular expression
|
|
"* RETURN VALUES:
|
|
" List of [prefix, group1, [infix, group2, [...]] suffix], or [a:pattern] if
|
|
" there's no toplevel group at all.
|
|
" Throws 'PrefixGroupsSuffix: Unmatched \(' or
|
|
" 'PrefixGroupsSuffix: Unmatched \)' if a:pattern is invalid.
|
|
"******************************************************************************
|
|
let l:pattern = a:pattern
|
|
let l:result = []
|
|
let l:accu = ''
|
|
let l:openGroupCnt = 0
|
|
while 1
|
|
let l:parse = matchlist(l:pattern, '^\(.\{-}\)\(\%(\%(^\|[^\\]\)\%(\\\\\)*\\\)\@<!\\\%(%\?(\|)\)\)\(.*\)$')
|
|
if empty(l:parse)
|
|
" No more open / close parentheses.
|
|
call add(l:result, l:pattern)
|
|
break
|
|
endif
|
|
let [l:prefix, l:paren, l:pattern] = l:parse[1:3]
|
|
|
|
let l:isOpen = (l:paren !=# '\)')
|
|
let l:openGroupCnt += (l:isOpen ? 1 : -1)
|
|
if l:openGroupCnt < 0
|
|
throw 'PrefixGroupsSuffix: Unmatched \)'
|
|
elseif l:isOpen && l:openGroupCnt == 1
|
|
call add(l:result, l:prefix)
|
|
elseif ! l:isOpen && l:openGroupCnt == 0
|
|
call add(l:result, l:accu . l:prefix)
|
|
let l:accu = ''
|
|
else
|
|
let l:accu .= l:prefix . l:paren
|
|
endif
|
|
endwhile
|
|
if l:openGroupCnt != 0
|
|
throw 'PrefixGroupsSuffix: Unmatched \('
|
|
endif
|
|
|
|
return l:result
|
|
endfunction
|
|
|
|
function! ingo#regexp#split#AddPatternByProjectedMatchLength( branches, pattern )
|
|
"******************************************************************************
|
|
"* PURPOSE:
|
|
" Add a:pattern to the List of regexp a:branches, in a position so that
|
|
" shorter earlier branches do not eclipse a following longer match.
|
|
"* ASSUMPTIONS / PRECONDITIONS:
|
|
" Does not consider "very magic" (/\v)-style syntax, in neither a:branches nor
|
|
" a:pattern. If you may have this, convert via ingo#regexp#magic#Normalize()
|
|
" first.
|
|
"* EFFECTS / POSTCONDITIONS:
|
|
" None.
|
|
"* INPUTS:
|
|
" a:branches List of regular expression branches (e.g. split via
|
|
" ingo#regexp#split#TopLevelBranches()).
|
|
" a:pattern Regular expression to be added at the appropriate position in
|
|
" a:branches, depending on the projected length of the matches.
|
|
" Longer matches will come first, so that a shorter earlier match
|
|
" does not eclipse a following longer one.
|
|
"* RETURN VALUES:
|
|
" Modified a:branches List.
|
|
"******************************************************************************
|
|
try
|
|
let l:projectedPatternMinLength = ingo#regexp#length#Project(a:pattern)[0]
|
|
catch /^PrefixGroupsSuffix:/
|
|
let l:projectedPatternMinLength = 0
|
|
endtry
|
|
|
|
let l:i = 0
|
|
while l:i < len(a:branches)
|
|
try
|
|
let [l:min, l:max] = ingo#regexp#length#Project(a:branches[l:i])
|
|
let l:compare = (l:max < 0x7FFFFFFF ? l:max : l:min)
|
|
if l:compare < l:projectedPatternMinLength
|
|
break
|
|
endif
|
|
catch /^PrefixGroupsSuffix:/
|
|
" Skip invalid existing branch.
|
|
endtry
|
|
|
|
let l:i += 1
|
|
endwhile
|
|
return insert(a:branches, a:pattern, l:i)
|
|
endfunction
|
|
|
|
function! ingo#regexp#split#GlobalFlags( pattern )
|
|
"******************************************************************************
|
|
"* PURPOSE:
|
|
" Split global regular expression engine flags from a:pattern. These control
|
|
" case sensitivity (/\c, /\C) and engine type (/\%#=0).
|
|
"* ASSUMPTIONS / PRECONDITIONS:
|
|
" None.
|
|
"* EFFECTS / POSTCONDITIONS:
|
|
" None.
|
|
"* INPUTS:
|
|
" a:pattern regular expression
|
|
"* RETURN VALUES:
|
|
" [engineTypeFlag, caseSensitivityFlag, purePattern]
|
|
"******************************************************************************
|
|
let [l:fragments, l:caseFlags] = ingo#collections#SeparateItemsAndSeparators(a:pattern, '\%(\%(^\|[^\\]\)\%(\\\\\)*\\\)\@<!\\[cC]')
|
|
let [l:fragments, l:engineFlags] = ingo#collections#SeparateItemsAndSeparators(join(l:fragments, ''), '\%(\%(^\|[^\\]\)\%(\\\\\)*\\\)\@<!\\%#=[012]')
|
|
let l:purePattern = join(l:fragments, '')
|
|
|
|
let l:caseSensitivityFlag = (index(l:caseFlags, '\c') == -1 ? get(l:caseFlags, 0, '') : '\c')
|
|
return [get(l:engineFlags, 0, ''), l:caseSensitivityFlag, l:purePattern]
|
|
endfunction
|
|
|
|
" vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax :
|