Skip to content

Commit 61a35ad

Browse files
committed
parse.y: add heredoc <<~ syntax (Feature #9098)
1 parent 325a50f commit 61a35ad

File tree

2 files changed

+65
-1
lines changed

2 files changed

+65
-1
lines changed

parse.y

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5177,6 +5177,7 @@ static int parser_tokadd_string(struct parser_params*,int,int,int,long*,rb_encod
51775177
static void parser_tokaddmbc(struct parser_params *parser, int c, rb_encoding *enc);
51785178
static int parser_parse_string(struct parser_params*,NODE*);
51795179
static int parser_here_document(struct parser_params*,NODE*);
5180+
static VALUE parser_heredoc_dedent(VALUE);
51805181

51815182

51825183
# define nextc() parser_nextc(parser)
@@ -5699,6 +5700,7 @@ rb_parser_compile_file_path(volatile VALUE vparser, VALUE fname, VALUE file, int
56995700
#define STR_FUNC_QWORDS 0x08
57005701
#define STR_FUNC_SYMBOL 0x10
57015702
#define STR_FUNC_INDENT 0x20
5703+
#define STR_FUNC_DEDENT 0x40
57025704

57035705
enum string_type {
57045706
str_squote = (0),
@@ -5725,6 +5727,10 @@ parser_str_new(const char *p, long n, rb_encoding *enc, int func, rb_encoding *e
57255727
}
57265728
}
57275729

5730+
if (func & STR_FUNC_DEDENT) {
5731+
return parser_heredoc_dedent(str);
5732+
}
5733+
57285734
return str;
57295735
}
57305736

@@ -6478,7 +6484,11 @@ parser_heredoc_identifier(struct parser_params *parser)
64786484
if (c == '-') {
64796485
c = nextc();
64806486
func = STR_FUNC_INDENT;
6487+
} else if (c == '~') {
6488+
c = nextc();
6489+
func = STR_FUNC_INDENT | STR_FUNC_DEDENT;
64816490
}
6491+
64826492
switch (c) {
64836493
case '\'':
64846494
func |= str_squote; goto quoted;
@@ -6502,7 +6512,9 @@ parser_heredoc_identifier(struct parser_params *parser)
65026512
default:
65036513
if (!parser_is_identchar()) {
65046514
pushback(c);
6505-
if (func & STR_FUNC_INDENT) {
6515+
if (func & STR_FUNC_DEDENT) {
6516+
pushback('~');
6517+
} else if (func & STR_FUNC_INDENT) {
65066518
pushback('-');
65076519
}
65086520
return 0;
@@ -6550,6 +6562,44 @@ parser_heredoc_restore(struct parser_params *parser, NODE *here)
65506562
ripper_flush(parser);
65516563
}
65526564

6565+
static VALUE
6566+
parser_heredoc_dedent(VALUE input)
6567+
{
6568+
char *str = RSTRING_PTR(input), *p, *out_p;
6569+
int len = RSTRING_LEN(input), indent = len, line_indent = 0, lines = 0;
6570+
char *end = &str[len];
6571+
VALUE output;
6572+
6573+
p = str;
6574+
while (p < end) {
6575+
lines++;
6576+
line_indent = 0;
6577+
while (p < end && (*p == ' ' || *p == '\t')) {
6578+
line_indent++;
6579+
p++;
6580+
}
6581+
if (p < end && line_indent < indent) indent = line_indent;
6582+
if (indent == 0) break;
6583+
6584+
while (p < end && *p != '\r' && *p != '\n') p++;
6585+
if (p < end && *p == '\r') p++;
6586+
if (p < end && *p == '\n') p++;
6587+
}
6588+
6589+
output = rb_str_new(0, len - (lines * indent));
6590+
out_p = RSTRING_PTR(output);
6591+
6592+
p = str;
6593+
while (p < end) {
6594+
p = p + indent;
6595+
while (p < end && *p != '\r' && *p != '\n') *out_p++ = *p++;
6596+
if (p < end && *p == '\r') *out_p++ = *p++;
6597+
if (p < end && *p == '\n') *out_p++ = *p++;
6598+
}
6599+
6600+
return output;
6601+
}
6602+
65536603
static int
65546604
parser_whole_match_p(struct parser_params *parser,
65556605
const char *eos, long len, int indent)

test/ruby/test_syntax.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,20 @@ def test_lineno_command_call_quote
425425
assert_equal(expected, actual, "#{Bug7559}: ")
426426
end
427427

428+
def test_dedented_heredoc_without_indentation
429+
assert_equal(" y\nz\n", <<~eos)
430+
y
431+
z
432+
eos
433+
end
434+
435+
def test_dedented_heredoc_with_indentation
436+
assert_equal(" a\nb\n", <<~eos)
437+
a
438+
b
439+
eos
440+
end
441+
428442
def test_lineno_after_heredoc
429443
bug7559 = '[ruby-dev:46737]'
430444
expected, _, actual = __LINE__, <<eom, __LINE__

0 commit comments

Comments
 (0)