"=============================================================================
" 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.
" }}}
"=============================================================================
function! vimshell#parser#check_script(script) abort "{{{
" Parse check only.
" Split statements.
for statement in vimproc#parser#split_statements(a:script)
call vimproc#parser#split_args(vimshell#parser#parse_alias(statement))
endfor
endfunction"}}}
function! vimshell#parser#eval_script(script, context) abort "{{{
let context = vimshell#init#_context(a:context)
" Split statements.
let statements = vimproc#parser#parse_statements(a:script)
let max = len(statements)
let context = a:context
let context.is_single_command = (context.is_interactive && max == 1)
let i = 0
while i < max
try
let ret = s:execute_statement(statements[i].statement, context)
catch /^exe: Process started./
" Change continuation.
let b:vimshell.continuation = {
\ 'statements' : statements[i : ], 'context' : context,
\ 'script' : a:script,
\ }
call vimshell#interactive#execute_process_out(0)
return 1
endtry
let condition = statements[i].condition
if (condition ==# 'true' && ret)
\ || (condition ==# 'false' && !ret)
break
endif
let i += 1
endwhile
" Call postexec hook.
call vimshell#hook#call('postexec', context, a:script)
return 0
endfunction"}}}
function! vimshell#parser#execute_command(commands, context) abort "{{{
if empty(a:commands)
return 0
endif
let commands = a:commands
let program = commands[0].args[0]
let args = commands[0].args[1:]
let fd = {
\ 'stdin' : commands[0].fd.stdin,
\ 'stdout' : commands[-1].fd.stdout,
\ 'stderr' : commands[-1].fd.stderr,
\ }
let context = a:context
let context.fd = fd
let internal_commands = vimshell#available_commands(program)
let dir = substitute(substitute(program, '^\~\ze[/\\]',
\ substitute($HOME, '\\', '/', 'g'), ''),
\ '\\\(.\)', '\1', 'g')
" Check pipeline.
if get(get(internal_commands, program, {}),
\ 'kind', '') == 'execute'
" Execute execute commands.
let commands[0].args = args
return vimshell#helpers#execute_internal_command(program, commands, context)
elseif program =~ '^!'
" Convert to internal "h" command.
if program == '!!'
let args = []
else
let args = [program[1:]] + args
endif
return vimshell#helpers#execute_internal_command('h', args, context)
elseif len(a:commands) > 1
if a:commands[-1].args[0] == 'less'
" Execute less(Syntax sugar).
let commands = a:commands[: -2]
if !empty(a:commands[-1].args[1:])
let commands[0].args =
\ a:commands[-1].args[1:] + commands[0].args
endif
return vimshell#helpers#execute_internal_command('less', commands, context)
else
" Execute external commands.
return vimshell#helpers#execute_internal_command('exe', commands, context)
endif
elseif has_key(get(internal_commands, program, {}), 'execute')
" Internal commands.
return vimshell#helpers#execute_internal_command(program, args, context)
elseif !executable(dir) && isdirectory(dir)
" Directory.
" Change the working directory like zsh.
" Call internal cd command.
return vimshell#helpers#execute_internal_command('cd', [dir], context)
else "{{{
let ext = fnamemodify(program, ':e')
if !empty(ext) && has_key(g:vimshell_execute_file_list, ext)
" Suffix execution.
let args = extend(split(g:vimshell_execute_file_list[ext]),
\ a:commands[0].args)
let commands = [ { 'args' : args, 'fd' : fd } ]
return vimshell#parser#execute_command(commands, a:context)
else
let args = insert(args, program)
if has_key(g:vimshell_terminal_commands, program)
\ && g:vimshell_terminal_commands[program]
" Execute terminal commands.
return vimshell#helpers#execute_internal_command('iexe', commands, context)
else
" Execute external commands.
return vimshell#helpers#execute_internal_command('exe', commands, context)
endif
endif
endif"}}}
endfunction
"}}}
function! vimshell#parser#execute_continuation(is_insert) abort "{{{
" Execute pipe.
call vimshell#interactive#execute_process_out(a:is_insert)
if empty(b:vimshell.continuation)
return
endif
if b:interactive.process.is_valid
return 1
endif
let b:vimshell.system_variables['status'] = b:interactive.status
let b:interactive.encoding = &encoding
let ret = b:interactive.status
let statements = b:vimshell.continuation.statements
let condition = statements[0].condition
if (condition ==# 'true' && ret)
\ || (condition ==# 'false' && !ret)
" Exit.
let b:vimshell.continuation.statements = []
let statements = []
endif
if ret != 0
" Print exit value.
let context = b:vimshell.continuation.context
if b:interactive.cond ==# 'signal'
" Note: Ignore SIGINT.
if b:interactive.status != 2
let message = printf('vimshell: %s %d(%s) "%s"',
\ b:interactive.cond, b:interactive.status,
\ vimshell#interactive#decode_signal(b:interactive.status),
\ b:interactive.cmdline)
call vimshell#error_line(context.fd, message)
endif
else
let message = printf('vimshell: %s %d "%s"',
\ b:interactive.cond, b:interactive.status, b:interactive.cmdline)
call vimshell#error_line(context.fd, message)
endif
endif
" Execute rest commands.
let statements = statements[1:]
let max = len(statements)
let context = b:vimshell.continuation.context
let i = 0
while i < max
try
let ret = s:execute_statement(statements[i].statement, context)
catch /^exe: Process started./
" Change continuation.
let b:vimshell.continuation.statements = statements[i : ]
let b:vimshell.continuation.context = context
return 1
endtry
let condition = statements[i].condition
if (condition ==# 'true' && ret)
\ || (condition ==# 'false' && !ret)
break
endif
let i += 1
endwhile
if !exists('b:vimshell')
return
endif
" Call postexec hook.
call vimshell#hook#call('postexec',
\ context, b:vimshell.continuation.script)
let b:vimshell.continuation = {}
if b:interactive.syntax !=# &filetype
" Set highlight.
let start = searchpos(
\ b:vimshell.context.prompt_pattern, 'bWn')[0]
if start > 0
call s:highlight_with(start + 1,
\ printf('"\ze\%%(^\[%%\]\|%s\)"',
\ b:vimshell.context.prompt_pattern),
\ b:interactive.syntax)
endif
let b:interactive.syntax = &filetype
endif
call vimshell#next_prompt(context, a:is_insert)
endfunction
"}}}
function! s:execute_statement(statement, context) abort "{{{
let statement = vimshell#parser#parse_alias(a:statement)
" Call preexec filter.
let statement = vimshell#hook#call_filter(
\ 'preexec', a:context, statement)
let program = vimshell#parser#parse_program(statement)
let internal_commands = vimshell#available_commands(program)
if program =~ '^\s*:'
" Convert to vexe special command.
let fd = { 'stdin' : '', 'stdout' : '', 'stderr' : '' }
let commands = [ { 'args' :
\ split(substitute(statement, '^:', 'vexe ', '')), 'fd' : fd } ]
elseif statement =~ '&$'
" Convert to internal "bg" command.
let commands = vimproc#parser#parse_pipe(statement)
let commands[-1].args[-1] = commands[-1].args[-1][:-2]
if commands[-1].args[-1] == ''
" Delete empty arg.
call remove(commands[-1].args, -1)
endif
call insert(commands[-1].args, 'bg')
elseif has_key(internal_commands, program)
\ && internal_commands[program].kind ==# 'special'
" Special commands.
let fd = { 'stdin' : '', 'stdout' : '', 'stderr' : '' }
let commands = [ { 'args' : split(statement), 'fd' : fd } ]
else
let commands = vimproc#parser#parse_pipe(statement)
endif
return vimshell#parser#execute_command(commands, a:context)
endfunction
"}}}
" Parse helper.
function! vimshell#parser#parse_alias(statement) abort "{{{
let statement = s:parse_galias(a:statement)
let program = matchstr(statement, vimshell#helpers#get_program_pattern())
if statement != '' && program == ''
throw 'Error: Invalid command name.'
endif
if exists('b:vimshell') &&
\ !empty(get(b:vimshell.alias_table, program, []))
" Expand alias.
let args = vimproc#parser#split_args_through(
\ statement[matchend(statement, vimshell#helpers#get_program_pattern()) :])
let statement = s:recursive_expand_alias(program, args)
endif
return statement
endfunction"}}}
function! vimshell#parser#parse_program(statement) abort "{{{
" Get program.
let program = matchstr(a:statement, vimshell#helpers#get_program_pattern())
if program == ''
throw 'Error: Invalid command name.'
endif
if program != '' && program[0] == '~'
" Parse tilde.
let program =
\ vimshell#util#substitute_path_separator($HOME)
\ . program[1:]
endif
return program
endfunction"}}}
function! s:parse_galias(script) abort "{{{
if !exists('b:vimshell')
return a:script
endif
let script = a:script
let max = len(script)
let args = []
let arg = ''
let i = 0
while i < max
if 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
" Expand global alias.
let i = 0
for arg in args
if has_key(b:vimshell.galias_table, arg)
let args[i] = b:vimshell.galias_table[arg]
endif
let i += 1
endfor
return join(args)
endfunction"}}}
function! s:recursive_expand_alias(alias_name, args) abort "{{{
" Recursive expand alias.
let alias = b:vimshell.alias_table[a:alias_name]
let expanded = {}
while 1
if has_key(expanded, alias) || !has_key(b:vimshell.alias_table, alias)
break
endif
let expanded[alias] = 1
let alias = b:vimshell.alias_table[alias]
endwhile
" Expand variables.
let script = ''
let i = 0
let max = len(alias)
let args = insert(copy(a:args), a:alias_name)
try
while i < max
let matchlist = matchlist(alias,
\'^$$args\(\[\d\+\%(:\%(\d\+\)\?\)\?\]\)\?', i)
if empty(matchlist)
let script .= alias[i]
let i += 1
else
let index = matchlist[1]
if index == ''
" All args.
let script .= join(args[1:])
elseif index =~ '^\[\d\+\]$'
let script .= get(args, index[1: -2], '')
else
" Some args.
let script .= join(eval('args' . index))
endif
let i += len(matchlist[0])
endif
endwhile
endtry
if script ==# alias && !empty(a:args)
let script .= ' ' . join(a:args)
endif
return script
endfunction"}}}
" Misc.
function! vimshell#parser#check_wildcard() abort "{{{
let args = vimshell#helpers#get_current_args()
return !empty(args) && args[-1] =~ '[[*?]\|^\\[()|]'
endfunction"}}}
function! vimshell#parser#getopt(args, optsyntax, ...) abort "{{{
let default_values = get(a:000, 0, {})
" Initialize.
let optsyntax = a:optsyntax
if !has_key(optsyntax, 'noarg')
let optsyntax['noarg'] = []
endif
if !has_key(optsyntax, 'noarg_short')
let optsyntax['noarg_short'] = []
endif
if !has_key(optsyntax, 'arg1')
let optsyntax['arg1'] = []
endif
if !has_key(optsyntax, 'arg1_short')
let optsyntax['arg1_short'] = []
endif
if !has_key(optsyntax, 'arg=')
let optsyntax['arg='] = []
endif
let args = []
let options = {}
for arg in a:args
let found = 0
for opt in optsyntax['noarg']
if arg ==# opt
let found = 1
" Get argument value.
let options[opt] = 1
break
endif
endfor
for opt in optsyntax['arg=']
if vimshell#util#head_match(arg, opt.'=')
let found = 1
" Get argument value.
let options[opt] = arg[len(opt.'='):]
break
endif
endfor
if !found
call add(args, arg)
endif
endfor
" Set default value.
for [opt, default] in items(default_values)
if !has_key(options, opt)
let options[opt] = default
endif
endfor
return [args, options]
endfunction"}}}
function! s:highlight_with(start, end, syntax) abort "{{{
let cnt = get(b:, 'highlight_count', 0)
if globpath(&runtimepath, 'syntax/' . a:syntax . '.vim') == ''
return
endif
unlet! b:current_syntax
let save_isk= &l:iskeyword " For scheme.
execute printf('silent! syntax include @highlightWith%d syntax/%s.vim',
\ cnt, a:syntax)
let &l:iskeyword = save_isk
execute printf('syntax region highlightWith%d start=/\%%%dl/ end=%s keepend '
\ . 'contains=@highlightWith%d,VimShellError',
\ cnt, a:start, a:end, cnt)
let b:highlight_count = cnt + 1
endfunction"}}}
" vim: foldmethod=marker