2017-09-17 10:50:53 +02:00
|
|
|
#ifndef regex_impl_hh_INCLUDED
|
|
|
|
#define regex_impl_hh_INCLUDED
|
|
|
|
|
2017-10-02 08:59:04 +02:00
|
|
|
#include "unicode.hh"
|
|
|
|
#include "utf8.hh"
|
|
|
|
#include "utf8_iterator.hh"
|
|
|
|
#include "vector.hh"
|
2017-10-02 16:34:57 +02:00
|
|
|
#include "flags.hh"
|
2017-10-02 08:59:04 +02:00
|
|
|
|
2017-09-26 09:44:30 +02:00
|
|
|
namespace Kakoune
|
|
|
|
{
|
|
|
|
|
2017-10-02 08:59:04 +02:00
|
|
|
struct CompiledRegex
|
|
|
|
{
|
|
|
|
enum Op : char
|
|
|
|
{
|
|
|
|
Match,
|
|
|
|
Literal,
|
|
|
|
LiteralIgnoreCase,
|
|
|
|
AnyChar,
|
|
|
|
Matcher,
|
|
|
|
Jump,
|
|
|
|
Split_PrioritizeParent,
|
|
|
|
Split_PrioritizeChild,
|
|
|
|
Save,
|
|
|
|
LineStart,
|
|
|
|
LineEnd,
|
|
|
|
WordBoundary,
|
|
|
|
NotWordBoundary,
|
|
|
|
SubjectBegin,
|
|
|
|
SubjectEnd,
|
|
|
|
LookAhead,
|
|
|
|
LookBehind,
|
|
|
|
NegativeLookAhead,
|
|
|
|
NegativeLookBehind,
|
|
|
|
};
|
|
|
|
|
|
|
|
using Offset = unsigned;
|
|
|
|
static constexpr Offset search_prefix_size = 3 + 2 * sizeof(Offset);
|
|
|
|
|
|
|
|
explicit operator bool() const { return not bytecode.empty(); }
|
|
|
|
|
|
|
|
Vector<char> bytecode;
|
|
|
|
Vector<std::function<bool (Codepoint)>> matchers;
|
|
|
|
size_t save_count;
|
|
|
|
};
|
|
|
|
|
|
|
|
CompiledRegex compile_regex(StringView re);
|
|
|
|
|
2017-10-02 16:34:57 +02:00
|
|
|
enum class RegexExecFlags
|
|
|
|
{
|
|
|
|
None = 0,
|
|
|
|
Search = 1 << 0,
|
|
|
|
NotBeginOfLine = 1 << 1,
|
|
|
|
NotEndOfLine = 1 << 2,
|
|
|
|
NotBeginOfWord = 1 << 3,
|
|
|
|
NotEndOfWord = 1 << 4,
|
|
|
|
NotBeginOfSubject = 1 << 5,
|
|
|
|
NotInitialNull = 1 << 6,
|
|
|
|
AnyMatch = 1 << 7
|
|
|
|
};
|
|
|
|
|
|
|
|
constexpr bool with_bit_ops(Meta::Type<RegexExecFlags>) { return true; }
|
|
|
|
|
2017-10-02 08:59:04 +02:00
|
|
|
template<typename Iterator>
|
|
|
|
struct ThreadedRegexVM
|
|
|
|
{
|
|
|
|
ThreadedRegexVM(const CompiledRegex& program)
|
|
|
|
: m_program{program} { kak_assert(m_program); }
|
|
|
|
|
|
|
|
struct Thread
|
|
|
|
{
|
|
|
|
const char* inst;
|
|
|
|
Vector<Iterator> saves = {};
|
|
|
|
};
|
|
|
|
|
|
|
|
enum class StepResult { Consumed, Matched, Failed };
|
|
|
|
StepResult step(size_t thread_index)
|
|
|
|
{
|
|
|
|
const auto prog_start = m_program.bytecode.data();
|
|
|
|
const auto prog_end = prog_start + m_program.bytecode.size();
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
auto& thread = m_threads[thread_index];
|
|
|
|
const Codepoint cp = m_pos == m_end ? 0 : *m_pos;
|
|
|
|
const CompiledRegex::Op op = (CompiledRegex::Op)*thread.inst++;
|
|
|
|
switch (op)
|
|
|
|
{
|
|
|
|
case CompiledRegex::Literal:
|
|
|
|
if (utf8::read_codepoint(thread.inst, prog_end) == cp)
|
|
|
|
return StepResult::Consumed;
|
|
|
|
return StepResult::Failed;
|
|
|
|
case CompiledRegex::LiteralIgnoreCase:
|
|
|
|
if (utf8::read_codepoint(thread.inst, prog_end) == to_lower(cp))
|
|
|
|
return StepResult::Consumed;
|
|
|
|
return StepResult::Failed;
|
|
|
|
case CompiledRegex::AnyChar:
|
|
|
|
return StepResult::Consumed;
|
|
|
|
case CompiledRegex::Jump:
|
|
|
|
{
|
|
|
|
auto inst = prog_start + *reinterpret_cast<const CompiledRegex::Offset*>(thread.inst);
|
|
|
|
// if instruction is already going to be executed by another thread, drop this thread
|
|
|
|
if (std::find_if(m_threads.begin(), m_threads.end(),
|
|
|
|
[inst](const Thread& t) { return t.inst == inst; }) != m_threads.end())
|
|
|
|
return StepResult::Failed;
|
|
|
|
thread.inst = inst;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case CompiledRegex::Split_PrioritizeParent:
|
|
|
|
{
|
2017-10-02 14:16:32 +02:00
|
|
|
auto new_thread_inst = prog_start + *reinterpret_cast<const CompiledRegex::Offset*>(thread.inst);
|
|
|
|
thread.inst += sizeof(CompiledRegex::Offset);
|
|
|
|
add_thread(thread_index+1, new_thread_inst, thread.saves);
|
2017-10-02 08:59:04 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case CompiledRegex::Split_PrioritizeChild:
|
|
|
|
{
|
2017-10-02 14:16:32 +02:00
|
|
|
auto new_thread_inst = thread.inst + sizeof(CompiledRegex::Offset);
|
|
|
|
thread.inst = prog_start + *reinterpret_cast<const CompiledRegex::Offset*>(thread.inst);
|
|
|
|
add_thread(thread_index+1, new_thread_inst, thread.saves);
|
2017-10-02 08:59:04 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case CompiledRegex::Save:
|
|
|
|
{
|
|
|
|
const char index = *thread.inst++;
|
|
|
|
thread.saves[index] = m_pos.base();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case CompiledRegex::Matcher:
|
|
|
|
{
|
|
|
|
const int matcher_id = *thread.inst++;
|
|
|
|
return m_program.matchers[matcher_id](*m_pos) ?
|
|
|
|
StepResult::Consumed : StepResult::Failed;
|
|
|
|
}
|
|
|
|
case CompiledRegex::LineStart:
|
|
|
|
if (not is_line_start())
|
|
|
|
return StepResult::Failed;
|
|
|
|
break;
|
|
|
|
case CompiledRegex::LineEnd:
|
|
|
|
if (not is_line_end())
|
|
|
|
return StepResult::Failed;
|
|
|
|
break;
|
|
|
|
case CompiledRegex::WordBoundary:
|
|
|
|
if (not is_word_boundary())
|
|
|
|
return StepResult::Failed;
|
|
|
|
break;
|
|
|
|
case CompiledRegex::NotWordBoundary:
|
|
|
|
if (is_word_boundary())
|
|
|
|
return StepResult::Failed;
|
|
|
|
break;
|
|
|
|
case CompiledRegex::SubjectBegin:
|
2017-10-02 16:34:57 +02:00
|
|
|
if (m_pos != m_begin or m_flags & RegexExecFlags::NotBeginOfSubject)
|
2017-10-02 08:59:04 +02:00
|
|
|
return StepResult::Failed;
|
|
|
|
break;
|
|
|
|
case CompiledRegex::SubjectEnd:
|
|
|
|
if (m_pos != m_end)
|
|
|
|
return StepResult::Failed;
|
|
|
|
break;
|
|
|
|
case CompiledRegex::LookAhead:
|
|
|
|
case CompiledRegex::NegativeLookAhead:
|
|
|
|
{
|
|
|
|
int count = *thread.inst++;
|
|
|
|
for (auto it = m_pos; count and it != m_end; ++it, --count)
|
|
|
|
if (*it != utf8::read(thread.inst))
|
|
|
|
break;
|
|
|
|
if ((op == CompiledRegex::LookAhead and count != 0) or
|
|
|
|
(op == CompiledRegex::NegativeLookAhead and count == 0))
|
|
|
|
return StepResult::Failed;
|
|
|
|
thread.inst = utf8::advance(thread.inst, prog_end, CharCount{count - 1});
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case CompiledRegex::LookBehind:
|
|
|
|
case CompiledRegex::NegativeLookBehind:
|
|
|
|
{
|
|
|
|
int count = *thread.inst++;
|
|
|
|
for (auto it = m_pos-1; count and it >= m_begin; --it, --count)
|
|
|
|
if (*it != utf8::read(thread.inst))
|
|
|
|
break;
|
|
|
|
if ((op == CompiledRegex::LookBehind and count != 0) or
|
|
|
|
(op == CompiledRegex::NegativeLookBehind and count == 0))
|
|
|
|
return StepResult::Failed;
|
|
|
|
thread.inst = utf8::advance(thread.inst, prog_end, CharCount{count - 1});
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case CompiledRegex::Match:
|
|
|
|
return StepResult::Matched;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return StepResult::Failed;
|
|
|
|
}
|
|
|
|
|
2017-10-02 16:34:57 +02:00
|
|
|
bool exec(Iterator begin, Iterator end, RegexExecFlags flags)
|
2017-10-02 08:59:04 +02:00
|
|
|
{
|
|
|
|
bool found_match = false;
|
|
|
|
m_threads.clear();
|
2017-10-02 16:34:57 +02:00
|
|
|
const auto start_offset = (flags & RegexExecFlags::Search) ? 0 : CompiledRegex::search_prefix_size;
|
2017-10-02 14:16:32 +02:00
|
|
|
add_thread(0, m_program.bytecode.data() + start_offset,
|
2017-10-02 08:59:04 +02:00
|
|
|
Vector<Iterator>(m_program.save_count, Iterator{}));
|
|
|
|
|
|
|
|
m_begin = begin;
|
|
|
|
m_end = end;
|
2017-10-02 16:34:57 +02:00
|
|
|
m_flags = flags;
|
|
|
|
|
|
|
|
if (flags & RegexExecFlags::NotInitialNull and m_begin == m_end)
|
|
|
|
return false;
|
2017-10-02 08:59:04 +02:00
|
|
|
|
|
|
|
for (m_pos = Utf8It{m_begin, m_begin, m_end}; m_pos != m_end; ++m_pos)
|
|
|
|
{
|
2017-10-02 10:24:38 +02:00
|
|
|
for (int i = 0; i < m_threads.size(); )
|
2017-10-02 08:59:04 +02:00
|
|
|
{
|
|
|
|
const auto res = step(i);
|
|
|
|
if (res == StepResult::Matched)
|
|
|
|
{
|
2017-10-02 16:34:57 +02:00
|
|
|
if (not (flags & RegexExecFlags::Search) or // We are not at end, this is not a full match
|
|
|
|
(flags & RegexExecFlags::NotInitialNull and m_pos == m_begin))
|
2017-10-02 10:24:38 +02:00
|
|
|
{
|
|
|
|
m_threads.erase(m_threads.begin() + i);
|
2017-10-02 16:34:57 +02:00
|
|
|
continue;
|
2017-10-02 10:24:38 +02:00
|
|
|
}
|
2017-10-02 08:59:04 +02:00
|
|
|
|
|
|
|
m_captures = std::move(m_threads[i].saves);
|
2017-10-02 16:34:57 +02:00
|
|
|
if (flags & RegexExecFlags::AnyMatch)
|
|
|
|
return true;
|
|
|
|
|
2017-10-02 08:59:04 +02:00
|
|
|
found_match = true;
|
|
|
|
m_threads.resize(i); // remove this and lower priority threads
|
|
|
|
}
|
|
|
|
else if (res == StepResult::Failed)
|
2017-10-02 10:24:38 +02:00
|
|
|
m_threads.erase(m_threads.begin() + i);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto it = m_threads.begin() + i;
|
|
|
|
if (std::find_if(m_threads.begin(), it, [inst = it->inst](auto& t)
|
|
|
|
{ return t.inst == inst; }) != it)
|
|
|
|
m_threads.erase(it);
|
|
|
|
else
|
|
|
|
++i;
|
|
|
|
}
|
2017-10-02 08:59:04 +02:00
|
|
|
}
|
2017-10-02 10:24:38 +02:00
|
|
|
// we should never have more than one thread on the same instruction
|
|
|
|
kak_assert(m_threads.size() <= m_program.bytecode.size());
|
2017-10-02 08:59:04 +02:00
|
|
|
if (m_threads.empty())
|
|
|
|
return found_match;
|
|
|
|
}
|
2017-10-02 19:16:30 +02:00
|
|
|
if (found_match)
|
|
|
|
return true;
|
2017-10-02 08:59:04 +02:00
|
|
|
|
|
|
|
// Step remaining threads to see if they match without consuming anything else
|
|
|
|
for (int i = 0; i < m_threads.size(); ++i)
|
|
|
|
{
|
|
|
|
if (step(i) == StepResult::Matched)
|
|
|
|
{
|
|
|
|
m_captures = std::move(m_threads[i].saves);
|
2017-10-02 19:16:30 +02:00
|
|
|
return true;
|
2017-10-02 08:59:04 +02:00
|
|
|
}
|
|
|
|
}
|
2017-10-02 19:16:30 +02:00
|
|
|
return false;
|
2017-10-02 08:59:04 +02:00
|
|
|
}
|
|
|
|
|
2017-10-02 14:16:32 +02:00
|
|
|
void add_thread(int index, const char* inst, Vector<Iterator> saves)
|
2017-10-02 08:59:04 +02:00
|
|
|
{
|
|
|
|
if (std::find_if(m_threads.begin(), m_threads.end(),
|
|
|
|
[inst](const Thread& t) { return t.inst == inst; }) == m_threads.end())
|
|
|
|
m_threads.insert(m_threads.begin() + index, {inst, std::move(saves)});
|
2017-10-02 10:24:38 +02:00
|
|
|
kak_assert(m_threads.size() < m_program.bytecode.size());
|
2017-10-02 08:59:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool is_line_start() const
|
|
|
|
{
|
2017-10-02 16:34:57 +02:00
|
|
|
return (m_pos == m_begin and not (m_flags & RegexExecFlags::NotBeginOfLine)) or
|
|
|
|
*(m_pos-1) == '\n';
|
2017-10-02 08:59:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool is_line_end() const
|
|
|
|
{
|
2017-10-02 16:34:57 +02:00
|
|
|
return (m_pos == m_end and not (m_flags & RegexExecFlags::NotEndOfLine)) or
|
|
|
|
*m_pos == '\n';
|
2017-10-02 08:59:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool is_word_boundary() const
|
|
|
|
{
|
2017-10-02 16:34:57 +02:00
|
|
|
return (m_pos == m_begin and not (m_flags & RegexExecFlags::NotBeginOfWord)) or
|
|
|
|
(m_pos == m_end and not (m_flags & RegexExecFlags::NotEndOfWord)) or
|
2017-10-02 08:59:04 +02:00
|
|
|
is_word(*(m_pos-1)) != is_word(*m_pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
const CompiledRegex& m_program;
|
|
|
|
Vector<Thread> m_threads;
|
|
|
|
|
|
|
|
using Utf8It = utf8::iterator<Iterator>;
|
|
|
|
|
|
|
|
Iterator m_begin;
|
|
|
|
Iterator m_end;
|
|
|
|
Utf8It m_pos;
|
2017-10-02 16:34:57 +02:00
|
|
|
RegexExecFlags m_flags;
|
2017-10-02 08:59:04 +02:00
|
|
|
|
|
|
|
Vector<Iterator> m_captures;
|
|
|
|
};
|
|
|
|
|
|
|
|
template<typename It>
|
2017-10-02 16:34:57 +02:00
|
|
|
bool regex_match(It begin, It end, const CompiledRegex& re, RegexExecFlags flags = RegexExecFlags::None)
|
2017-10-02 08:59:04 +02:00
|
|
|
{
|
|
|
|
ThreadedRegexVM<It> vm{re};
|
2017-10-02 16:34:57 +02:00
|
|
|
return vm.exec(begin, end, (RegexExecFlags)(flags & ~(RegexExecFlags::Search)) | RegexExecFlags::AnyMatch);
|
2017-10-02 08:59:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
template<typename It>
|
2017-10-02 16:34:57 +02:00
|
|
|
bool regex_match(It begin, It end, Vector<It>& captures, const CompiledRegex& re,
|
|
|
|
RegexExecFlags flags = RegexExecFlags::None)
|
2017-10-02 08:59:04 +02:00
|
|
|
{
|
|
|
|
ThreadedRegexVM<It> vm{re};
|
2017-10-02 16:34:57 +02:00
|
|
|
if (vm.exec(begin, end, flags & ~(RegexExecFlags::Search)))
|
2017-10-02 08:59:04 +02:00
|
|
|
{
|
|
|
|
captures = std::move(vm.m_captures);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename It>
|
2017-10-02 16:34:57 +02:00
|
|
|
bool regex_search(It begin, It end, const CompiledRegex& re,
|
|
|
|
RegexExecFlags flags = RegexExecFlags::None)
|
2017-10-02 08:59:04 +02:00
|
|
|
{
|
|
|
|
ThreadedRegexVM<It> vm{re};
|
2017-10-02 16:34:57 +02:00
|
|
|
return vm.exec(begin, end, flags | RegexExecFlags::Search | RegexExecFlags::AnyMatch);
|
2017-10-02 08:59:04 +02:00
|
|
|
}
|
2017-09-26 09:44:30 +02:00
|
|
|
|
2017-10-02 08:59:04 +02:00
|
|
|
template<typename It>
|
2017-10-02 16:34:57 +02:00
|
|
|
bool regex_search(It begin, It end, Vector<It>& captures, const CompiledRegex& re,
|
|
|
|
RegexExecFlags flags = RegexExecFlags::None)
|
2017-10-02 08:59:04 +02:00
|
|
|
{
|
|
|
|
ThreadedRegexVM<It> vm{re};
|
2017-10-02 16:34:57 +02:00
|
|
|
if (vm.exec(begin, end, flags | RegexExecFlags::Search))
|
2017-10-02 08:59:04 +02:00
|
|
|
{
|
|
|
|
captures = std::move(vm.m_captures);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2017-09-26 09:44:30 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-09-17 10:50:53 +02:00
|
|
|
#endif // regex_impl_hh_INCLUDED
|