Regex: Introduce RegexProgram::ThreadedExecutor and add line end/begin impl

This commit is contained in:
Maxime Coste 2017-09-17 19:48:23 +09:00
parent a448e1e222
commit f9dc6774b9

View File

@ -299,99 +299,126 @@ void dump(ConstArrayView<char> program)
} }
} }
struct StepResult struct ThreadedExecutor
{ {
enum Result { Consumed, Matched, Failed } result; ThreadedExecutor(ConstArrayView<char> program) : m_program{program} {}
const char* next = nullptr;
};
StepResult step_thread(const char* inst, char c, const char* start, Vector<const char*>& threads) struct StepResult
{
while (true)
{ {
const RegexProgram::Op op = (RegexProgram::Op)*inst++; enum Result { Consumed, Matched, Failed } result;
switch (op) const char* next = nullptr;
};
StepResult step(const char* inst)
{
while (true)
{ {
case RegexProgram::Literal: auto c = m_pos == m_subject.end() ? 0 : *m_pos;
if (*inst++ == c) const RegexProgram::Op op = (RegexProgram::Op)*inst++;
return { StepResult::Consumed, inst }; switch (op)
return { StepResult::Failed };
case RegexProgram::AnyChar:
return { StepResult::Consumed, inst };
case RegexProgram::Jump:
inst = start + *reinterpret_cast<const RegexProgram::Offset*>(inst);
break;
case RegexProgram::Split:
{ {
const int count = *inst++; case RegexProgram::Literal:
auto* offsets = reinterpret_cast<const RegexProgram::Offset*>(inst); if (*inst++ == c)
for (int o = 1; o < count; ++o) return { StepResult::Consumed, inst };
threads.push_back(start + offsets[o]); return { StepResult::Failed };
inst = start + offsets[0]; case RegexProgram::AnyChar:
break; return { StepResult::Consumed, inst };
case RegexProgram::Jump:
inst = m_program.begin() + *reinterpret_cast<const RegexProgram::Offset*>(inst);
break;
case RegexProgram::Split:
{
const int count = *inst++;
auto* offsets = reinterpret_cast<const RegexProgram::Offset*>(inst);
for (int o = 1; o < count; ++o)
m_threads.push_back(m_program.begin() + offsets[o]);
inst = m_program.begin() + offsets[0];
break;
}
case RegexProgram::LineStart:
if (not is_line_start())
return { StepResult::Failed };
break;
case RegexProgram::LineEnd:
if (not is_line_end())
return { StepResult::Failed };
break;
case RegexProgram::Match:
return { StepResult::Matched };
} }
case RegexProgram::LineStart:
// TODO
break;
case RegexProgram::LineEnd:
// TODO
break;
case RegexProgram::Match:
return { StepResult::Matched };
} }
return { StepResult::Failed };
} }
return { StepResult::Failed };
}
bool match(ConstArrayView<char> program, StringView data) bool match(ConstArrayView<char> program, StringView data)
{
const char* start = program.begin();
Vector<const char*> threads = { start };
for (auto c : data)
{ {
for (int i = 0; i < threads.size(); ++i) m_threads = Vector<const char*>{program.begin()};
m_subject = data;
m_pos = data.begin();
for (m_pos = m_subject.begin(); m_pos != m_subject.end(); ++m_pos)
{ {
auto res = step_thread(threads[i], c, start, threads); for (int i = 0; i < m_threads.size(); ++i)
threads[i] = res.next; {
if (res.result == StepResult::Matched) auto res = step(m_threads[i]);
m_threads[i] = res.next;
if (res.result == StepResult::Matched)
return true;
}
m_threads.erase(std::remove(m_threads.begin(), m_threads.end(), nullptr), m_threads.end());
if (m_threads.empty())
break;
}
// Step remaining threads to see if they match without consuming anything else
for (int i = 0; i < m_threads.size(); ++i)
{
if (step(m_threads[i]).result == StepResult::Matched)
return true; return true;
} }
threads.erase(std::remove(threads.begin(), threads.end(), nullptr), threads.end()); return false;
if (threads.empty())
break;
} }
// Step remaining threads to see if they match without consuming anything else bool is_line_start() const
for (int i = 0; i < threads.size(); ++i)
{ {
if (step_thread(threads[i], 0, start, threads).result == StepResult::Matched) return m_pos == m_subject.begin() or *(m_pos-1) == '\n';
return true;
} }
return false;
} bool is_line_end() const
{
return m_pos == m_subject.end() or *m_pos == '\n';
}
ConstArrayView<char> m_program;
Vector<const char*> m_threads;
StringView m_subject;
const char* m_pos;
};
} }
auto test_regex = UnitTest{[]{ auto test_regex = UnitTest{[]{
using Exec = RegexProgram::ThreadedExecutor;
{ {
StringView re = "a*b"; StringView re = "a*b";
auto program = RegexCompiler::compile(re.begin(), re.end()); auto program = RegexCompiler::compile(re.begin(), re.end());
RegexProgram::dump(program); RegexProgram::dump(program);
kak_assert(RegexProgram::match(program, "b")); Exec exec{program};
kak_assert(RegexProgram::match(program, "ab")); kak_assert(exec.match(program, "b"));
kak_assert(RegexProgram::match(program, "aaab")); kak_assert(exec.match(program, "ab"));
kak_assert(not RegexProgram::match(program, "acb")); kak_assert(exec.match(program, "aaab"));
kak_assert(not RegexProgram::match(program, "")); kak_assert(not exec.match(program, "acb"));
kak_assert(not exec.match(program, ""));
} }
{ {
StringView re = "^(foo|qux)+(bar)?baz$"; StringView re = "^(foo|qux)+(bar)?baz$";
auto program = RegexCompiler::compile(re.begin(), re.end()); auto program = RegexCompiler::compile(re.begin(), re.end());
RegexProgram::dump(program); RegexProgram::dump(program);
kak_assert(RegexProgram::match(program, "fooquxbarbaz")); Exec exec{program};
kak_assert(not RegexProgram::match(program, "quxbar")); kak_assert(exec.match(program, "fooquxbarbaz"));
kak_assert(not RegexProgram::match(program, "blahblah")); kak_assert(not exec.match(program, "quxbar"));
kak_assert(RegexProgram::match(program, "foobaz")); kak_assert(not exec.match(program, "blahblah"));
kak_assert(RegexProgram::match(program, "quxbaz")); kak_assert(exec.match(program, "foobaz"));
kak_assert(exec.match(program, "quxbaz"));
} }
}}; }};