aboutsummaryrefslogtreecommitdiff
path: root/runtime
diff options
context:
space:
mode:
authorThiago de Arruda <tpadilha84@gmail.com>2015-04-02 11:28:24 -0300
committerThiago de Arruda <tpadilha84@gmail.com>2015-04-02 11:33:48 -0300
commit617878f7473a9f2980df2627601c38fd9f5029ca (patch)
treef568497c53daaf4b32bd20655fc5845bf497ed07 /runtime
parent6abde99ff243d3ead24c26eff24121140a039fb7 (diff)
downloadrneovim-617878f7473a9f2980df2627601c38fd9f5029ca.tar.gz
rneovim-617878f7473a9f2980df2627601c38fd9f5029ca.tar.bz2
rneovim-617878f7473a9f2980df2627601c38fd9f5029ca.zip
runtime: Add vimexpect library and example gdb plugin
This library makes it easier to script communication with interactive programs. It is similar to what the "expect" tcl extension does, but uses an object oriented API and is designed to integrate nicely with Neovim job control.
Diffstat (limited to 'runtime')
-rw-r--r--runtime/autoload/vimexpect.vim154
1 files changed, 154 insertions, 0 deletions
diff --git a/runtime/autoload/vimexpect.vim b/runtime/autoload/vimexpect.vim
new file mode 100644
index 0000000000..16e7d30d6c
--- /dev/null
+++ b/runtime/autoload/vimexpect.vim
@@ -0,0 +1,154 @@
+" vimexpect.vim is a small object-oriented library that simplifies the task of
+" scripting communication with jobs or any interactive program. The name
+" `expect` comes from the famous tcl extension that has the same purpose.
+"
+" This library is built upon two simple concepts: Parsers and States.
+"
+" A State represents a program state and associates a set of regular
+" expressions(to parse program output) with method names(to deal with parsed
+" output). States are created with the vimexpect#State(patterns) function.
+"
+" A Parser manages data received from the program. It also manages State
+" objects by storing them into a stack, where the top of the stack is the
+" current State. Parsers are created with the vimexpect#Parser(initial_state,
+" target) function
+"
+" The State methods are defined by the user, and are always called with `self`
+" set as the Parser target. Advanced control flow is achieved by changing the
+" current state with the `push`/`pop`/`switch` parser methods.
+"
+" An example of this library in action can be found in Neovim source
+" code(contrib/neovim_gdb subdirectory)
+
+let s:State = {}
+
+
+" Create a new State instance with a list where each item is a [regexp, name]
+" pair. A method named `name` must be defined in the created instance.
+function s:State.create(patterns)
+ let this = copy(self)
+ let this._patterns = a:patterns
+ return this
+endfunction
+
+
+let s:Parser = {}
+let s:Parser.LINE_BUFFER_MAX_LEN = 100
+
+
+" Create a new Parser instance with the initial state and a target. The target
+" is a dictionary that will be the `self` of every State method call associated
+" with the parser, and may contain options normally passed to
+" `jobstart`(on_stdout/on_stderr will be overriden). Returns the target so it
+" can be called directly as the second argument of `jobstart`:
+"
+" call jobstart(prog_argv, vimexpect#Parser(initial_state, {'pty': 1}))
+function s:Parser.create(initial_state, target)
+ let parser = copy(self)
+ let parser._line_buffer = []
+ let parser._stack = [a:initial_state]
+ let parser._target = a:target
+ let parser._target.on_stdout = function('s:JobOutput')
+ let parser._target.on_stderr = function('s:JobOutput')
+ let parser._target._parser = parser
+ return parser._target
+endfunction
+
+
+" Push a state to the state stack
+function s:Parser.push(state)
+ call add(self._stack, a:state)
+endfunction
+
+
+" Pop a state from the state stack. Fails if there's only one state remaining.
+function s:Parser.pop()
+ if len(self._stack) == 1
+ throw 'vimexpect:emptystack:State stack cannot be empty'
+ endif
+ return remove(self._stack, -1)
+endfunction
+
+
+" Replace the state currently in the top of the stack.
+function s:Parser.switch(state)
+ let old_state = self._stack[-1]
+ let self._stack[-1] = a:state
+ return old_state
+endfunction
+
+
+" Append a list of lines to the parser line buffer and try to match it the
+" current state. This will shift old lines if the buffer crosses its
+" limit(defined by the LINE_BUFFER_MAX_LEN field). During normal operation,
+" this function is called by the job handler provided by this module, but it
+" may be called directly by the user for other purposes(testing for example)
+function s:Parser.feed(lines)
+ if empty(a:lines)
+ return
+ endif
+ let lines = a:lines
+ let linebuf = self._line_buffer
+ if lines[0] != "\n" && !empty(linebuf)
+ " continue the previous line
+ let linebuf[-1] .= lines[0]
+ call remove(lines, 0)
+ endif
+ " append the newly received lines to the line buffer
+ let linebuf += lines
+ " keep trying to match handlers while the line isnt empty
+ while !empty(linebuf)
+ let match_idx = self.parse(linebuf)
+ if match_idx == -1
+ break
+ endif
+ let linebuf = linebuf[match_idx + 1 : ]
+ endwhile
+ " shift excess lines from the buffer
+ while len(linebuf) > self.LINE_BUFFER_MAX_LEN
+ call remove(linebuf, 0)
+ endwhile
+ let self._line_buffer = linebuf
+endfunction
+
+
+" Try to match a list of lines with the current state and call the handler if
+" the match succeeds. Return the index in `lines` of the first match.
+function s:Parser.parse(lines)
+ let lines = a:lines
+ if empty(lines)
+ return -1
+ endif
+ let state = self.state()
+ " search for a match using the list of patterns
+ for [pattern, handler] in state._patterns
+ let matches = matchlist(lines, pattern)
+ if empty(matches)
+ continue
+ endif
+ let match_idx = match(lines, pattern)
+ call call(state[handler], matches[1:], self._target)
+ return match_idx
+ endfor
+endfunction
+
+
+" Return the current state
+function s:Parser.state()
+ return self._stack[-1]
+endfunction
+
+
+" Job handler that simply forwards lines to the parser.
+function! s:JobOutput(id, lines)
+ call self._parser.feed(a:lines)
+endfunction
+
+function vimexpect#Parser(initial_state, target)
+ return s:Parser.create(a:initial_state, a:target)
+endfunction
+
+
+function vimexpect#State(patterns)
+ return s:State.create(a:patterns)
+endfunction