Regex: Add initial support for character ranges

This commit is contained in:
Maxime Coste 2017-09-23 16:18:21 +09:00
parent 52678fafa1
commit 182b70cb0a

View File

@ -16,6 +16,8 @@ struct CompiledRegex
Match, Match,
Literal, Literal,
AnyChar, AnyChar,
CharRange,
NegativeCharRange,
Jump, Jump,
Split, Split,
Save, Save,
@ -68,6 +70,8 @@ enum class Op
{ {
Literal, Literal,
AnyChar, AnyChar,
CharRange,
NegativeCharRange,
Sequence, Sequence,
Alternation, Alternation,
LineStart, LineStart,
@ -88,28 +92,38 @@ struct AstNode
using AstNodePtr = std::unique_ptr<AstNode>; using AstNodePtr = std::unique_ptr<AstNode>;
struct CharRange { char min, max; };
struct ParsedRegex
{
AstNodePtr ast;
size_t capture_count;
Vector<Vector<CharRange>> ranges;
};
AstNodePtr make_ast_node(Op op, char value = -1, AstNodePtr make_ast_node(Op op, char value = -1,
Quantifier quantifier = {Quantifier::One}) Quantifier quantifier = {Quantifier::One})
{ {
return AstNodePtr{new AstNode{op, value, quantifier, {}}}; return AstNodePtr{new AstNode{op, value, quantifier, {}}};
} }
// Recursive descent parser based on naming using in the ECMAScript // Recursive descent parser based on naming used in the ECMAScript
// standard, although the syntax is not fully compatible. // standard, although the syntax is not fully compatible.
template<typename Iterator> template<typename Iterator>
struct Parser struct Parser
{ {
AstNodePtr parse(Iterator pos, Iterator end) static ParsedRegex parse(Iterator pos, Iterator end)
{ {
return disjunction(pos, end, 0); ParsedRegex res;
res.capture_count = 1;
res.ast = disjunction(res, pos, end, 0);
return res;
} }
size_t capture_count() const { return m_next_capture; }
private: private:
AstNodePtr disjunction(Iterator& pos, Iterator end, char capture = -1) static AstNodePtr disjunction(ParsedRegex& parsed_regex, Iterator& pos, Iterator end, char capture = -1)
{ {
AstNodePtr node = alternative(pos, end); AstNodePtr node = alternative(parsed_regex, pos, end);
if (pos == end or *pos != '|') if (pos == end or *pos != '|')
{ {
node->value = capture; node->value = capture;
@ -118,32 +132,32 @@ private:
AstNodePtr res = make_ast_node(Op::Alternation); AstNodePtr res = make_ast_node(Op::Alternation);
res->children.push_back(std::move(node)); res->children.push_back(std::move(node));
res->children.push_back(disjunction(++pos, end)); res->children.push_back(disjunction(parsed_regex, ++pos, end));
res->value = capture; res->value = capture;
return res; return res;
} }
AstNodePtr alternative(Iterator& pos, Iterator end) static AstNodePtr alternative(ParsedRegex& parsed_regex, Iterator& pos, Iterator end)
{ {
AstNodePtr res = make_ast_node(Op::Sequence); AstNodePtr res = make_ast_node(Op::Sequence);
while (auto node = term(pos, end)) while (auto node = term(parsed_regex, pos, end))
res->children.push_back(std::move(node)); res->children.push_back(std::move(node));
return res; return res;
} }
AstNodePtr term(Iterator& pos, Iterator end) static AstNodePtr term(ParsedRegex& parsed_regex, Iterator& pos, Iterator end)
{ {
if (auto node = assertion(pos, end)) if (auto node = assertion(parsed_regex, pos, end))
return node; return node;
if (auto node = atom(pos, end)) if (auto node = atom(parsed_regex, pos, end))
{ {
node->quantifier = quantifier(pos, end); node->quantifier = quantifier(parsed_regex, pos, end);
return node; return node;
} }
return nullptr; return nullptr;
} }
AstNodePtr assertion(Iterator& pos, Iterator end) static AstNodePtr assertion(ParsedRegex& parsed_regex, Iterator& pos, Iterator end)
{ {
switch (*pos) switch (*pos)
{ {
@ -165,7 +179,7 @@ private:
return nullptr; return nullptr;
} }
AstNodePtr atom(Iterator& pos, Iterator end) static AstNodePtr atom(ParsedRegex& parsed_regex, Iterator& pos, Iterator end)
{ {
const auto c = *pos; const auto c = *pos;
switch (c) switch (c)
@ -174,13 +188,19 @@ private:
case '(': case '(':
{ {
++pos; ++pos;
auto content = disjunction(pos, end, m_next_capture++); auto content = disjunction(parsed_regex, pos, end, parsed_regex.capture_count++);
if (pos == end or *pos != ')') if (pos == end or *pos != ')')
throw runtime_error{"Unclosed parenthesis"}; throw runtime_error{"Unclosed parenthesis"};
++pos; ++pos;
return content; return content;
} }
case '\\':
++pos;
return atom_escape(parsed_regex, pos, end);
case '[':
++pos;
return character_class(parsed_regex, pos, end);
default: default:
if (contains("^$.*+?()[]{}|", c)) if (contains("^$.*+?()[]{}|", c))
return nullptr; return nullptr;
@ -189,7 +209,67 @@ private:
} }
} }
Quantifier quantifier(Iterator& pos, Iterator end) static AstNodePtr atom_escape(ParsedRegex& parsed_regex, Iterator& pos, Iterator end)
{
const auto c = *pos;
struct { char name; char value; } control_escapes[] = {
{ 'f', '\f' }, { 'n', '\n' }, { 'r', '\r' }, { 't', '\t' }, { 'v', '\v' }
};
for (auto& control : control_escapes)
{
if (control.name == c)
return make_ast_node(Op::Literal, control.value);
}
// TOOD: \c..., \0..., '\0x...', \u...
if (contains("^$\\.*+?()[]{}|", c)) // SyntaxCharacter
return make_ast_node(Op::Literal, c);
throw runtime_error{"Unknown atom escape"};
}
static AstNodePtr character_class(ParsedRegex& parsed_regex, Iterator& pos, Iterator end)
{
const bool negative = pos != end and *pos == '^';
if (negative)
++pos;
Vector<CharRange> ranges;
while (pos != end and *pos != ']')
{
const auto c = *pos++;
if (c == '-')
{
ranges.push_back({ '-', 0 });
continue;
}
if (pos == end)
break;
CharRange range = { c, 0 };
if (*pos == '-')
{
if (++pos == end)
break;
range.max = *pos++;
if (range.min > range.max)
throw runtime_error{"Invalid range specified"};
}
ranges.push_back(range);
}
if (pos == end)
throw runtime_error{"Unclosed character class"};
++pos;
auto ranges_id = parsed_regex.ranges.size();
parsed_regex.ranges.push_back(std::move(ranges));
return make_ast_node(negative ? Op::NegativeCharRange : Op::CharRange, ranges_id);
}
static Quantifier quantifier(ParsedRegex& parsed_regex, Iterator& pos, Iterator end)
{ {
auto read_int = [](Iterator& pos, Iterator begin, Iterator end) { auto read_int = [](Iterator& pos, Iterator begin, Iterator end) {
int res = 0; int res = 0;
@ -226,12 +306,8 @@ private:
default: return {Quantifier::One}; default: return {Quantifier::One};
} }
} }
char m_next_capture = 1;
}; };
CompiledRegex::Offset compile_node(CompiledRegex& program, const AstNodePtr& node);
CompiledRegex::Offset alloc_offset(CompiledRegex& program) CompiledRegex::Offset alloc_offset(CompiledRegex& program)
{ {
auto pos = program.bytecode.size(); auto pos = program.bytecode.size();
@ -244,7 +320,9 @@ CompiledRegex::Offset& get_offset(CompiledRegex& program, CompiledRegex::Offset
return *reinterpret_cast<CompiledRegex::Offset*>(&program.bytecode[pos]); return *reinterpret_cast<CompiledRegex::Offset*>(&program.bytecode[pos]);
} }
CompiledRegex::Offset compile_node_inner(CompiledRegex& program, const AstNodePtr& node) CompiledRegex::Offset compile_node(CompiledRegex& program, const ParsedRegex& parsed_regex, const AstNodePtr& node);
CompiledRegex::Offset compile_node_inner(CompiledRegex& program, const ParsedRegex& parsed_regex, const AstNodePtr& node)
{ {
const auto start_pos = program.bytecode.size(); const auto start_pos = program.bytecode.size();
@ -265,9 +343,35 @@ CompiledRegex::Offset compile_node_inner(CompiledRegex& program, const AstNodePt
case Op::AnyChar: case Op::AnyChar:
program.bytecode.push_back(CompiledRegex::AnyChar); program.bytecode.push_back(CompiledRegex::AnyChar);
break; break;
case Op::CharRange: case Op::NegativeCharRange:
{
auto& ranges = parsed_regex.ranges[node->value];
size_t single_count = std::count_if(ranges.begin(), ranges.end(),
[](auto& r) { return r.max == 0; });
program.bytecode.push_back(node->op == Op::CharRange ?
CompiledRegex::CharRange
: CompiledRegex::NegativeCharRange);
program.bytecode.push_back((char)single_count);
program.bytecode.push_back((char)(ranges.size() - single_count));
for (auto& r : ranges)
{
if (r.max == 0)
program.bytecode.push_back(r.min);
}
for (auto& r : ranges)
{
if (r.max != 0)
{
program.bytecode.push_back(r.min);
program.bytecode.push_back(r.max);
}
}
break;
}
case Op::Sequence: case Op::Sequence:
for (auto& child : node->children) for (auto& child : node->children)
compile_node(program, child); compile_node(program, parsed_regex, child);
break; break;
case Op::Alternation: case Op::Alternation:
{ {
@ -277,11 +381,11 @@ CompiledRegex::Offset compile_node_inner(CompiledRegex& program, const AstNodePt
program.bytecode.push_back(CompiledRegex::Split); program.bytecode.push_back(CompiledRegex::Split);
auto offset = alloc_offset(program); auto offset = alloc_offset(program);
compile_node(program, children[0]); compile_node(program, parsed_regex, children[0]);
program.bytecode.push_back(CompiledRegex::Jump); program.bytecode.push_back(CompiledRegex::Jump);
goto_inner_end_offsets.push_back(alloc_offset(program)); goto_inner_end_offsets.push_back(alloc_offset(program));
auto right_pos = compile_node(program, children[1]); auto right_pos = compile_node(program, parsed_regex, children[1]);
get_offset(program, offset) = right_pos; get_offset(program, offset) = right_pos;
break; break;
@ -318,7 +422,7 @@ CompiledRegex::Offset compile_node_inner(CompiledRegex& program, const AstNodePt
return start_pos; return start_pos;
} }
CompiledRegex::Offset compile_node(CompiledRegex& program, const AstNodePtr& node) CompiledRegex::Offset compile_node(CompiledRegex& program, const ParsedRegex& parsed_regex, const AstNodePtr& node)
{ {
CompiledRegex::Offset pos = program.bytecode.size(); CompiledRegex::Offset pos = program.bytecode.size();
Vector<CompiledRegex::Offset> goto_end_offsets; Vector<CompiledRegex::Offset> goto_end_offsets;
@ -329,10 +433,10 @@ CompiledRegex::Offset compile_node(CompiledRegex& program, const AstNodePtr& nod
goto_end_offsets.push_back(alloc_offset(program)); goto_end_offsets.push_back(alloc_offset(program));
} }
auto inner_pos = compile_node_inner(program, node); auto inner_pos = compile_node_inner(program, parsed_regex, node);
// Write the node multiple times when we have a min count quantifier // Write the node multiple times when we have a min count quantifier
for (int i = 1; i < node->quantifier.min; ++i) for (int i = 1; i < node->quantifier.min; ++i)
inner_pos = compile_node_inner(program, node); inner_pos = compile_node_inner(program, parsed_regex, node);
if (node->quantifier.allows_infinite_repeat()) if (node->quantifier.allows_infinite_repeat())
{ {
@ -345,7 +449,7 @@ CompiledRegex::Offset compile_node(CompiledRegex& program, const AstNodePtr& nod
{ {
program.bytecode.push_back(CompiledRegex::Split); program.bytecode.push_back(CompiledRegex::Split);
goto_end_offsets.push_back(alloc_offset(program)); goto_end_offsets.push_back(alloc_offset(program));
compile_node_inner(program, node); compile_node_inner(program, parsed_regex, node);
} }
for (auto offset : goto_end_offsets) for (auto offset : goto_end_offsets)
@ -367,22 +471,20 @@ void write_search_prefix(CompiledRegex& program)
get_offset(program, alloc_offset(program)) = 1 + sizeof(CompiledRegex::Offset); get_offset(program, alloc_offset(program)) = 1 + sizeof(CompiledRegex::Offset);
} }
CompiledRegex compile(const AstNodePtr& node, size_t capture_count) CompiledRegex compile(const ParsedRegex& parsed_regex)
{ {
CompiledRegex res; CompiledRegex res;
write_search_prefix(res); write_search_prefix(res);
compile_node(res, node); compile_node(res, parsed_regex, parsed_regex.ast);
res.bytecode.push_back(CompiledRegex::Match); res.bytecode.push_back(CompiledRegex::Match);
res.save_count = capture_count * 2; res.save_count = parsed_regex.capture_count * 2;
return res; return res;
} }
template<typename Iterator> template<typename Iterator>
CompiledRegex compile(Iterator begin, Iterator end) CompiledRegex compile(Iterator begin, Iterator end)
{ {
Parser<Iterator> parser; return compile(Parser<Iterator>::parse(begin, end));
auto node = parser.parse(begin, end);
return compile(node, parser.capture_count());
} }
} }
@ -392,7 +494,8 @@ void dump(const CompiledRegex& program)
for (auto pos = program.bytecode.begin(); pos < program.bytecode.end(); ) for (auto pos = program.bytecode.begin(); pos < program.bytecode.end(); )
{ {
printf("%4zd ", pos - program.bytecode.begin()); printf("%4zd ", pos - program.bytecode.begin());
switch ((CompiledRegex::Op)*pos++) const auto op = (CompiledRegex::Op)*pos++;
switch (op)
{ {
case CompiledRegex::Literal: case CompiledRegex::Literal:
printf("literal %c\n", *pos++); printf("literal %c\n", *pos++);
@ -413,6 +516,24 @@ void dump(const CompiledRegex& program)
case CompiledRegex::Save: case CompiledRegex::Save:
printf("save %d\n", *pos++); printf("save %d\n", *pos++);
break; break;
case CompiledRegex::CharRange: case CompiledRegex::NegativeCharRange:
{
printf("%schar range, [", op == CompiledRegex::NegativeCharRange ? "negative " : "");
auto single_count = *pos++;
auto range_count = *pos++;
for (int i = 0; i < single_count; ++i)
printf("%c", *pos++);
printf("]");
for (int i = 0; i < range_count; ++i)
{
auto min = *pos++;
auto max = *pos++;
printf(" [%c-%c]", min, max);
}
printf("\n");
break;
}
case CompiledRegex::LineStart: case CompiledRegex::LineStart:
printf("line start\n"); printf("line start\n");
break; break;
@ -486,6 +607,33 @@ struct ThreadedRegexVM
thread.saves[index] = m_pos; thread.saves[index] = m_pos;
break; break;
} }
case CompiledRegex::CharRange: case CompiledRegex::NegativeCharRange:
{
auto single_count = *thread.inst++;
auto range_count = *thread.inst++;
const char* end = thread.inst + single_count + 2 * range_count;
for (int i = 0; i < single_count; ++i)
{
auto candidate = *thread.inst++;
if (c == candidate)
{
thread.inst = end;
return op == CompiledRegex::CharRange ? StepResult::Consumed : StepResult::Failed;
}
}
for (int i = 0; i < range_count; ++i)
{
auto min = *thread.inst++;
auto max = *thread.inst++;
if (min <= c and c <= max)
{
thread.inst = end;
return op == CompiledRegex::CharRange ? StepResult::Consumed : StepResult::Failed;
}
}
kak_assert(thread.inst == end);
return op == CompiledRegex::CharRange ? StepResult::Failed : StepResult::Consumed;
}
case CompiledRegex::LineStart: case CompiledRegex::LineStart:
if (not is_line_start()) if (not is_line_start())
return StepResult::Failed; return StepResult::Failed;
@ -690,6 +838,17 @@ auto test_regex = UnitTest{[]{
kak_assert(vm.exec("mais que fais la police", false)); kak_assert(vm.exec("mais que fais la police", false));
kak_assert(StringView{vm.m_captures[0], vm.m_captures[1]} == "fa"); kak_assert(StringView{vm.m_captures[0], vm.m_captures[1]} == "fa");
} }
{
StringView re = R"([ab-dX-Z]{3,5})";
auto program = RegexCompiler::compile(re.begin(), re.end());
dump(program);
ThreadedRegexVM vm{program};
kak_assert(vm.exec("acY"));
kak_assert(not vm.exec("aeY"));
kak_assert(vm.exec("abcdX"));
kak_assert(not vm.exec("efg"));
}
}}; }};
} }