summaryrefslogtreecommitdiffstats
path: root/after/ftplugin/llvm.vim
diff options
context:
space:
mode:
Diffstat (limited to 'after/ftplugin/llvm.vim')
-rw-r--r--after/ftplugin/llvm.vim477
1 files changed, 477 insertions, 0 deletions
diff --git a/after/ftplugin/llvm.vim b/after/ftplugin/llvm.vim
new file mode 100644
index 00000000..70f196c8
--- /dev/null
+++ b/after/ftplugin/llvm.vim
@@ -0,0 +1,477 @@
+if exists('g:polyglot_disabled') && index(g:polyglot_disabled, 'llvm') != -1
+ finish
+endif
+
+" Copyright (c) 2018 rhysd
+"
+" Permission is hereby granted, free of charge, to any person obtaining a copy
+" of this software and associated documentation files (the "Software"), to deal
+" in the Software without restriction, including without limitation the rights
+" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+" copies of the Software, and to permit persons to whom the Software is
+" furnished to do so, subject to the following conditions:
+"
+" The above copyright notice and this permission notice shall be included in
+" all copies or substantial portions of the Software.
+"
+" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+" AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+" LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+" OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+" THE SOFTWARE.
+
+if get(g:, 'llvm_extends_official', 1) == 0
+ finish
+endif
+
+let g:llvm_ext_no_mapping = get(g:, 'llvm_ext_no_mapping', 0)
+let g:llvm_ext_lli_executable = get(g:, 'llvm_ext_lli_executable', 'lli')
+
+let s:KIND_BLOCK_PREC = 0
+let s:KIND_BLOCK_FOLLOW = 1
+let s:KIND_FUNC_BEGIN = 2
+let s:KIND_FUNC_END = 3
+
+function! s:section_delim_at(lnum) abort
+ let line = getline(a:lnum)
+ let m = matchlist(line, '^\([^:]\+\):\%( \+; preds = \(%.\+\)\)\=$')
+ if !empty(m)
+ if m[2] ==# ''
+ return [s:KIND_BLOCK_PREC, m[1]]
+ else
+ return [s:KIND_BLOCK_FOLLOW, m[1], split(m[2], ',\s*')]
+ endif
+ endif
+ if line =~# '^}$'
+ return [s:KIND_FUNC_END]
+ endif
+ if line =~# '^define\>'
+ return [s:KIND_FUNC_BEGIN]
+ endif
+ return []
+endfunction
+
+function! s:is_section_delim(line, func_delim) abort
+ let sec = s:section_delim_at(a:line)
+ if empty(sec)
+ return 0
+ endif
+ let kind = sec[0]
+ return kind == s:KIND_BLOCK_PREC || kind == s:KIND_BLOCK_FOLLOW || kind == func_delim
+endfunction
+
+function! s:next_section(stop_func_begin) abort
+ let func_delim = a:stop_func_begin ? s:KIND_FUNC_BEGIN : s:KIND_FUNC_END
+ let last = line('$') - 1
+ let line = line('.')
+ while line < last
+ let line += 1
+ if s:is_section_delim(line, func_delim)
+ call cursor(line, col('.'))
+ return
+ endif
+ endwhile
+endfunction
+
+function! s:prev_section(stop_func_begin) abort
+ let func_delim = a:stop_func_begin ? s:KIND_FUNC_BEGIN : s:KIND_FUNC_END
+ let line = line('.')
+ while line > 1
+ let line -= 1
+ if s:is_section_delim(line, func_delim)
+ call cursor(line, col('.'))
+ return
+ endif
+ endwhile
+endfunction
+
+if !g:llvm_ext_no_mapping
+ nnoremap <buffer><silent>]] :<C-u>call <SID>next_section(1)<CR>
+ nnoremap <buffer><silent>[[ :<C-u>call <SID>prev_section(1)<CR>
+ nnoremap <buffer><silent>][ :<C-u>call <SID>next_section(0)<CR>
+ nnoremap <buffer><silent>[] :<C-u>call <SID>prev_section(0)<CR>
+endif
+
+function! s:function_range_at(linum) abort
+ let line = a:linum
+ while line >= 1
+ let s = getline(line)
+ if s =~# '^define\>'
+ let start = line
+ break
+ elseif s =~# '^}$'
+ return []
+ endif
+ let line -= 1
+ endwhile
+ if line < 1
+ return []
+ endif
+
+ let line = a:linum
+ let last = line('$')
+ while line <= last
+ let s = getline(line)
+ if s =~# '^}$'
+ let end = line
+ break
+ elseif s =~# '^define\>'
+ return []
+ endif
+ let line += 1
+ endwhile
+ if line > last
+ return []
+ endif
+
+ return [start, end]
+endfunction
+
+function! s:blocks_graph_at(linum) abort
+ let func_range = s:function_range_at(a:linum)
+ if empty(func_range)
+ return {}
+ endif
+ let line = func_range[0] + 1
+ let last = func_range[1] - 1
+ let graph = {}
+ while line <= last
+ let block = s:section_delim_at(line)
+ if empty(block)
+ let line += 1
+ continue
+ endif
+ let block_name = '%' . block[1]
+ if block[0] == s:KIND_BLOCK_PREC
+ let graph[block_name] = {'line': line, 'follows': [], 'preds': []}
+ elseif block[0] == s:KIND_BLOCK_FOLLOW
+ let graph[block_name] = {'line': line, 'follows': [], 'preds': block[2]}
+ for follow in block[2]
+ call add(graph[follow].follows, block_name)
+ endfor
+ else
+ echoerr 'unreachable'
+ endif
+ let line += 1
+ endwhile
+ return graph
+endfunction
+
+function! s:find_pred_block(linum) abort
+ let sec = s:section_delim_at(a:linum)
+ if empty(sec) || sec[0] != s:KIND_BLOCK_PREC && sec[0] != s:KIND_BLOCK_FOLLOW
+ throw 'No block is starting at line ' . a:linum
+ endif
+ if sec[0] != s:KIND_BLOCK_FOLLOW
+ throw printf("Block '%s' has no pred block", sec[1])
+ endif
+ let block_name = '%' . sec[1]
+ let pred_block = sec[2][0]
+
+ let graph = s:blocks_graph_at(a:linum)
+ if empty(graph)
+ throw 'No block is found in function at line ' . a:linum
+ endif
+
+ if !has_key(graph, pred_block)
+ throw printf("Block '%s' (pred block of '%s') not found in function", pred_block, block_name)
+ endif
+ return graph[pred_block]
+endfunction
+
+function! s:move_to_pred_block() abort
+ try
+ let b = s:find_pred_block(line('.'))
+ call cursor(b.line, col('.'))
+ catch
+ echohl ErrorMsg | echom v:exception | echohl None
+ endtry
+endfunction
+
+function! s:find_following_block(linum) abort
+ let sec = s:section_delim_at(a:linum)
+ if empty(sec) || sec[0] != s:KIND_BLOCK_PREC && sec[0] != s:KIND_BLOCK_FOLLOW
+ throw 'No block is starting at line ' . a:linum
+ endif
+ let block_name = '%' . sec[1]
+
+ let graph = s:blocks_graph_at(a:linum)
+ if empty(graph)
+ throw 'No block is found in function at line ' . a:linum
+ endif
+
+ let follows = graph[block_name].follows
+ if empty(follows)
+ throw printf("Block '%s' has no following block", block_name)
+ endif
+
+ echom printf("Block '%s' has %d following blocks: %s", block_name, len(follows), join(follows, ', '))
+
+ if !has_key(graph, follows[0])
+ throw printf("Block '%s' is not defined in function at line %d", follows[0], a:linum)
+ endif
+ return graph[follows[0]]
+endfunction
+
+function! s:move_to_following_block() abort
+ try
+ let b = s:find_following_block(line('.'))
+ call cursor(b.line, col('.'))
+ catch
+ echohl ErrorMsg | echom v:exception | echohl None
+ endtry
+endfunction
+
+if !g:llvm_ext_no_mapping
+ nnoremap <buffer><silent>[b :<C-u>call <SID>move_to_pred_block()<CR>
+ nnoremap <buffer><silent>]b :<C-u>call <SID>move_to_following_block()<CR>
+endif
+
+function! s:get_func_identifiers(line) abort
+ let idx = stridx(a:line, '@')
+ if idx == -1
+ " Invalid signature
+ return []
+ endif
+
+ " e.g. define internal i32 @foo(...) { -> @foo(...) {
+ let sig = a:line[idx:]
+
+ let idx = stridx(sig, '(')
+ if idx == -1
+ " Invalid signature
+ return []
+ endif
+
+ " @foo(...) { -> @foo
+ let idents = [sig[:idx-1]]
+
+ " @foo(...) { -> ...) {
+ let params = sig[idx+1:]
+
+ let idx = strridx(sig, ')')
+ if idx == -1
+ return idents
+ endif
+
+ " ...) { -> ...
+ let params = params[:idx-1]
+
+ " Gather parameters in function signature
+ while params !=# ''
+ let m = matchlist(params, '^[^%]*\(%\%("[^"]\+"\|[[:alnum:]_.]\+\)\)\s*\(.*\)$')
+ if empty(m)
+ break
+ endif
+ let idents += [m[1]]
+ let params = m[2]
+ endwhile
+
+ return idents
+endfunction
+
+function! s:get_identifiers(line) abort
+ " Registers and type defs
+ let m = matchlist(a:line, '^\s*\(%\S\+\)\s\+=')
+ if !empty(m)
+ return [m[1]]
+ endif
+
+ " Constants
+ let m = matchlist(a:line, '^\(@\S\+\)\s\+=.\+\<constant\>')
+ if !empty(m)
+ return [m[1]]
+ endif
+
+ " Labels for basic blocks
+ let m = matchlist(a:line, '^\([^:]\+\):\%(\s\+; preds = .\+\)\=$')
+ if !empty(m)
+ return ['%' . m[1]]
+ endif
+
+ " Meta variables
+ let m = matchlist(a:line, '^\(!\S\+\)\s\+=')
+ if !empty(m)
+ return [m[1]]
+ endif
+
+ " Attributes
+ let m = matchlist(a:line, '^attributes\s\+\(#\d\+\)\s\+=')
+ if !empty(m)
+ return [m[1]]
+ endif
+
+ if a:line =~# '^\%(declare\|define\)\>'
+ return s:get_func_identifiers(a:line)
+ endif
+
+ return []
+endfunction
+
+function! s:extract_identifier(word) abort
+ if strlen(a:word) <= 1
+ return ''
+ endif
+
+ let prefix = a:word[0]
+ if prefix ==# '@' || prefix ==# '%' || prefix ==# '!'
+ if prefix ==# '!' && a:word[1] ==# '{'
+ return ''
+ endif
+
+ if a:word[1] == '"'
+ let idx = stridx(a:word, '"', 2)
+ if idx == -1
+ return ''
+ endif
+ " @"foo" or %"foo"
+ return a:word[:idx]
+ else
+ " @foo or %foo
+ return matchstr(a:word, '^[@%!][[:alnum:]_.]\+')
+ endif
+ endif
+
+ if prefix ==# '#'
+ return matchstr(a:word, '^#\d\+')
+ endif
+
+ return ''
+endfunction
+
+function! s:jump_to_identifier_at(linum, ident) abort
+ let line = getline(a:linum)
+ let column = stridx(line, a:ident) + 1
+ if column == 0
+ let column = col('.')
+ endif
+ call cursor(a:linum, column)
+endfunction
+
+function! s:browser_open_command() abort
+ if exists('g:llvm_ext_browser_open_command')
+ return g:llvm_ext_browser_open_command
+ endif
+ if exists('s:browser_opener')
+ return s:browser_opener
+ endif
+ let s:browser_opener = ''
+ if has('mac')
+ let s:browser_opener = 'open'
+ elseif has('win32') || has('win64')
+ let s:browser_opener = 'cmd /q /c start ""'
+ else
+ for cmd in ['xdg-open', 'chromium', 'google-chrome', 'firefox']
+ if executable(cmd)
+ let s:browser_opener = cmd
+ break
+ endif
+ endfor
+ endif
+ return s:browser_opener
+endfunction
+
+function! s:open_browser(url) abort
+ let cmd = s:browser_open_command()
+ if cmd ==# ''
+ throw "Failed to open a browser. I don't know how to open a browser: Please set g:llvm_ext_browser_open_command"
+ endif
+ let cmdline = cmd . ' ' . shellescape(a:url)
+ let out = system(cmdline)
+ if v:shell_error
+ throw printf("Failed to open a browser with command '%s': %s", cmdline, out)
+ endif
+endfunction
+
+function! s:goto_definition() abort
+ " Open language reference manual under the cursor in browser
+ let syn_name = synIDattr(synID(line('.'),col('.'),1),'name')
+ if syn_name ==# 'llvmStatement'
+ let word = expand('<cword>')
+ if word !=# ''
+ try
+ " Open browser assuming a word under the cursor is an instruction
+ call s:open_browser('https://llvm.org/docs/LangRef.html#' . word . '-instruction')
+ catch /^Failed to open a browser/
+ echohl ErrorMsg | echom v:exception | echohl None
+ endtry
+ endif
+ return
+ endif
+
+ " XXX: This does not support identifiers which contains spaces
+ let word = expand('<cWORD>')
+ if word ==# ''
+ echom 'No identifier found under the cursor'
+ return
+ endif
+ let ident = s:extract_identifier(word)
+ if ident ==# ''
+ echom 'No identifier found under the cursor'
+ return
+ endif
+
+ " Definition tends to be near its usages. Look back at first.
+ let line = line('.')
+ while line > 0
+ for found in s:get_identifiers(getline(line))
+ if ident ==# found
+ call s:jump_to_identifier_at(line, ident)
+ return
+ endif
+ endfor
+ let line -= 1
+ endwhile
+
+ let line = line('.') + 1
+ let last = line('$')
+ while line <= last
+ for found in s:get_identifiers(getline(line))
+ if ident ==# found
+ call s:jump_to_identifier_at(line, ident)
+ return
+ endif
+ endfor
+ let line += 1
+ endwhile
+
+ echom "No definition for '" . ident . "' found"
+endfunction
+
+if !g:llvm_ext_no_mapping
+ nnoremap <buffer><silent>K :<C-u>call <SID>goto_definition()<CR>
+endif
+
+function! s:run_lli(...) abort
+ if !has('job') || !has('channel') || !has('terminal')
+ echohl ErrorMsg
+ echomsg ':LLI requires terminal feature. Please update your Vim to 8.0+'
+ echohl None
+ return
+ endif
+
+ if !executable(g:llvm_ext_lli_executable)
+ echohl ErrorMsg
+ echomsg g:llvm_ext_lli_executable . ' is not executable. Please set g:llvm_ext_lli_executable'
+ echohl None
+ return
+ endif
+
+ if a:0 > 0
+ let bufnr = term_start([g:llvm_ext_lli_executable, a:1])
+ echo 'Run lli in termnal buffer(' . bufnr . ')'
+ return
+ endif
+
+ let tmpfile = tempname()
+ call writefile(getline(1, '$'), tmpfile)
+ let Cleanup = {ch -> filereadable(tmpfile) ? delete(tmpfile) : 0}
+ let bufnr = term_start([g:llvm_ext_lli_executable, tmpfile], {'close_cb': Cleanup, 'exit_cb': Cleanup})
+ echo 'Run lli in termnal buffer(' . bufnr . ')'
+endfunction
+
+if !exists(':LLI')
+ command! -buffer -nargs=? -bar -complete=file LLI call <SID>run_lli(<f-args>)
+endif