Skip to content

Commit 212763c

Browse files
author
Charlie Somerville
committed
Merge pull request #6 from github/fstr
Add %f() syntax for frozen strings
2 parents 3830598 + f70e6db commit 212763c

File tree

5 files changed

+140
-9
lines changed

5 files changed

+140
-9
lines changed

compile.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2351,7 +2351,7 @@ case_when_optimizable_literal(NODE * node)
23512351
modf(RFLOAT_VALUE(v), &ival) == 0.0) {
23522352
return FIXABLE(ival) ? LONG2FIX((long)ival) : rb_dbl2big(ival);
23532353
}
2354-
if (SYMBOL_P(v) || rb_obj_is_kind_of(v, rb_cNumeric)) {
2354+
if (SYMBOL_P(v) || rb_obj_is_kind_of(v, rb_cNumeric) || RB_TYPE_P(v, T_STRING)) {
23552355
return v;
23562356
}
23572357
break;

ext/ripper/eventids2.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ static ID ripper_id_symbeg;
3636
static ID ripper_id_tstring_beg;
3737
static ID ripper_id_tstring_content;
3838
static ID ripper_id_tstring_end;
39+
static ID ripper_id_tstring_suffix;
3940
static ID ripper_id_words_beg;
4041
static ID ripper_id_qwords_beg;
4142
static ID ripper_id_words_sep;
@@ -89,6 +90,7 @@ ripper_init_eventids2(VALUE self)
8990
ripper_id_tstring_beg = rb_intern_const("on_tstring_beg");
9091
ripper_id_tstring_content = rb_intern_const("on_tstring_content");
9192
ripper_id_tstring_end = rb_intern_const("on_tstring_end");
93+
ripper_id_tstring_suffix = rb_intern_const("on_tstring_suffix");
9294
ripper_id_words_beg = rb_intern_const("on_words_beg");
9395
ripper_id_qwords_beg = rb_intern_const("on_qwords_beg");
9496
ripper_id_words_sep = rb_intern_const("on_words_sep");
@@ -240,6 +242,7 @@ static const struct token_assoc {
240242
{tSTRING_DBEG, &ripper_id_embexpr_beg},
241243
{tSTRING_DVAR, &ripper_id_embvar},
242244
{tSTRING_END, &ripper_id_tstring_end},
245+
{tSTRING_SUFFIX, &ripper_id_tstring_suffix},
243246
{tSYMBEG, &ripper_id_symbeg},
244247
{tUMINUS, &ripper_id_op},
245248
{tUMINUS_NUM, &ripper_id_op},

parse.y

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,8 @@ static NODE *new_evstr_gen(struct parser_params*,NODE*);
365365
#define new_evstr(n) new_evstr_gen(parser,(n))
366366
static NODE *evstr2dstr_gen(struct parser_params*,NODE*);
367367
#define evstr2dstr(n) evstr2dstr_gen(parser,(n))
368+
static NODE *str_suffix_gen(struct parser_params*, NODE*, long);
369+
#define str_suffix(n,o) str_suffix_gen(parser,(n),(o))
368370
static NODE *splat_array(NODE*);
369371

370372
static NODE *call_bin_op_gen(struct parser_params*,NODE*,ID,NODE*);
@@ -474,6 +476,8 @@ static int lvar_defined_gen(struct parser_params*, ID);
474476
#define RE_OPTION_MASK 0xff
475477
#define RE_OPTION_ARG_ENCODING_NONE 32
476478

479+
#define STR_OPTION_FROZEN 1
480+
477481
#define NODE_STRTERM NODE_ZARRAY /* nothing to gc */
478482
#define NODE_HEREDOC NODE_ARRAY /* 1, 3 to gc */
479483
#define SIGN_EXTEND(x,n) (((1<<(n)-1)^((x)&~(~0<<(n))))-(1<<(n)-1))
@@ -679,7 +683,7 @@ static void token_info_pop(struct parser_params*, const char *token);
679683
%token <id> tIDENTIFIER tFID tGVAR tIVAR tCONSTANT tCVAR tLABEL
680684
%token <node> tINTEGER tFLOAT tSTRING_CONTENT tCHAR
681685
%token <node> tNTH_REF tBACK_REF
682-
%token <num> tREGEXP_END
686+
%token <num> tREGEXP_END tSTRING_SUFFIX
683687

684688
%type <node> singleton strings string string1 xstring regexp
685689
%type <node> string_contents xstring_contents regexp_contents string_content
@@ -703,6 +707,7 @@ static void token_info_pop(struct parser_params*, const char *token);
703707
%type <node> mlhs mlhs_head mlhs_basic mlhs_item mlhs_node mlhs_post mlhs_inner
704708
%type <id> fsym keyword_variable user_variable sym symbol operation operation2 operation3
705709
%type <id> cname fname op f_rest_arg f_block_arg opt_f_block_arg f_norm_arg f_bad_arg
710+
%type <num> opt_string_sfx
706711
/*%%%*/
707712
/*%
708713
%type <val> program reswords then do dot_or_colon
@@ -3839,7 +3844,7 @@ literal : numeric
38393844
| dsym
38403845
;
38413846

3842-
strings : string
3847+
strings : string opt_string_sfx
38433848
{
38443849
/*%%%*/
38453850
NODE *node = $1;
@@ -3849,6 +3854,7 @@ strings : string
38493854
else {
38503855
node = evstr2dstr(node);
38513856
}
3857+
node = str_suffix(node, $2);
38523858
$$ = node;
38533859
/*%
38543860
$$ = $1;
@@ -3878,6 +3884,10 @@ string1 : tSTRING_BEG string_contents tSTRING_END
38783884
}
38793885
;
38803886

3887+
opt_string_sfx : tSTRING_SUFFIX
3888+
| /* none */ { $$ = 0; }
3889+
;
3890+
38813891
xstring : tXSTRING_BEG xstring_contents tSTRING_END
38823892
{
38833893
/*%%%*/
@@ -4886,6 +4896,7 @@ none : /* none */
48864896
# define yylval (*((YYSTYPE*)(parser->parser_yylval)))
48874897

48884898
static int parser_regx_options(struct parser_params*);
4899+
static int parser_str_options(struct parser_params*);
48894900
static int parser_tokadd_string(struct parser_params*,int,int,int,long*,rb_encoding**);
48904901
static void parser_tokaddmbc(struct parser_params *parser, int c, rb_encoding *enc);
48914902
static int parser_parse_string(struct parser_params*,NODE*);
@@ -4901,6 +4912,7 @@ static int parser_here_document(struct parser_params*,NODE*);
49014912
# define read_escape(flags,e) parser_read_escape(parser, (flags), (e))
49024913
# define tokadd_escape(e) parser_tokadd_escape(parser, (e))
49034914
# define regx_options() parser_regx_options(parser)
4915+
# define str_options() parser_str_options(parser)
49044916
# define tokadd_string(f,t,p,n,e) parser_tokadd_string(parser,(f),(t),(p),(n),(e))
49054917
# define parse_string(n) parser_parse_string(parser,(n))
49064918
# define tokaddmbc(c, enc) parser_tokaddmbc(parser, (c), (enc))
@@ -5385,10 +5397,11 @@ rb_parser_compile_file(volatile VALUE vparser, const char *f, VALUE file, int st
53855397
#define STR_FUNC_QWORDS 0x08
53865398
#define STR_FUNC_SYMBOL 0x10
53875399
#define STR_FUNC_INDENT 0x20
5400+
#define STR_FUNC_OPTION 0x40
53885401

53895402
enum string_type {
5390-
str_squote = (0),
5391-
str_dquote = (STR_FUNC_EXPAND),
5403+
str_squote = (STR_FUNC_OPTION),
5404+
str_dquote = (STR_FUNC_EXPAND|STR_FUNC_OPTION),
53925405
str_xquote = (STR_FUNC_EXPAND),
53935406
str_regexp = (STR_FUNC_REGEXP|STR_FUNC_ESCAPE|STR_FUNC_EXPAND),
53945407
str_sword = (STR_FUNC_QWORDS),
@@ -5827,6 +5840,28 @@ parser_regx_options(struct parser_params *parser)
58275840
return options | RE_OPTION_ENCODING(kcode);
58285841
}
58295842

5843+
static int
5844+
parser_str_options(struct parser_params *parser)
5845+
{
5846+
int c, options = 0;
5847+
const char *save_p = lex_p;
5848+
5849+
while (c = nextc(), ISALPHA(c)) {
5850+
switch (c) {
5851+
#if STR_OPTION_FROZEN
5852+
case 'f':
5853+
options |= STR_OPTION_FROZEN;
5854+
break;
5855+
#endif
5856+
default:
5857+
lex_p = save_p;
5858+
return 0;
5859+
}
5860+
}
5861+
pushback(c);
5862+
return options;
5863+
}
5864+
58305865
static void
58315866
dispose_string(VALUE str)
58325867
{
@@ -5995,6 +6030,10 @@ parser_parse_string(struct parser_params *parser, NODE *quote)
59956030
rb_encoding *enc = parser->enc;
59966031

59976032
if (func == -1) return tSTRING_END;
6033+
if (func == 0) {
6034+
set_yylval_num(term);
6035+
return tSTRING_SUFFIX;
6036+
}
59986037
c = nextc();
59996038
if ((func & STR_FUNC_QWORDS) && ISSPACE(c)) {
60006039
do {c = nextc();} while (ISSPACE(c));
@@ -6003,11 +6042,18 @@ parser_parse_string(struct parser_params *parser, NODE *quote)
60036042
if (c == term && !quote->nd_nest) {
60046043
if (func & STR_FUNC_QWORDS) {
60056044
quote->nd_func = -1;
6045+
quote->u2.id = 0;
60066046
return ' ';
60076047
}
6008-
if (!(func & STR_FUNC_REGEXP)) return tSTRING_END;
6009-
set_yylval_num(regx_options());
6010-
return tREGEXP_END;
6048+
if (func & STR_FUNC_REGEXP) {
6049+
set_yylval_num(regx_options());
6050+
return tREGEXP_END;
6051+
}
6052+
if ((func & STR_FUNC_OPTION) && (func = str_options()) != 0) {
6053+
quote->nd_func = 0;
6054+
quote->u2.id = func;
6055+
}
6056+
return tSTRING_END;
60116057
}
60126058
if (space) {
60136059
pushback(c);
@@ -6655,7 +6701,8 @@ parser_yylex(struct parser_params *parser)
66556701
}
66566702
else {
66576703
token = parse_string(lex_strterm);
6658-
if (token == tSTRING_END || token == tREGEXP_END) {
6704+
if ((token == tSTRING_END && lex_strterm->nd_func) ||
6705+
token == tSTRING_SUFFIX || token == tREGEXP_END) {
66596706
rb_gc_force_recycle((VALUE)lex_strterm);
66606707
lex_strterm = 0;
66616708
lex_state = EXPR_END;
@@ -8187,6 +8234,27 @@ evstr2dstr_gen(struct parser_params *parser, NODE *node)
81878234
return node;
81888235
}
81898236

8237+
static NODE *
8238+
str_suffix_gen(struct parser_params *parser, NODE *node, long opt)
8239+
{
8240+
if (nd_type(node) == NODE_STR) {
8241+
#if STR_OPTION_FROZEN
8242+
if (opt & STR_OPTION_FROZEN) {
8243+
OBJ_FREEZE(node->nd_lit);
8244+
nd_set_type(node, NODE_LIT);
8245+
}
8246+
#endif
8247+
}
8248+
else {
8249+
#if STR_OPTION_FROZEN
8250+
if (opt & STR_OPTION_FROZEN) {
8251+
node = NEW_CALL(node, rb_intern("freeze"), 0);
8252+
}
8253+
#endif
8254+
}
8255+
return node;
8256+
}
8257+
81908258
static NODE *
81918259
new_evstr_gen(struct parser_params *parser, NODE *node)
81928260
{

test/ripper/test_scanner_events.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,15 @@ def test_tstring_end
569569
scan('tstring_end', '%Q[abcdef]')
570570
end
571571

572+
def test_tstring_suffix
573+
assert_equal ['"f'],
574+
scan('tstring_end', '"abcdef"f')
575+
assert_equal [']f'],
576+
scan('tstring_end', '%q[abcdef]f')
577+
assert_equal [']f'],
578+
scan('tstring_end', '%Q[abcdef]f')
579+
end
580+
572581
def test_regexp_beg
573582
assert_equal [],
574583
scan('regexp_beg', '')

test/ruby/test_string.rb

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1996,4 +1996,55 @@ def test_byteslice
19961996

19971997
assert_equal(u("\x82")+("\u3042"*9), ("\u3042"*10).byteslice(2, 28))
19981998
end
1999+
2000+
def test_unknown_string_option
2001+
str = nil
2002+
assert_nothing_raised(SyntaxError) do
2003+
eval(%{
2004+
str = begin"hello"end
2005+
})
2006+
end
2007+
assert_equal "hello", str
2008+
refute str.frozen?
2009+
end
2010+
2011+
def test_frozen_string
2012+
assert_equal "hello", "hello"f
2013+
2014+
assert "hello"f.frozen?
2015+
2016+
f = -> { "hello"f }
2017+
2018+
assert_equal f.call.object_id, f.call.object_id
2019+
end
2020+
2021+
def test_frozen_dstring
2022+
assert_equal "hello123", "hello#{123}"f
2023+
2024+
assert "hello#{123}"f.frozen?
2025+
2026+
i = 0
2027+
f = -> { "#{i += 1}"f }
2028+
assert_equal "1", f.call
2029+
assert_equal "2", f.call
2030+
end
2031+
2032+
def test_frozen_string_adjacent
2033+
str = nil
2034+
assert_nothing_raised(SyntaxError) do
2035+
eval(%{
2036+
str = "hello" "world"f
2037+
})
2038+
end
2039+
assert_equal "helloworld", str
2040+
assert str.frozen?
2041+
end
2042+
2043+
def test_frozen_string_cannot_be_adjacent
2044+
assert_raise(SyntaxError) do
2045+
eval(%{
2046+
"hello"f "world"
2047+
})
2048+
end
2049+
end
19992050
end

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy