Skip to content

Commit

Permalink
feat: improve vimscript implementation of bib parser
Browse files Browse the repository at this point in the history
  • Loading branch information
lervag committed Oct 25, 2023
1 parent 76b3148 commit 830da25
Showing 1 changed file with 74 additions and 82 deletions.
156 changes: 74 additions & 82 deletions autoload/vimtex/parser/bib.vim
Original file line number Diff line number Diff line change
Expand Up @@ -270,117 +270,111 @@ function! s:parse_with_vim(file) abort " {{{1
return []
endif

let l:current = {}
let l:items = []
let l:strings = {}
let l:entries = []

let l:item = {}
let l:lnum = 0
for l:line in readfile(a:file)
let l:lnum += 1

if empty(l:current)
if s:parse_type(a:file, l:lnum, l:line, l:current, l:strings, l:entries)
let l:current = {}
endif
continue
endif
let l:item = empty(l:item)
\ ? s:parse_head(a:file, l:lnum, l:line)
\ : s:parse_tail(l:item, l:line)

if l:current.type ==# 'string'
if s:parse_string(l:line, l:current, l:strings)
let l:current = {}
endif
else
if s:parse_entry(l:line, l:current, l:entries)
let l:current = {}
if has_key(l:item, 'parsed')
if l:item.type == "string"
let [l:key, l:value] = s:parse_string(l:item.body)
if !empty(l:key)
let l:strings[l:key] = l:value
endif
else
call add(l:items, l:item)
endif
let l:item = {}
endif
endfor

return map(l:entries, 's:parse_entry_body(v:val, l:strings)')
return map(l:items, 's:parse_item(v:val, l:strings)')
endfunction

" }}}1

function! s:parse_type(file, lnum, line, current, strings, entries) abort " {{{1
function! s:parse_head(file, lnum, line) abort " {{{1
let l:matches = matchlist(a:line, '\v^\@(\w+)\s*\{\s*(.*)')
if empty(l:matches) | return 0 | endif
if empty(l:matches) | return {} | endif

let l:type = tolower(l:matches[1])
if index(['preamble', 'comment'], l:type) >= 0 | return 0 | endif

let a:current.level = 1
let a:current.body = ''
let a:current.source_file = a:file
let a:current.source_lnum = a:lnum
if l:type == 'preamble' || l:type == 'comment' | return {} | endif

return s:parse_tail({
\ 'level': 1,
\ 'body': '',
\ 'source_file': a:file,
\ 'source_lnum': a:lnum,
\ 'type': l:type,
\}, l:matches[2])
endfunction

if l:type ==# 'string'
return s:parse_string(l:matches[2], a:current, a:strings)
" }}}1
function! s:parse_tail(item, line) abort " {{{1
let a:item.level += s:count(a:line, '{') - s:count(a:line, '}')
if a:item.level > 0
let a:item.body .= a:line
else
let l:matches = matchlist(l:matches[2], '\v^([^, ]*)\s*,\s*(.*)')
let a:current.type = l:type
let a:current.key = l:matches[1]

return empty(l:matches[2])
\ ? 0
\ : s:parse_entry(l:matches[2], a:current, a:entries)
let a:item.body .= matchstr(a:line, '.*\ze}')
let a:item.parsed = v:true
endif

return a:item
endfunction

" }}}1
function! s:parse_string(line, string, strings) abort " {{{1
let a:string.level += s:count(a:line, '{') - s:count(a:line, '}')
if a:string.level > 0
let a:string.body .= a:line
return 0
function! s:parse_string(raw_string) abort " {{{1
let l:matches = matchlist(a:raw_string, '\v^\s*(\S+)\s*\=\s*"(.*)"\s*$')
if !empty(l:matches) && !empty(l:matches[1])
return [l:matches[1], l:matches[2]]
endif

let a:string.body .= matchstr(a:line, '.*\ze}')

let l:matches = matchlist(a:string.body, '\v^\s*(\w+)\s*\=\s*"(.*)"\s*$')
let l:matches = matchlist(a:raw_string, '\v^\s*(\S+)\s*\=\s*\{(.*)\}\s*$')
if !empty(l:matches) && !empty(l:matches[1])
let a:strings[l:matches[1]] = l:matches[2]
return [l:matches[1], l:matches[2]]
endif

return 1
return ['', '']
endfunction

" }}}1
function! s:parse_entry(line, entry, entries) abort " {{{1
let a:entry.level += s:count(a:line, '{') - s:count(a:line, '}')
if a:entry.level > 0
let a:entry.body .= a:line
return 0
endif

