Skip to content

Commit

Permalink
lib: implement multi-match
Browse files Browse the repository at this point in the history
Allow match to take a table of pattern -> handlers instead, and match the
earliest and longest pattern that we find in the output buffer.
  • Loading branch information
kevans91 committed Aug 16, 2024
1 parent 34ac390 commit cfc7317
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 12 deletions.
30 changes: 30 additions & 0 deletions examples/cat-multi.orch
Original file line number Diff line number Diff line change
@@ -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,
}
5 changes: 4 additions & 1 deletion lib/porch.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ porch.run_script = scripter.run_script
porch.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.
porch.spawn = direct.spawn

-- Reset all of the state; this largely means resetting the scripting bits, as
Expand Down
35 changes: 33 additions & 2 deletions lib/porch/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions lib/porch/direct.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions lib/porch/process.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
49 changes: 45 additions & 4 deletions lib/porch/scripter.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 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)
Expand Down
14 changes: 14 additions & 0 deletions tests/simple_multimatch.orch
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- What we write to cat(1) should come straight back to us.
write "Hello\rPANIC: at the disco\r"

match {
PANIC = function()
exit(1)
end,
Hello = function()
-- Should trigger on Hello every time, since it's the earliest match.
end,
}

-- Make sure that we didn't flush PANIC from the buffer.
match "PANIC"

0 comments on commit cfc7317

Please sign in to comment.