From 3380590ac4a4dc922e51423d16cf459f2c4286a8 Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Sun, 4 Aug 2024 22:46:13 -0500 Subject: [PATCH] lib: implement multi-match Allow match to take a table of pattern -> handlers instead, and match the earliest and longest pattern that we find in the output buffer. --- examples/cat-multi.orch | 30 +++++++++++++++++++++++++ lib/orch.lua | 5 ++++- lib/orch/actions.lua | 35 +++++++++++++++++++++++++++-- lib/orch/direct.lua | 5 +++-- lib/orch/process.lua | 7 +++--- lib/orch/scripter.lua | 49 +++++++++++++++++++++++++++++++++++++---- 6 files changed, 119 insertions(+), 12 deletions(-) create mode 100644 examples/cat-multi.orch diff --git a/examples/cat-multi.orch b/examples/cat-multi.orch new file mode 100644 index 0000000..778ba16 --- /dev/null +++ b/examples/cat-multi.orch @@ -0,0 +1,30 @@ +-- Executed like `./orch -f cat-multi.orch -- cat` -- this one won't spawn +-- anything itself, so it must be done on the command line. + +-- We get released on first match anyways, so this doesn't need to be called: +-- release() + +write "Send One Two\r" +match { + One = function() + write "LOL\r" + + debug "Matched one" + match "LOL" { + callback = function() + debug "lol" + write "Foo\r" + end + } + -- Also valid: + -- write "Foo" + match "Foo" { + callback = function() + debug "foo matched too" + end + } + end, + Two = function() + debug "Called two" + end, +} diff --git a/lib/orch.lua b/lib/orch.lua index 8a12672..2606693 100644 --- a/lib/orch.lua +++ b/lib/orch.lua @@ -28,7 +28,10 @@ orch.run_script = scripter.run_script orch.sleep = core.sleep -- spawn(cmd...): spawn the given command, returning a process that may be --- manipulated as needed. +-- manipulated as needed. This is the primary interface we expose to users of +-- the lib; most of the things one would do with the scripting interface are +-- broken out via methods on the DirectProcess instead to provide a more +-- object-oriented feel. orch.spawn = direct.spawn -- Reset all of the state; this largely means resetting the scripting bits, as diff --git a/lib/orch/actions.lua b/lib/orch/actions.lua index a6bbd42..58285dd 100644 --- a/lib/orch/actions.lua +++ b/lib/orch/actions.lua @@ -46,9 +46,40 @@ function MatchAction:dump(level) end end function MatchAction:matches(buffer) - local matcher_arg = self.pattern_obj or self.pattern + local first, last, cb + local len + + for pattern, def in pairs(self.patterns) do + local matcher_arg = def._compiled or pattern + + -- We use the earliest and longest match, rather than the first to + -- match, to provide predictable semantics. If any more control than + -- that is desired, it should be split up into multiple distinct matches + -- or, in a scripter context, switched to a one() block. + local tfirst, tlast = self.matcher.match(matcher_arg, buffer) + if not tfirst then + goto next + end + + if len and tfirst > first then + -- Earlier matches take precedence. + goto next + end + + local tlen = tlast - tfirst + if tfirst == first and tlen <= len then + -- Longer matches take precedence. If we have two + -- patterns that managed to result in the same match, + -- then we arbitrarily choose the first one we noticed. + goto next + end + + first, last, cb = tfirst, tlast, def.callback + len = tlen +::next:: + end - return self.matcher.match(matcher_arg, buffer) + return first, last, cb end actions.MatchAction = MatchAction diff --git a/lib/orch/direct.lua b/lib/orch/direct.lua index e500133..048a3ef 100644 --- a/lib/orch/direct.lua +++ b/lib/orch/direct.lua @@ -44,13 +44,14 @@ function DirectProcess:match(pattern, matcher) matcher = matcher or matchers.available.default local action = actions.MatchAction:new("match") + local patterns = { [pattern] = {} } action.timeout = self.timeout - action.pattern = pattern action.matcher = matcher if matcher.compile then - action.pattern_obj = action.matcher.compile(pattern) + patterns[pattern]._compiled = action.matcher.compile(pattern) end + action.patterns = patterns return self._process:match(action) end diff --git a/lib/orch/process.lua b/lib/orch/process.lua index f01d45b..d0ebd85 100644 --- a/lib/orch/process.lua +++ b/lib/orch/process.lua @@ -18,7 +18,7 @@ function MatchBuffer:new(process, ctx) return obj end function MatchBuffer:_matches(action) - local first, last = action:matches(self.buffer) + local first, last, callback = action:matches(self.buffer) if not first then return false @@ -29,8 +29,9 @@ function MatchBuffer:_matches(action) self.buffer = self.buffer:sub(last + 1) -- Return value is not significant, ignored. - if action.callback then - self.ctx:execute(action.callback) + callback = callback or action.callback + if callback then + self.ctx:execute(callback) end return true diff --git a/lib/orch/scripter.lua b/lib/orch/scripter.lua index dd94212..e1fac26 100644 --- a/lib/orch/scripter.lua +++ b/lib/orch/scripter.lua @@ -19,6 +19,12 @@ local CTX_CALLBACK = 3 local current_ctx +-- Configuration keys valid for an individual pattern specified to a match +local match_pattern_valid_cfg = { + callback = true, +} + +-- Configuration keys valid for a single match statement local match_valid_cfg = { callback = true, timeout = true, @@ -423,17 +429,52 @@ local extra_actions = { }, match = { print_diagnostics = function(action) + local ppat = "" + local first = true + + for pattern in pairs(action.patterns) do + if not first then + ppat = ppat .. "|" + end + + ppat = ppat .. pattern + end + io.stderr:write(string.format("[%s]:%d: match (pattern '%s') failed\n", - action.src, action.line, action.pattern)) + action.src, action.line, ppat)) end, init = function(action, args) local pattern = args[1] - action.pattern = pattern action.timeout = action.ctx.timeout + if type(pattern) == "table" then + -- The table version allows any of the patterns + -- to trigger this match, while also allowing + for pat, cfg in pairs(pattern) do + if type(cfg) == "function" then + cfg = { callback = cfg } + pattern[pat] = cfg + end + + for k, v in pairs(cfg) do + if not match_pattern_valid_cfg[k] then + error(k .. " is not a valid pattern cfg field") + end + end + if action.matcher.compile then + cfg._compiled = action.matcher.compile(pattern) + end + end + + action.patterns = pattern + else + local patterns = { [pattern] = {} } + + if action.matcher.compile then + patterns[pattern]._compiled = action.matcher.compile(pattern) + end - if action.matcher.compile then - action.pattern_obj = action.matcher.compile(pattern) + action.patterns = patterns end local function set_cfg(cfg)