mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-02-03 06:30:03 +08:00
1019 lines
25 KiB
VimL
Vendored
1019 lines
25 KiB
VimL
Vendored
"=============================================================================
|
|
" FILE: parser.vim
|
|
" AUTHOR: Shougo Matsushita <Shougo.Matsu@gmail.com>
|
|
" License: MIT license {{{
|
|
" 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.
|
|
" }}}
|
|
"=============================================================================
|
|
|
|
" Saving 'cpoptions' {{{
|
|
let s:save_cpo = &cpo
|
|
set cpo&vim
|
|
" }}}
|
|
|
|
" For vimshell parser.
|
|
function! vimproc#parser#parse_pipe(statement) abort "{{{
|
|
let commands = []
|
|
for cmdline in vimproc#parser#split_pipe(a:statement)
|
|
" Split args.
|
|
let cmdline = s:parse_cmdline(cmdline)
|
|
|
|
" Parse redirection.
|
|
if cmdline =~ '[<>]'
|
|
let [fd, cmdline] = s:parse_redirection(cmdline)
|
|
else
|
|
let fd = { 'stdin' : '', 'stdout' : '', 'stderr' : '' }
|
|
endif
|
|
|
|
for key in ['stdout', 'stderr']
|
|
if fd[key] == '' || fd[key] =~ '^>'
|
|
continue
|
|
endif
|
|
|
|
if fd[key] ==# '/dev/clip'
|
|
" Clear.
|
|
let @+ = ''
|
|
elseif fd[key] ==# '/dev/quickfix'
|
|
" Clear quickfix.
|
|
call setqflist([])
|
|
endif
|
|
endfor
|
|
|
|
call add(commands, {
|
|
\ 'args' : vimproc#parser#split_args(cmdline),
|
|
\ 'fd' : fd
|
|
\})
|
|
endfor
|
|
|
|
return commands
|
|
endfunction"}}}
|
|
function! s:parse_cmdline(cmdline) abort "{{{
|
|
let cmdline = a:cmdline
|
|
|
|
" Expand block.
|
|
if cmdline =~ '{'
|
|
let cmdline = s:parse_block(cmdline)
|
|
endif
|
|
|
|
" Expand tilde.
|
|
if cmdline =~ '\~'
|
|
let cmdline = s:parse_tilde(cmdline)
|
|
endif
|
|
|
|
" Expand filename.
|
|
if cmdline =~ ' ='
|
|
let cmdline = s:parse_equal(cmdline)
|
|
endif
|
|
|
|
" Expand variables.
|
|
if cmdline =~ '\$'
|
|
let cmdline = s:parse_variables(cmdline)
|
|
endif
|
|
|
|
" Expand wildcard.
|
|
if cmdline =~ '[[*?]\|\\[()|]'
|
|
let cmdline = s:parse_wildcard(cmdline)
|
|
endif
|
|
|
|
return s:parse_tilde(cmdline)
|
|
endfunction"}}}
|
|
function! vimproc#parser#parse_statements(script) abort "{{{
|
|
if type(a:script) == type('') && a:script =~ '^\s*:'
|
|
return [ {
|
|
\ 'statement' : a:script,
|
|
\ 'condition' : 'always',
|
|
\ 'cwd' : getcwd(),
|
|
\ } ]
|
|
endif
|
|
|
|
let script = type(a:script) == type([]) ?
|
|
\ a:script : split(a:script, '\zs')
|
|
let max = len(script)
|
|
let statements = []
|
|
let statement = ''
|
|
let i = 0
|
|
while i < max
|
|
if script[i] == ';'
|
|
if statement != ''
|
|
call add(statements,
|
|
\ {
|
|
\ 'statement' : statement,
|
|
\ 'condition' : 'always',
|
|
\ 'cwd' : getcwd(),
|
|
\})
|
|
endif
|
|
let statement = ''
|
|
let i += 1
|
|
elseif script[i] == '&'
|
|
if i+1 < max && script[i+1] == '&'
|
|
if statement != ''
|
|
call add(statements,
|
|
\ {
|
|
\ 'statement' : statement,
|
|
\ 'condition' : 'true',
|
|
\ 'cwd' : getcwd(),
|
|
\})
|
|
endif
|
|
let statement = ''
|
|
let i += 2
|
|
else
|
|
let statement .= script[i]
|
|
|
|
let i += 1
|
|
endif
|
|
elseif script[i] == '|'
|
|
if i+1 < max && script[i+1] == '|'
|
|
if statement != ''
|
|
call add(statements,
|
|
\ {
|
|
\ 'statement' : statement,
|
|
\ 'condition' : 'false',
|
|
\ 'cwd' : getcwd(),
|
|
\})
|
|
endif
|
|
let statement = ''
|
|
let i += 2
|
|
else
|
|
let statement .= script[i]
|
|
|
|
let i += 1
|
|
endif
|
|
elseif script[i] == "'"
|
|
" Single quote.
|
|
let [string, i] = s:skip_single_quote(script, i)
|
|
let statement .= string
|
|
elseif script[i] == '"'
|
|
" Double quote.
|
|
let [string, i] = s:skip_double_quote(script, i)
|
|
let statement .= string
|
|
elseif script[i] == '`'
|
|
" Back quote.
|
|
let [string, i] = s:skip_back_quote(script, i)
|
|
let statement .= string
|
|
elseif script[i] == '\'
|
|
" Escape.
|
|
let i += 1
|
|
|
|
if i >= max
|
|
throw 'Exception: Join to next line (\).'
|
|
endif
|
|
|
|
let statement .= '\' . script[i]
|
|
let i += 1
|
|
elseif script[i] == '#' && statement == ''
|
|
" Comment.
|
|
break
|
|
else
|
|
let statement .= script[i]
|
|
let i += 1
|
|
endif
|
|
endwhile
|
|
|
|
if statement !~ '^\s*$'
|
|
call add(statements,
|
|
\ {
|
|
\ 'statement' : statement,
|
|
\ 'condition' : 'always',
|
|
\ 'cwd' : getcwd(),
|
|
\})
|
|
endif
|
|
|
|
return statements
|
|
endfunction"}}}
|
|
|
|
function! vimproc#parser#split_statements(script) abort "{{{
|
|
return map(vimproc#parser#parse_statements(a:script),
|
|
\ 'v:val.statement')
|
|
endfunction"}}}
|
|
function! vimproc#parser#split_args(script) abort "{{{
|
|
let script = type(a:script) == type([]) ?
|
|
\ a:script : split(a:script, '\zs')
|
|
let max = len(script)
|
|
let args = []
|
|
let arg = ''
|
|
let i = 0
|
|
while i < max
|
|
if script[i] == "'"
|
|
" Single quote.
|
|
let [arg_quote, i] = s:parse_single_quote(script, i)
|
|
let arg .= arg_quote
|
|
if arg == ''
|
|
call add(args, '')
|
|
endif
|
|
elseif script[i] == '"'
|
|
" Double quote.
|
|
let [arg_quote, i] = s:parse_double_quote(script, i)
|
|
let arg .= arg_quote
|
|
if arg == ''
|
|
call add(args, '')
|
|
endif
|
|
elseif script[i] == '`'
|
|
" Back quote.
|
|
let head = i > 0 ? script[: i-1] : []
|
|
let [arg_quote, i] = s:parse_back_quote(script, i)
|
|
|
|
" Re-parse script.
|
|
return vimproc#parser#split_args(
|
|
\ head + split(arg_quote, '\zs') + script[i :])
|
|
elseif script[i] == '\'
|
|
" Escape.
|
|
let i += 1
|
|
|
|
if i >= max
|
|
throw 'Exception: Join to next line (\).'
|
|
endif
|
|
|
|
let arg .= script[i]
|
|
let i += 1
|
|
elseif script[i] == '#' && arg == ''
|
|
" Comment.
|
|
break
|
|
elseif script[i] != ' '
|
|
let arg .= script[i]
|
|
let i += 1
|
|
else
|
|
" Space.
|
|
if arg != ''
|
|
call add(args, arg)
|
|
endif
|
|
|
|
let arg = ''
|
|
|
|
let i += 1
|
|
endif
|
|
endwhile
|
|
|
|
if arg != ''
|
|
call add(args, arg)
|
|
endif
|
|
|
|
return args
|
|
endfunction"}}}
|
|
function! vimproc#parser#split_args_through(script) abort "{{{
|
|
let script = type(a:script) == type([]) ?
|
|
\ a:script : split(a:script, '\zs')
|
|
let max = len(script)
|
|
let args = []
|
|
let arg = ''
|
|
let i = 0
|
|
while i < max
|
|
if script[i] == "'"
|
|
" Single quote.
|
|
let [string, i] = s:skip_single_quote(script, i)
|
|
let arg .= string
|
|
if arg == ''
|
|
call add(args, '')
|
|
endif
|
|
elseif script[i] == '"'
|
|
" Double quote.
|
|
let [string, i] = s:skip_double_quote(script, i)
|
|
let arg .= string
|
|
if arg == ''
|
|
call add(args, '')
|
|
endif
|
|
elseif script[i] == '`'
|
|
" Back quote.
|
|
let [string, i] = s:skip_back_quote(script, i)
|
|
let arg .= string
|
|
if arg == ''
|
|
call add(args, '')
|
|
endif
|
|
elseif script[i] == '\'
|
|
" Escape.
|
|
let i += 1
|
|
|
|
if i >= max
|
|
throw 'Exception: Join to next line (\).'
|
|
endif
|
|
|
|
let arg .= '\'.script[i]
|
|
let i += 1
|
|
elseif script[i] != ' '
|
|
let arg .= script[i]
|
|
let i += 1
|
|
else
|
|
" Space.
|
|
if arg != ''
|
|
call add(args, arg)
|
|
endif
|
|
|
|
let arg = ''
|
|
|
|
let i += 1
|
|
endif
|
|
endwhile
|
|
|
|
if arg != ''
|
|
call add(args, arg)
|
|
endif
|
|
|
|
return args
|
|
endfunction"}}}
|
|
function! vimproc#parser#split_pipe(script) abort "{{{
|
|
let script = type(a:script) == type([]) ?
|
|
\ a:script : split(a:script, '\zs')
|
|
let max = len(script)
|
|
let command = ''
|
|
|
|
let i = 0
|
|
let commands = []
|
|
while i < max
|
|
if script[i] == '|'
|
|
" Pipe.
|
|
call add(commands, command)
|
|
|
|
" Search next command.
|
|
let command = ''
|
|
let i += 1
|
|
elseif script[i] == "'"
|
|
" Single quote.
|
|
let [string, i] = s:skip_single_quote(script, i)
|
|
let command .= string
|
|
elseif script[i] == '"'
|
|
" Double quote.
|
|
let [string, i] = s:skip_double_quote(script, i)
|
|
let command .= string
|
|
elseif script[i] == '`'
|
|
" Back quote.
|
|
let [string, i] = s:skip_back_quote(script, i)
|
|
let command .= string
|
|
elseif script[i] == '\' && i + 1 < max
|
|
" Escape.
|
|
let command .= '\' . script[i+1]
|
|
let i += 2
|
|
else
|
|
let command .= script[i]
|
|
let i += 1
|
|
endif
|
|
endwhile
|
|
|
|
call add(commands, command)
|
|
|
|
return commands
|
|
endfunction"}}}
|
|
function! vimproc#parser#split_commands(script) abort "{{{
|
|
let script = type(a:script) == type([]) ?
|
|
\ a:script : split(a:script, '\zs')
|
|
let max = len(script)
|
|
let commands = []
|
|
let command = ''
|
|
let i = 0
|
|
while i < max
|
|
if script[i] == '\'
|
|
" Escape.
|
|
let command .= script[i]
|
|
let i += 1
|
|
|
|
if i >= max
|
|
throw 'Exception: Join to next line (\).'
|
|
endif
|
|
|
|
let command .= script[i]
|
|
let i += 1
|
|
elseif script[i] == '|'
|
|
if command != ''
|
|
call add(commands, command)
|
|
endif
|
|
let command = ''
|
|
|
|
let i += 1
|
|
else
|
|
|
|
let command .= script[i]
|
|
let i += 1
|
|
endif
|
|
endwhile
|
|
|
|
if command != ''
|
|
call add(commands, command)
|
|
endif
|
|
|
|
return commands
|
|
endfunction"}}}
|
|
function! vimproc#parser#expand_wildcard(wildcard) abort "{{{
|
|
" Check wildcard.
|
|
let i = 0
|
|
let max = len(a:wildcard)
|
|
let script = ''
|
|
let found = 0
|
|
while i < max
|
|
if a:wildcard[i] == '*' || a:wildcard[i] == '?' || a:wildcard[i] == '['
|
|
let found = 1
|
|
break
|
|
else
|
|
let [script, i] = s:skip_else(script, a:wildcard, i)
|
|
endif
|
|
endwhile
|
|
|
|
if !found
|
|
return [ a:wildcard ]
|
|
endif
|
|
|
|
let wildcard = a:wildcard
|
|
|
|
" Exclude wildcard.
|
|
let exclude = matchstr(wildcard, '\\\@<!\~\zs.\+$')
|
|
let exclude_wilde = []
|
|
if exclude != ''
|
|
" Truncate wildcard.
|
|
let wildcard = wildcard[: len(wildcard)-len(exclude)-2]
|
|
let exclude_wilde = vimproc#parser#expand_wildcard(exclude)
|
|
endif
|
|
|
|
" Modifier.
|
|
let modifier = matchstr(wildcard, '\\\@<!(\zs.\+\ze)$')
|
|
if modifier != ''
|
|
" Truncate wildcard.
|
|
let wildcard = wildcard[: len(wildcard)-len(modifier)-3]
|
|
endif
|
|
|
|
" Expand wildcard.
|
|
let expanded = split(escape(substitute(
|
|
\ glob(wildcard, 1), '\\', '/', 'g'), ' '), '\n')
|
|
if empty(expanded)
|
|
" Use original string.
|
|
return [ a:wildcard ]
|
|
else
|
|
" Check exclude wildcard.
|
|
let candidates = expanded
|
|
let expanded = []
|
|
for candidate in candidates
|
|
let found = 0
|
|
|
|
for ex in exclude_wilde
|
|
if candidate ==# ex
|
|
let found = 1
|
|
break
|
|
endif
|
|
endfor
|
|
|
|
if !found
|
|
call add(expanded, candidate)
|
|
endif
|
|
endfor
|
|
endif
|
|
|
|
if modifier != ''
|
|
" Check file modifier.
|
|
let i = 0
|
|
let max = len(modifier)
|
|
while i < max
|
|
if modifier[i] ==# '/'
|
|
" Directory.
|
|
let expr = 'getftype(v:val) ==# "dir"'
|
|
elseif modifier[i] ==# '.'
|
|
" Normal.
|
|
let expr = 'getftype(v:val) ==# "file"'
|
|
elseif modifier[i] ==# '@'
|
|
" Link.
|
|
let expr = 'getftype(v:val) ==# "link"'
|
|
elseif modifier[i] ==# '='
|
|
" Socket.
|
|
let expr = 'getftype(v:val) ==# "socket"'
|
|
elseif modifier[i] ==# 'p'
|
|
" FIFO Pipe.
|
|
let expr = 'getftype(v:val) ==# "pipe"'
|
|
elseif modifier[i] ==# '*'
|
|
" Executable.
|
|
let expr = 'getftype(v:val) ==# "pipe"'
|
|
elseif modifier[i] ==# '%'
|
|
" Device.
|
|
|
|
if modifier[i :] =~# '^%[bc]'
|
|
if modifier[i] ==# 'b'
|
|
" Block device.
|
|
let expr = 'getftype(v:val) ==# "bdev"'
|
|
else
|
|
" Character device.
|
|
let expr = 'getftype(v:val) ==# "cdev"'
|
|
endif
|
|
|
|
let i += 1
|
|
else
|
|
let expr = 'getftype(v:val) ==# "bdev" || getftype(v:val) ==# "cdev"'
|
|
endif
|
|
else
|
|
" Unknown.
|
|
return []
|
|
endif
|
|
|
|
call filter(expanded, expr)
|
|
let i += 1
|
|
endwhile
|
|
endif
|
|
|
|
return filter(expanded, 'v:val != "." && v:val != ".."')
|
|
endfunction"}}}
|
|
|
|
" Parse helper.
|
|
function! s:parse_block(script) abort "{{{
|
|
let script = ''
|
|
|
|
let i = 0
|
|
let max = len(a:script)
|
|
while i < max
|
|
if a:script[i] == '{'
|
|
" Block.
|
|
let block = matchstr(a:script, '^{\zs.\{-}\ze}', i)
|
|
let rest = a:script[matchend(a:script, '^{.\{-}}', i) :]
|
|
if block == ''
|
|
let [script, i] = s:skip_else(script, a:script, i)
|
|
continue
|
|
endif
|
|
|
|
let head = matchstr(a:script[: i-1], '[^[:blank:]]*$')
|
|
" Truncate script.
|
|
let script = script[: -len(head)-1]
|
|
let rest = (rest =~ '^\s\+' ? ' ' : '') .
|
|
\ join(vimproc#parser#split_args(s:parse_cmdline(rest)))
|
|
let foot = matchstr(rest, '^\S\+')
|
|
let rest = rest[len(foot):]
|
|
if block == ''
|
|
throw 'Exception: Block is not found.'
|
|
elseif block =~ '^\d\+\.\.\d\+$'
|
|
" Range block.
|
|
let start = matchstr(block, '^\d\+')
|
|
let end = matchstr(block, '\d\+$')
|
|
let zero = len(matchstr(block, '^0\+'))+1
|
|
let pattern = '%0' . zero . 'd'
|
|
for b in range(start, end)
|
|
" Concat.
|
|
let script .= head . printf(pattern, b) . foot . ' '
|
|
endfor
|
|
else
|
|
" Normal block.
|
|
let blocks = (stridx(block, ',') < 0) ?
|
|
\ split(block, '\zs') :
|
|
\ split(block, ',', 1)
|
|
for b in vimproc#util#uniq(blocks)
|
|
" Concat.
|
|
let script .= head . escape(b, ' ') . foot . ' '
|
|
endfor
|
|
endif
|
|
|
|
let script .= rest
|
|
return script
|
|
else
|
|
let [script, i] = s:skip_else(script, a:script, i)
|
|
endif
|
|
endwhile
|
|
|
|
return script
|
|
endfunction"}}}
|
|
function! s:parse_tilde(script) abort "{{{
|
|
let script = ''
|
|
|
|
let i = 0
|
|
let max = len(a:script)
|
|
while i < max
|
|
if a:script[i] == ' ' && a:script[i+1] == '~'
|
|
" Tilde.
|
|
" Expand home directory.
|
|
let script .= ' ' . escape(substitute($HOME, '\\', '/', 'g'), '\ ')
|
|
let i += 2
|
|
|
|
elseif i == 0 && a:script[i] == '~'
|
|
" Tilde.
|
|
" Expand home directory.
|
|
let script .= escape(substitute($HOME, '\\', '/', 'g'), '\ ')
|
|
let i += 1
|
|
else
|
|
let [script, i] = s:skip_else(script, a:script, i)
|
|
endif
|
|
endwhile
|
|
|
|
return script
|
|
endfunction"}}}
|
|
function! s:parse_equal(script) abort "{{{
|
|
let script = ''
|
|
|
|
let i = 0
|
|
let max = len(a:script)
|
|
while i < max
|
|
if a:script[i] == ' ' && a:script[i+1] == '='
|
|
" Expand filename.
|
|
let prog = matchstr(a:script, '^=\zs[^[:blank:]]*', i+1)
|
|
if prog == ''
|
|
let [script, i] = s:skip_else(script, a:script, i)
|
|
else
|
|
let filename = vimproc#get_command_name(prog)
|
|
if filename == ''
|
|
throw printf('Error: File "%s" is not found.', prog)
|
|
else
|
|
let script .= filename
|
|
endif
|
|
|
|
" Consume `a:script` until an end of `prog`.
|
|
" e.g.
|
|
" 'echo =ls hoge' -> 'echo =ls hoge'
|
|
" ^ ^
|
|
let i += strlen(a:script[i] . a:script[i+1] . prog)
|
|
endif
|
|
else
|
|
let [script, i] = s:skip_else(script, a:script, i)
|
|
endif
|
|
endwhile
|
|
|
|
return script
|
|
endfunction"}}}
|
|
function! s:parse_variables(script) abort "{{{
|
|
let script = ''
|
|
|
|
let i = 0
|
|
let max = len(a:script)
|
|
try
|
|
while i < max
|
|
if a:script[i] == '$' && a:script[i :] =~ '^$$\?\h'
|
|
" Eval variables.
|
|
let variable_name = matchstr(a:script, '^$$\?\zs\h\w*', i)
|
|
if exists('b:vimshell')
|
|
" For vimshell.
|
|
let script_head = a:script[i :]
|
|
if script_head =~ '^$\l' &&
|
|
\ has_key(b:vimshell.variables, variable_name)
|
|
let script .= b:vimshell.variables[variable_name]
|
|
elseif script_head =~ '^\$\$' &&
|
|
\ has_key(b:vimshell.system_variables, variable_name)
|
|
let script .= b:vimshell.system_variables[variable_name]
|
|
elseif script_head =~ '^$\h'
|
|
let script .= vimproc#util#substitute_path_separator(
|
|
\ eval('$' . variable_name))
|
|
endif
|
|
else
|
|
let script .= vimproc#util#substitute_path_separator(
|
|
\ eval(matchstr(a:script, '^$\h\w*', i)))
|
|
endif
|
|
|
|
let i = matchend(a:script, '^$$\?\h\w*', i)
|
|
else
|
|
let [script, i] = s:skip_else(script, a:script, i)
|
|
endif
|
|
endwhile
|
|
catch /^Vim\%((\a\+)\)\=:E15/
|
|
" Parse error.
|
|
return a:script
|
|
endtry
|
|
|
|
return script
|
|
endfunction"}}}
|
|
function! s:parse_wildcard(script) abort "{{{
|
|
let script = ''
|
|
for arg in vimproc#parser#split_args_through(a:script)
|
|
let script .= join(vimproc#parser#expand_wildcard(arg)) . ' '
|
|
endfor
|
|
|
|
return script
|
|
endfunction"}}}
|
|
function! s:parse_redirection(script) abort "{{{
|
|
let script = ''
|
|
let fd = { 'stdin' : '', 'stdout' : '', 'stderr' : '' }
|
|
|
|
let i = 0
|
|
let max = len(a:script)
|
|
while i < max
|
|
if a:script[i] == '<'
|
|
" Input redirection.
|
|
let i += 1
|
|
let fd.stdin = get(vimproc#parser#split_args(
|
|
\ matchstr(a:script, '^\s*\S\+', i)), 0, '')
|
|
let i = matchend(a:script, '^\s*\S\+', i)
|
|
elseif a:script[i] =~ '^[12]' && a:script[i :] =~ '^[12]>'
|
|
" Output redirection.
|
|
let i += 2
|
|
if a:script[i-2] == 1
|
|
let fd.stdout = get(vimproc#parser#split_args(
|
|
\ matchstr(a:script, '^\s*\S\+', i)), 0, '')
|
|
else
|
|
let fd.stderr = get(vimproc#parser#split_args(
|
|
\ matchstr(a:script, '^\s*\zs\(\S\+\|&\d\+\)', i)), 0, '')
|
|
if fd.stderr ==# '&1'
|
|
" Redirection to stdout.
|
|
let fd.stderr = '/dev/stdout'
|
|
endif
|
|
endif
|
|
|
|
let i = matchend(a:script, '^\s*\zs\(\S\+\|&\d\+\)', i)
|
|
elseif a:script[i] == '&' && a:script[i :] =~ '^&>'
|
|
" Output stderr.
|
|
let i += 2
|
|
let fd.stderr = get(vimproc#parser#split_args(
|
|
\ matchstr(a:script, '^\s*\S\+', i)), 0, '')
|
|
let i = matchend(a:script, '^\s*\S\+', i)
|
|
elseif a:script[i] == '>'
|
|
" Output redirection.
|
|
if a:script[i :] =~ '^>&'
|
|
" Output stderr.
|
|
let i += 2
|
|
let fd.stderr = get(vimproc#parser#split_args(
|
|
\ matchstr(a:script, '^\s*\S\+', i)), 0, '')
|
|
elseif a:script[i :] =~ '^>>'
|
|
" Append stdout.
|
|
let i += 2
|
|
let fd.stdout = '>' . get(vimproc#parser#split_args(
|
|
\ matchstr(a:script, '^\s*\S\+', i)), 0, '')
|
|
else
|
|
" Output stdout.
|
|
let i += 1
|
|
let fd.stdout = get(vimproc#parser#split_args(
|
|
\ matchstr(a:script, '^\s*\S\+', i)), 0, '')
|
|
endif
|
|
|
|
let i = matchend(a:script, '^\s*\zs\S*', i)
|
|
else
|
|
let [script, i] = s:skip_else(script, a:script, i)
|
|
endif
|
|
endwhile
|
|
|
|
return [fd, script]
|
|
endfunction"}}}
|
|
|
|
function! s:parse_single_quote(script, i) abort "{{{
|
|
if a:script[a:i] != "'"
|
|
return ['', a:i]
|
|
endif
|
|
|
|
let arg = ''
|
|
let i = a:i + 1
|
|
let max = len(a:script)
|
|
while i < max
|
|
if a:script[i] == "'"
|
|
if i+1 < max && a:script[i+1] == "'"
|
|
" Escape quote.
|
|
let arg .= "'"
|
|
let i += 2
|
|
else
|
|
" Quote end.
|
|
return [arg, i+1]
|
|
endif
|
|
else
|
|
let arg .= a:script[i]
|
|
let i += 1
|
|
endif
|
|
endwhile
|
|
|
|
throw 'Exception: Quote ('') is not found.'
|
|
endfunction"}}}
|
|
function! s:parse_double_quote(script, i) abort "{{{
|
|
if a:script[a:i] != '"'
|
|
return ['', a:i]
|
|
endif
|
|
|
|
let escape_sequences = {
|
|
\ 'a' : "\<C-g>", 'b' : "\<BS>",
|
|
\ 't' : "\<Tab>", 'r' : "\<CR>",
|
|
\ 'n' : "\<LF>", 'e' : "\<Esc>",
|
|
\ '\' : '\', '?' : '?',
|
|
\ '"' : '"', "'" : "'",
|
|
\ '`' : '`', '$' : '$',
|
|
\}
|
|
let arg = ''
|
|
let i = a:i + 1
|
|
let script = type(a:script) == type([]) ?
|
|
\ a:script : split(a:script, '\zs')
|
|
let max = len(script)
|
|
while i < max
|
|
if script[i] == '"'
|
|
" Quote end.
|
|
return [arg, i+1]
|
|
elseif script[i] == '$'
|
|
" Eval variables.
|
|
let var = matchstr(join(script[i :], ''), '^$\h\w*')
|
|
if var != ''
|
|
let arg .= s:parse_variables(var)
|
|
let i += len(var)
|
|
else
|
|
let arg .= '$'
|
|
let i += 1
|
|
endif
|
|
elseif script[i] == '`'
|
|
" Backquote.
|
|
let [arg_quote, i] = s:parse_back_quote(script, i)
|
|
let arg .= arg_quote
|
|
elseif script[i] == '\'
|
|
" Escape.
|
|
let i += 1
|
|
|
|
if i >= max
|
|
throw 'Exception: Join to next line (\).'
|
|
endif
|
|
|
|
if script[i] == 'x'
|
|
let num = matchstr(join(script[i+1 :], ''), '^\x\+')
|
|
let arg .= nr2char(str2nr(num, 16))
|
|
let i += len(num)
|
|
elseif has_key(escape_sequences, script[i])
|
|
let arg .= escape_sequences[script[i]]
|
|
else
|
|
let arg .= '\' . script[i]
|
|
endif
|
|
let i += 1
|
|
else
|
|
let arg .= script[i]
|
|
let i += 1
|
|
endif
|
|
endwhile
|
|
|
|
throw 'Exception: Quote (") is not found.'
|
|
endfunction"}}}
|
|
function! s:parse_back_quote(script, i) abort "{{{
|
|
if a:script[a:i] != '`'
|
|
return ['', a:i]
|
|
endif
|
|
|
|
let arg = ''
|
|
let max = len(a:script)
|
|
if a:i + 1 < max && a:script[a:i + 1] == '='
|
|
" Vim eval quote.
|
|
let i = a:i + 2
|
|
|
|
while i < max
|
|
if a:script[i] == '\'
|
|
" Escape.
|
|
let i += 1
|
|
|
|
if i >= max
|
|
throw 'Exception: Join to next line (\).'
|
|
endif
|
|
|
|
let arg .= '\' . a:script[i]
|
|
let i += 1
|
|
elseif a:script[i] == '`'
|
|
" Quote end.
|
|
return [eval(arg), i+1]
|
|
else
|
|
let arg .= a:script[i]
|
|
let i += 1
|
|
endif
|
|
endwhile
|
|
else
|
|
" Eval quote.
|
|
let i = a:i + 1
|
|
|
|
while i < max
|
|
if a:script[i] == '`'
|
|
" Quote end.
|
|
return [substitute(vimproc#system(arg), '\n$', '', ''), i+1]
|
|
else
|
|
let arg .= a:script[i]
|
|
let i += 1
|
|
endif
|
|
endwhile
|
|
endif
|
|
|
|
throw 'Exception: Quote (`) is not found.'
|
|
endfunction"}}}
|
|
|
|
" Skip helper.
|
|
function! s:skip_single_quote(script, i) abort "{{{
|
|
let max = len(a:script)
|
|
let string = ''
|
|
let i = a:i
|
|
|
|
" a:script[i] is always "'" when this function is called
|
|
if i >= max || a:script[i] != ''''
|
|
throw 'Exception: Quote ('') is not found.'
|
|
endif
|
|
let string .= a:script[i]
|
|
let i += 1
|
|
|
|
let ss = []
|
|
while i < max
|
|
if a:script[i] == ''''
|
|
if i+1 < max && a:script[i+1] == ''''
|
|
" Escape quote.
|
|
let ss += [a:script[i]]
|
|
let i += 1
|
|
else
|
|
break
|
|
endif
|
|
endif
|
|
|
|
let ss += [a:script[i]]
|
|
let i += 1
|
|
endwhile
|
|
let string .= join(ss, '')
|
|
|
|
if i < max
|
|
" must end with "'"
|
|
if a:script[i] != ''''
|
|
throw 'Exception: Quote ('') is not found.'
|
|
endif
|
|
let string .= a:script[i]
|
|
let i += 1
|
|
endif
|
|
|
|
return [string, i]
|
|
endfunction"}}}
|
|
function! s:skip_double_quote(script, i) abort "{{{
|
|
let max = len(a:script)
|
|
let string = ''
|
|
let i = a:i
|
|
|
|
" a:script[i] is always '"' when this function is called
|
|
if i >= max || a:script[i] != '"'
|
|
throw 'Exception: Quote (") is not found.'
|
|
endif
|
|
let string .= a:script[i]
|
|
let i += 1
|
|
|
|
let ss = []
|
|
while i < max
|
|
if a:script[i] == '\' && i+1 < max
|
|
" Escape quote.
|
|
let ss += [a:script[i]]
|
|
let i += 1
|
|
elseif a:script[i] == '"'
|
|
break
|
|
endif
|
|
|
|
let ss += [a:script[i]]
|
|
let i += 1
|
|
endwhile
|
|
let string .= join(ss, '')
|
|
|
|
if i < max
|
|
" must end with '"'
|
|
if a:script[i] != '"'
|
|
throw 'Exception: Quote (") is not found.'
|
|
endif
|
|
let string .= a:script[i]
|
|
let i += 1
|
|
endif
|
|
|
|
return [string, i]
|
|
endfunction"}}}
|
|
function! s:skip_back_quote(script, i) abort "{{{
|
|
let max = len(a:script)
|
|
let string = ''
|
|
let i = a:i
|
|
|
|
" a:script[i] is always '`' when this function is called
|
|
if a:script[i] != '`'
|
|
throw 'Exception: Quote (`) is not found.'
|
|
endif
|
|
let string .= a:script[i]
|
|
let i += 1
|
|
|
|
while i < max && a:script[i] != '`'
|
|
let string .= a:script[i]
|
|
let i += 1
|
|
endwhile
|
|
|
|
if i < max
|
|
" must end with "`"
|
|
if a:script[i] != '`'
|
|
throw 'Exception: Quote (`) is not found.'
|
|
endif
|
|
let string .= a:script[i]
|
|
let i += 1
|
|
endif
|
|
|
|
return [string, i]
|
|
endfunction"}}}
|
|
function! s:skip_else(args, script, i) abort "{{{
|
|
if a:script[a:i] == "'"
|
|
" Single quote.
|
|
let [string, i] = s:skip_single_quote(a:script, a:i)
|
|
let script = a:args . string
|
|
elseif a:script[a:i] == '"'
|
|
" Double quote.
|
|
let [string, i] = s:skip_double_quote(a:script, a:i)
|
|
let script = a:args . string
|
|
elseif a:script[a:i] == '`'
|
|
" Back quote.
|
|
let [string, i] = s:skip_back_quote(a:script, a:i)
|
|
let script = a:args . string
|
|
elseif a:script[a:i] == '\'
|
|
" Escape.
|
|
let script = a:args . '\' . a:script[a:i+1]
|
|
let i = a:i + 2
|
|
else
|
|
let script = a:args . a:script[a:i]
|
|
let i = a:i + 1
|
|
endif
|
|
|
|
return [script, i]
|
|
endfunction"}}}
|
|
|
|
" Restore 'cpoptions' {{{
|
|
let &cpo = s:save_cpo
|
|
" }}}
|
|
" vim:foldmethod=marker:fen:sw=2:sts=2
|