mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-04-30 19:09:26 +08:00
Fix #284: Add raw string support to avoid instring_eval
Add C++11-style raw string literals (R"delimiter(content)delimiter") to
ChaiScript. Raw strings do not process escape sequences or perform string
interpolation, solving the issue where base85 encoded data containing ${
sequences would trigger unwanted instring_eval. The implementation adds
Raw_String_() and Raw_String() parser functions and hooks them into the
expression parser.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2eb3279c39
commit
34fea05bc2
@ -1363,6 +1363,109 @@ namespace chaiscript {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads a raw string from input R"delimiter(content)delimiter", without skipping initial whitespace
|
||||
bool Raw_String_() {
|
||||
if (m_position.has_more() && (*m_position == 'R') && m_position.remaining() > 1 && *(m_position + 1) == '"') {
|
||||
auto s = m_position + 2;
|
||||
|
||||
// Read the delimiter (may be empty)
|
||||
std::string delimiter;
|
||||
while (s.has_more() && *s != '(' && *s != '\n') {
|
||||
delimiter.push_back(*s);
|
||||
++s;
|
||||
}
|
||||
|
||||
if (!s.has_more() || *s != '(') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip the opening '('
|
||||
++s;
|
||||
|
||||
// Build the closing sequence: )delimiter"
|
||||
std::string close_seq = ")";
|
||||
close_seq += delimiter;
|
||||
close_seq += '"';
|
||||
|
||||
// Search for the closing sequence
|
||||
while (s.has_more()) {
|
||||
if (*s == close_seq[0]) {
|
||||
// Check if we match the full closing sequence
|
||||
auto t = s;
|
||||
std::size_t i = 0;
|
||||
while (t.has_more() && i < close_seq.size() && *t == close_seq[i]) {
|
||||
++t;
|
||||
++i;
|
||||
}
|
||||
if (i == close_seq.size()) {
|
||||
// Found the closing sequence
|
||||
m_position = t;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (*s == '\n') {
|
||||
m_position.col = 1;
|
||||
}
|
||||
++s;
|
||||
}
|
||||
|
||||
throw exception::eval_error("Unclosed raw string", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Reads and captures a raw string from input. No escape processing or interpolation.
|
||||
bool Raw_String() {
|
||||
Depth_Counter dc{this};
|
||||
SkipWS();
|
||||
|
||||
const auto start = m_position;
|
||||
|
||||
if (Raw_String_()) {
|
||||
// Extract content between R"delimiter( and )delimiter"
|
||||
auto s = start + 2; // skip R"
|
||||
|
||||
// Skip delimiter
|
||||
std::string delimiter;
|
||||
while (*s != '(') {
|
||||
delimiter.push_back(*s);
|
||||
++s;
|
||||
}
|
||||
++s; // skip '('
|
||||
|
||||
// Build closing sequence
|
||||
std::string close_seq = ")";
|
||||
close_seq += delimiter;
|
||||
close_seq += '"';
|
||||
|
||||
// Extract raw content up to closing sequence
|
||||
std::string match;
|
||||
auto end = m_position; // m_position is already past the closing sequence
|
||||
|
||||
// Content is from s up to (end - close_seq.size())
|
||||
// We need to find the closing sequence from s
|
||||
while (s.has_more()) {
|
||||
if (*s == close_seq[0]) {
|
||||
auto t = s;
|
||||
std::size_t i = 0;
|
||||
while (t.has_more() && i < close_seq.size() && *t == close_seq[i]) {
|
||||
++t;
|
||||
++i;
|
||||
}
|
||||
if (i == close_seq.size() && t == end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
match.push_back(*s);
|
||||
++s;
|
||||
}
|
||||
|
||||
m_match_stack.push_back(make_node<eval::Constant_AST_Node<Tracer>>(match, start.line, start.col, const_var(match)));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Reads a character group from input, without skipping initial whitespace
|
||||
bool Single_Quoted_String_() {
|
||||
bool retval = false;
|
||||
@ -2212,7 +2315,7 @@ namespace chaiscript {
|
||||
bool retval = false;
|
||||
|
||||
const auto prev_stack_top = m_match_stack.size();
|
||||
if (Lambda() || Num() || Quoted_String() || Single_Quoted_String() || Paren_Expression() || Inline_Container() || Id(false)) {
|
||||
if (Lambda() || Num() || Raw_String() || Quoted_String() || Single_Quoted_String() || Paren_Expression() || Inline_Container() || Id(false)) {
|
||||
retval = true;
|
||||
bool has_more = true;
|
||||
|
||||
|
||||
23
unittests/raw_string.chai
Normal file
23
unittests/raw_string.chai
Normal file
@ -0,0 +1,23 @@
|
||||
// Basic raw string
|
||||
assert_equal("hello", R"(hello)")
|
||||
|
||||
// Raw string with no interpolation
|
||||
assert_equal("\${5+5}", R"(${5+5})")
|
||||
|
||||
// Raw string preserves backslashes
|
||||
assert_equal("hello\\nworld", R"(hello\nworld)")
|
||||
|
||||
// Raw string with custom delimiter
|
||||
assert_equal("hello)world", R"foo(hello)world)foo")
|
||||
|
||||
// Raw string with quotes inside
|
||||
assert_equal("he said \"hi\"", R"(he said "hi")")
|
||||
|
||||
// Raw string with dollar signs
|
||||
assert_equal("cost is \$100", R"(cost is $100)")
|
||||
|
||||
// Empty raw string
|
||||
assert_equal("", R"()")
|
||||
|
||||
// Raw string with backslash-dollar
|
||||
assert_equal("\\\${foo}", R"(\${foo})")
|
||||
Loading…
x
Reference in New Issue
Block a user