From 8f67be87eeacc07ba846eaaaa4fe15bf39a256e0 Mon Sep 17 00:00:00 2001 From: Syed Daanish Date: Sun, 13 Sep 2026 14:07:19 +0100 Subject: [PATCH] Add escape in strings control to ruby parser. --- include/internal/syntax/ruby/decl.h | 3 +- src/internal/syntax/ruby/{ruby.cc => core.cc} | 0 src/internal/syntax/ruby/parse.cc | 143 ++++++++++-------- 3 files changed, 83 insertions(+), 63 deletions(-) rename src/internal/syntax/ruby/{ruby.cc => core.cc} (100%) diff --git a/include/internal/syntax/ruby/decl.h b/include/internal/syntax/ruby/decl.h index 6167d9c..8e4617d 100644 --- a/include/internal/syntax/ruby/decl.h +++ b/include/internal/syntax/ruby/decl.h @@ -24,7 +24,8 @@ struct alignas(2) RubyState { DEF_NAME = 0b10, MODULE_NAME = 0b11 }; - static constexpr uint8_t NEWLINE = 1 << 5; + static constexpr uint8_t NEWLINE = 1 << 4; + static constexpr uint8_t ALLOW_ESCAPE = 1 << 5; static constexpr uint8_t ALLOW_INTERPOLATION = 1 << 6; static constexpr uint8_t EXPECTING_EXPRESSION = 1 << 7; uint8_t flags = 0; diff --git a/src/internal/syntax/ruby/ruby.cc b/src/internal/syntax/ruby/core.cc similarity index 100% rename from src/internal/syntax/ruby/ruby.cc rename to src/internal/syntax/ruby/core.cc diff --git a/src/internal/syntax/ruby/parse.cc b/src/internal/syntax/ruby/parse.cc index 134d026..fba8ee6 100644 --- a/src/internal/syntax/ruby/parse.cc +++ b/src/internal/syntax/ruby/parse.cc @@ -35,72 +35,83 @@ inline uint8_t utf8_codepoint_width(unsigned char c) { } bool handle_escapes(RubyParser &p, std::vector *tokens, uint32_t &start, bool string = true) { - // TODO: properly handle escapes for %q ' etc. - if (p.peek() == '\\') { - if (string) - tokens->push_back({start, p.i, io::Token::String}); - else - tokens->push_back({start, p.i, io::Token::Regexp}); - start = p.i; + if (p.peek() != '\\') + return false; + if (!(p.current().flags & RubyState::RubyInternalState::ALLOW_ESCAPE)) { p.advance(); - if (p.peek() == 'x') { - p.advance(); - if (is_hex(p.peek())) - p.advance(); - if (is_hex(p.peek())) - p.advance(); - } else if (p.peek() == 'u') { - p.advance(); - if (p.peek() == '{') { - p.advance(); - while (p.peek() != '}' && p.peek() != '\0') - p.advance(); - if (p.peek() == '}') - p.advance(); - } else { - if (is_hex(p.peek())) - p.advance(); - if (is_hex(p.peek())) - p.advance(); - if (is_hex(p.peek())) - p.advance(); - if (is_hex(p.peek())) - p.advance(); - } - } else if ('0' <= p.peek() && p.peek() <= '7') { - p.advance(); - if ('0' <= p.peek() && p.peek() <= '7') - p.advance(); - if ('0' <= p.peek() && p.peek() <= '7') - p.advance(); - } else if (p.peek() == 'c') { - p.advance(); - if (p.peek() != '\\') - p.advance(); - } else if (p.peek() == 'M' || p.peek() == 'C') { - p.advance(); - if (p.peek() == '-') { - p.advance(); - if (p.peek() != '\\') - p.advance(); - } - } else if (p.peek() == 'N') { - p.advance(); - if (p.peek() == '{') { - p.advance(); - while (p.peek() != '}' && p.peek() != '\0') - p.advance(); - if (p.peek() == '}') - p.advance(); - } - } else { - p.advance(); - } - tokens->push_back({start, p.i, io::Token::Escape}); + if (p.peek() != '\'' && p.peek() != '\\') + return false; + if (string) + tokens->push_back({start, p.i - 1, io::Token::String}); + else + tokens->push_back({start, p.i - 1, io::Token::Regexp}); + p.advance(); + tokens->push_back({p.i - 2, p.i, io::Token::Escape}); start = p.i; return true; } - return false; + if (string) + tokens->push_back({start, p.i, io::Token::String}); + else + tokens->push_back({start, p.i, io::Token::Regexp}); + start = p.i; + p.advance(); + if (p.peek() == 'x') { + p.advance(); + if (is_hex(p.peek())) + p.advance(); + if (is_hex(p.peek())) + p.advance(); + } else if (p.peek() == 'u') { + p.advance(); + if (p.peek() == '{') { + p.advance(); + while (p.peek() != '}' && p.peek() != '\0') + p.advance(); + if (p.peek() == '}') + p.advance(); + } else { + if (is_hex(p.peek())) + p.advance(); + if (is_hex(p.peek())) + p.advance(); + if (is_hex(p.peek())) + p.advance(); + if (is_hex(p.peek())) + p.advance(); + } + } else if ('0' <= p.peek() && p.peek() <= '7') { + p.advance(); + if ('0' <= p.peek() && p.peek() <= '7') + p.advance(); + if ('0' <= p.peek() && p.peek() <= '7') + p.advance(); + } else if (p.peek() == 'c') { + p.advance(); + if (p.peek() != '\\') + p.advance(); + } else if (p.peek() == 'M' || p.peek() == 'C') { + p.advance(); + if (p.peek() == '-') { + p.advance(); + if (p.peek() != '\\') + p.advance(); + } + } else if (p.peek() == 'N') { + p.advance(); + if (p.peek() == '{') { + p.advance(); + while (p.peek() != '}' && p.peek() != '\0') + p.advance(); + if (p.peek() == '}') + p.advance(); + } + } else { + p.advance(); + } + tokens->push_back({start, p.i, io::Token::Escape}); + start = p.i; + return true; }; bool handle_heredoc(RubyParser &p, std::vector *tokens, std::vector *events) { @@ -669,6 +680,7 @@ bool handle_syntax(RubyParser &p, std::vector *tokens, std::vector *tokens, std::vector *tokens, std::vector *tokens, std::vector *tokens, std::vector *tokens, std::vector