Skip to content

Commit

Permalink
wip: adding a new bib parser in lua
Browse files Browse the repository at this point in the history
  • Loading branch information
lervag committed Oct 22, 2023
1 parent e626412 commit 048d9c9
Show file tree
Hide file tree
Showing 3 changed files with 258 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .luarc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"runtime": {
"version": "LuaJIT"
},
"workspace": {
"library": [
"$VIMRUNTIME",
"${3rd}/luv/library"
],
"checkThirdParty": false
}
}
5 changes: 5 additions & 0 deletions .stylua.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
column_width = 80
indent_type = "Spaces"
indent_width = 2
quote_style = "AutoPreferDouble"
call_parentheses = "None"
240 changes: 240 additions & 0 deletions lua/vimtex/bibparser.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
local M = {}

local function empty(list)
if type(list) == "table" then
return next(list) == nil
end

if type(list) == "string" then
return list == ""
end

return false
end

local function format_line(title, object)
return "**" .. string.upper(title) .. "**" .. ": " .. object .. "\n"
end

local function format_entry(entry)
local info = ""
local _entry = {}
local prioritized_keys = { "title", "author" }

for _, key in pairs(prioritized_keys) do
if entry[key] ~= nil then
info = info .. format_line(key, entry[key])
end
end

local i = 0
for k, v in pairs(entry) do
if not vim.tbl_contains(prioritized_keys, k) then
i = i + 1
_entry[i] = format_line(k, v)
end
end
table.sort(_entry)

for _, a in ipairs(_entry) do
info = info .. a
end

return info
end

---@param head 0-index
local function get_key(body, head)
local matches = vim.fn.matchlist(body, [[^\v([-_:0-9a-zA-Z]+)\s*\=\s*]], head)
if empty(matches) then
return "", -1
end

return string.lower(matches[2]), head + vim.fn.strlen(matches[1])
end

local function parse_string(line, current, strings)
current.level = current.level
+ vim.fn["cmp_vimtex#count"](line, "{")
- vim.fn["cmp_vimtex#count"](line, "}")
if current.level > 0 then
current.body = current.body .. line
return
end

current.body = current.body .. vim.fn.matchstr(line, [[.*\ze}]])

local matches =
vim.fn.matchlist(current.body, [[\v^\s*(\w+)\s*\=\s*"(.*)"\s*$]])
if not empty(matches) and not empty(matches[2]) then
strings[matches[2]] = matches[3]
end
current = {}
end

local function parse_entry(line, current, entries)
current.level = current.level
+ vim.fn["cmp_vimtex#count"](line, "{")
- vim.fn["cmp_vimtex#count"](line, "}")
if current.level > 0 then
current.body = current.body .. line
return
end

current.body = current.body .. vim.fn.matchstr(line, [[.*\ze}]])

table.insert(entries, current)
current = {}
end

local function parse_type(file, lnum, line, current, strings, entries)
local matches = vim.fn.matchlist(line, [[\v^\@(\w+)\s*\{\s*(.*)]])
if empty(matches) then
return
end

local type = string.lower(matches[2])
local types = { preamble = 1, comment = 1 }
if types[type] ~= nil then
return
end

current.level = 1
current.body = ""
current.vimtex_file = file
current.vimtex_lnum = lnum

if type == "string" then
parse_string(matches[3], current, strings)
end

matches = vim.fn.matchlist(matches[3], [[\v^([^, ]*)\s*,\s*(.*)]])
current.type = type
current.key = matches[2]

if empty(matches[3]) then
return
end

parse_entry(matches[3], current, entries)
end

---@param head 0-index
local function get_value_string(body, head, strings)
local value
local head_1
if body:sub(head + 1, head + 1) == "{" then
local sum = 1
local i1 = head + 1
local i0 = i1

while sum > 0 do
local match
local res = vim.fn.matchstrpos(body, [=[[{}]]=], i1)
match, _, i1 = res[1], res[2], res[3]
res = nil

if i1 < 0 then
break
end

i0 = i1
sum = sum + (match == "{" and 1 or -1)
end

value = body:sub(head + 1 + 1, i0 - 2 + 1)
head_1 = vim.fn.matchend(body, [[^\s*]], i0)
elseif body:sub(head + 1, head + 1) == [["]] then
local index = vim.fn.match(body, [[\\\@<!"]], head + 1)
if index < 0 then
return "cmp_vimtex: get_value_string failed", ""
end

value = body:sub(head + 1 + 1, index - 1 + 1)
head_1 = vim.fn.matchend(body, [[^\s*]], index + 1)
return value, head_1
elseif vim.regex([[^\w]]):match_str(body:sub(head + 1)) then
value = vim.fn.matchstr(body, [[^\x\+]], head)
head_1 = vim.fn.matchend(body, [[^\s*]], head + vim.fn.strlen(value))
value = vim.fn.get(strings, value, [[@(]] .. value .. [[)]])
else
head_1 = head
end

if body:sub(head + 1, head + 1) == "#" then
head_1 = vim.fn.matchend(body, [[^\s*]], head_1 + 1)
local vadd
vadd, head_1 = get_value_string(body, head_1, strings)
value = value .. vadd
end

return value, vim.fn.matchend(body, [[^,\s*]], head_1)
end

local function get_value(body, head)
if vim.regex([[\d]]):match_str(body:sub(head + 1, head + 1)) then
local value = vim.fn.matchstr(body, [[^\d\+]], head)
local head_1 = vim.fn.matchend(body, [[^\s*,\s*]], head + vim.fn.len(value))
return value, head_1
end

return get_value_string(body, head)
end

local function parse_entry_body(entry)
entry.level = nil

local key = ""
local pos = vim.fn.matchend(entry.body, [[^\s*]])
while pos >= 0 do
if empty(key) then
key, pos = get_key(entry.body, pos)
else
local value
value, pos = get_value(entry.body, pos)
entry[key] = value
key = ""
end
end

entry.body = nil
return entry
end

M.parse = function(file)
if file == nil or not vim.fn.filereadable(file) then
return {}
end

local lines = vim.fn.readfile(file)
local entries = {}
local strings = {}
local current = {}

for lnum = 1, #lines do
local line = lines[lnum]

if empty(current) then
parse_type(lnum, line, file, current, strings, entries)
goto continue
end

if current.type == "string" then
parse_string(line)
else
parse_entry(line, current, entries)
end

::continue::
end

local result = {}
for _, v in pairs(entries) do
result[v.key] = parse_entry_body(v)
result[v.key].formatted_info = format_entry(result[v.key])
end

return result
end

return M

0 comments on commit 048d9c9

Please sign in to comment.