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/porch.lua b/lib/porch.lua index 6210db9..59cbaa3 100644 --- a/lib/porch.lua +++ b/lib/porch.lua @@ -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 diff --git a/lib/porch/actions.lua b/lib/porch/actions.lua index 17657fa..d655e7a 100644 --- a/lib/porch/actions.lua +++ b/lib/porch/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/porch/direct.lua b/lib/porch/direct.lua index 0a64413..2be1279 100644 --- a/lib/porch/direct.lua +++ b/lib/porch/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/porch/process.lua b/lib/porch/process.lua index f1526fe..aa2211c 100644 --- a/lib/porch/process.lua +++ b/lib/porch/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/porch/scripter.lua b/lib/porch/scripter.lua index 716f3a0..57761ad 100644 --- a/lib/porch/scripter.lua +++ b/lib/porch/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 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) diff --git a/tests/simple_multimatch.orch b/tests/simple_multimatch.orch new file mode 100644 index 0000000..bee2583 --- /dev/null +++ b/tests/simple_multimatch.orch @@ -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"