let a:entry.body .= matchstr(a:line, '.*\ze}')
function! s:parse_item(item, strings) abort " {{{1
let l:parts = matchlist(a:item.body, '\v^([^, ]*)\s*,\s*(.*)')

call add(a:entries, a:entry)
return 1
endfunction

" }}}1
let a:item.key = l:parts[1]
if empty(a:item.key) | return {} | endif

function! s:parse_entry_body(entry, strings) abort " {{{1
unlet a:entry.level
unlet a:item.level
unlet a:item.body
unlet a:item.parsed

let l:key = ''
let l:pos = matchend(a:entry.body, '^\s*')
while l:pos >= 0
if empty(l:key)
let [l:key, l:pos] = s:get_key(a:entry.body, l:pos)
let l:body = l:parts[2]
let l:tag = ''
let l:head = 0
while l:head >= 0
if empty(l:tag)
let [l:tag, l:head] = s:get_tag_name(l:body, l:head)
else
let [l:value, l:pos] = s:get_value(a:entry.body, l:pos, a:strings)
let a:entry[l:key] = l:value
let l:key = ''
let [l:value, l:head] = s:get_tag_value(l:body, l:head, a:strings)
let a:item[l:tag] = l:value
let l:tag = ''
endif
endwhile

unlet a:entry.body
return a:entry
return a:item
endfunction

" }}}1
function! s:get_key(body, head) abort " {{{1
" Parse the key part of a bib entry tag.
function! s:get_tag_name(body, head) abort " {{{1
" Parse the name part of a bib entry tag.
" Assumption: a:body is left trimmed and either empty or starts with a key.
" Returns: The key and the remaining part of the entry body.

Expand All @@ -391,7 +385,7 @@ function! s:get_key(body, head) abort " {{{1
endfunction

" }}}1
function! s:get_value(body, head, strings) abort " {{{1
function! s:get_tag_value(body, head, strings) abort " {{{1
" Parse the value part of a bib entry tag, until separating comma or end.
" Assumption: a:body is left trimmed and either empty or starts with a value.
" Returns: The value and the remaining part of the entry body.
Expand All @@ -405,15 +399,16 @@ function! s:get_value(body, head, strings) abort " {{{1
let l:value = matchstr(a:body, '^\d\+', a:head)
let l:head = matchend(a:body, '^\s*,\s*', a:head + len(l:value))
return [l:value, l:head]
else
return s:get_value_string(a:body, a:head, a:strings)
endif

return ['s:get_value failed', -1]
return s:get_tag_value_concat(a:body, a:head, a:strings, "")
endfunction

" }}}1
function! s:get_value_string(body, head, strings) abort " {{{1
function! s:get_tag_value_concat(body, head, strings, pre_value) abort " {{{1
let l:value = ""
let l:head = a:head

if a:body[a:head] ==# '{'
let l:sum = 1
let l:i1 = a:head + 1
Expand All @@ -432,27 +427,24 @@ function! s:get_value_string(body, head, strings) abort " {{{1
elseif a:body[a:head] ==# '"'
let l:index = match(a:body, '\\\@<!"', a:head+1)
if l:index < 0
return ['s:get_value_string failed', -1]
return ['s:get_tag_value_concat failed', -1]
endif

let l:value = a:body[a:head+1:l:index-1]
let l:head = matchend(a:body, '^\s*', l:index+1)
return [l:value, l:head]
elseif a:body[a:head:] =~# '^\w'
let l:value = matchstr(a:body, '^\w\+', a:head)
let l:value = matchstr(a:body, '^\w[0-9a-zA-Z_-]*', a:head)
let l:head = matchend(a:body, '^\s*', a:head + strlen(l:value))
let l:value = get(a:strings, l:value, '@(' . l:value . ')')
else
let l:head = a:head
endif

if a:body[l:head] ==# '#'
let l:head = matchend(a:body, '^\s*', l:head + 1)
let [l:vadd, l:head] = s:get_value_string(a:body, l:head, a:strings)
let l:value .= l:vadd
return s:get_tag_value_concat(
\ a:body, l:head, a:strings, a:pre_value . l:value)
endif

return [l:value, matchend(a:body, '^,\s*', l:head)]
return [a:pre_value . l:value, matchend(a:body, '^,\s*', l:head)]
endfunction

" }}}1
Expand Down

0 comments on commit 830da25

Please sign in to comment.