ruby-changes:30694
From: charliesome <ko1@a...>
Date: Mon, 2 Sep 2013 16:11:52 +0900 (JST)
Subject: [ruby-changes:30694] charliesome:r42773 (trunk): * NEWS: Add note about frozen string literals
charliesome 2013-09-02 16:11:41 +0900 (Mon, 02 Sep 2013) New Revision: 42773 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=42773 Log: * NEWS: Add note about frozen string literals * compile.c (case_when_optimizable_literal): optimize NODE_LIT strings in when clauses of case statements * ext/ripper/eventids2.c: add tSTRING_SUFFIX * parse.y: add 'f' suffix on string literals for frozen strings * test/ripper/test_scanner_events.rb: add scanner tests * test/ruby/test_string.rb: add frozen string tests [Feature #8579] [ruby-core:55699] Modified files: trunk/ChangeLog trunk/NEWS trunk/compile.c trunk/ext/ripper/eventids2.c trunk/parse.y trunk/test/ripper/test_scanner_events.rb trunk/test/ruby/test_string.rb Index: ChangeLog =================================================================== --- ChangeLog (revision 42772) +++ ChangeLog (revision 42773) @@ -1,3 +1,20 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1 +Mon Sep 2 16:06:00 2013 Charlie Somerville <charliesome@r...> + + * NEWS: Add note about frozen string literals + + * compile.c (case_when_optimizable_literal): optimize NODE_LIT strings + in when clauses of case statements + + * ext/ripper/eventids2.c: add tSTRING_SUFFIX + + * parse.y: add 'f' suffix on string literals for frozen strings + + * test/ripper/test_scanner_events.rb: add scanner tests + + * test/ruby/test_string.rb: add frozen string tests + + [Feature #8579] [ruby-core:55699] + Mon Sep 2 14:39:29 2013 Akinori MUSHA <knu@i...> * ruby.c (Process#setproctitle): [DOC] Fix and improve rdoc. Index: compile.c =================================================================== --- compile.c (revision 42772) +++ compile.c (revision 42773) @@ -2504,7 +2504,8 @@ case_when_optimizable_literal(NODE * nod https://github.com/ruby/ruby/blob/trunk/compile.c#L2504 modf(RFLOAT_VALUE(v), &ival) == 0.0) { return FIXABLE(ival) ? LONG2FIX((long)ival) : rb_dbl2big(ival); } - if (SYMBOL_P(v) || rb_obj_is_kind_of(v, rb_cNumeric)) { + if (SYMBOL_P(v) || RB_TYPE_P(v, T_STRING) || + rb_obj_is_kind_of(v, rb_cNumeric)) { return v; } break; Index: parse.y =================================================================== --- parse.y (revision 42772) +++ parse.y (revision 42773) @@ -400,6 +400,8 @@ static NODE *new_evstr_gen(struct parser https://github.com/ruby/ruby/blob/trunk/parse.y#L400 #define new_evstr(n) new_evstr_gen(parser,(n)) static NODE *evstr2dstr_gen(struct parser_params*,NODE*); #define evstr2dstr(n) evstr2dstr_gen(parser,(n)) +static NODE *str_suffix_gen(struct parser_params*, NODE*, long); +#define str_suffix(n,o) str_suffix_gen(parser,(n),(o)) static NODE *splat_array(NODE*); static NODE *call_bin_op_gen(struct parser_params*,NODE*,ID,NODE*); @@ -531,6 +533,9 @@ static int lvar_defined_gen(struct parse https://github.com/ruby/ruby/blob/trunk/parse.y#L533 #define RE_OPTION_MASK 0xff #define RE_OPTION_ARG_ENCODING_NONE 32 +#define STR_OPTION_FROZEN 1 +#define STR_OPTION_BINARY 0 /* disabled */ + #define NODE_STRTERM NODE_ZARRAY /* nothing to gc */ #define NODE_HEREDOC NODE_ARRAY /* 1, 3 to gc */ #define SIGN_EXTEND(x,n) (((1<<(n)-1)^((x)&~(~0<<(n))))-(1<<(n)-1)) @@ -758,7 +763,7 @@ static void token_info_pop(struct parser https://github.com/ruby/ruby/blob/trunk/parse.y#L763 %token <id> tIDENTIFIER tFID tGVAR tIVAR tCONSTANT tCVAR tLABEL %token <node> tINTEGER tFLOAT tRATIONAL tIMAGINARY tSTRING_CONTENT tCHAR %token <node> tNTH_REF tBACK_REF -%token <num> tREGEXP_END +%token <num> tREGEXP_END tSTRING_SUFFIX %type <node> singleton strings string string1 xstring regexp %type <node> string_contents xstring_contents regexp_contents string_content @@ -784,6 +789,7 @@ static void token_info_pop(struct parser https://github.com/ruby/ruby/blob/trunk/parse.y#L789 %type <id> fsym keyword_variable user_variable sym symbol operation operation2 operation3 %type <id> cname fname op f_rest_arg f_block_arg opt_f_block_arg f_norm_arg f_bad_arg %type <id> f_kwrest f_label +%type <num> opt_string_sfx /*%%%*/ /*% %type <val> program reswords then do dot_or_colon @@ -3806,7 +3812,7 @@ literal : numeric https://github.com/ruby/ruby/blob/trunk/parse.y#L3812 | dsym ; -strings : string +strings : string opt_string_sfx { /*%%%*/ NODE *node = $1; @@ -3816,6 +3822,7 @@ strings : string https://github.com/ruby/ruby/blob/trunk/parse.y#L3822 else { node = evstr2dstr(node); } + node = str_suffix(node, $2); $$ = node; /*% $$ = $1; @@ -3845,6 +3852,10 @@ string1 : tSTRING_BEG string_contents t https://github.com/ruby/ruby/blob/trunk/parse.y#L3852 } ; +opt_string_sfx : tSTRING_SUFFIX + | /* none */ {$$ = 0;} + ; + xstring : tXSTRING_BEG xstring_contents tSTRING_END { /*%%%*/ @@ -5008,6 +5019,7 @@ none : /* none */ https://github.com/ruby/ruby/blob/trunk/parse.y#L5019 # define yylval (*((YYSTYPE*)(parser->parser_yylval))) static int parser_regx_options(struct parser_params*); +static int parser_str_options(struct parser_params*); static int parser_tokadd_string(struct parser_params*,int,int,int,long*,rb_encoding**); static void parser_tokaddmbc(struct parser_params *parser, int c, rb_encoding *enc); static int parser_parse_string(struct parser_params*,NODE*); @@ -5023,6 +5035,7 @@ static int parser_here_document(struct p https://github.com/ruby/ruby/blob/trunk/parse.y#L5035 # define read_escape(flags,e) parser_read_escape(parser, (flags), (e)) # define tokadd_escape(e) parser_tokadd_escape(parser, (e)) # define regx_options() parser_regx_options(parser) +# define str_options() parser_str_options(parser) # define tokadd_string(f,t,p,n,e) parser_tokadd_string(parser,(f),(t),(p),(n),(e)) # define parse_string(n) parser_parse_string(parser,(n)) # define tokaddmbc(c, enc) parser_tokaddmbc(parser, (c), (enc)) @@ -5532,10 +5545,11 @@ rb_parser_compile_file_path(volatile VAL https://github.com/ruby/ruby/blob/trunk/parse.y#L5545 #define STR_FUNC_QWORDS 0x08 #define STR_FUNC_SYMBOL 0x10 #define STR_FUNC_INDENT 0x20 +#define STR_FUNC_OPTION 0x40 enum string_type { - str_squote = (0), - str_dquote = (STR_FUNC_EXPAND), + str_squote = (STR_FUNC_OPTION), + str_dquote = (STR_FUNC_EXPAND|STR_FUNC_OPTION), str_xquote = (STR_FUNC_EXPAND), str_regexp = (STR_FUNC_REGEXP|STR_FUNC_ESCAPE|STR_FUNC_EXPAND), str_sword = (STR_FUNC_QWORDS), @@ -5982,6 +5996,40 @@ parser_regx_options(struct parser_params https://github.com/ruby/ruby/blob/trunk/parse.y#L5996 return options | RE_OPTION_ENCODING(kcode); } +static int +parser_str_options(struct parser_params *parser) +{ + int c, options = 0; + + newtok(); + while (c = nextc(), ISALPHA(c)) { + switch (c) { +#if STR_OPTION_FROZEN + case 'f': + options |= STR_OPTION_FROZEN; + break; +#endif +#if STR_OPTION_BINARY + case 'b': + options |= STR_OPTION_BINARY; + break; +#endif + default: + tokadd(c); + break; + } + } + pushback(c); + + if (toklen()) { + tokfix(); + compile_error(PARSER_ARG "unknown string option%s - %s", + toklen() > 1 ? "s" : "", tok()); + } + + return options; +} + static void dispose_string(VALUE str) { @@ -6248,6 +6296,10 @@ parser_parse_string(struct parser_params https://github.com/ruby/ruby/blob/trunk/parse.y#L6296 rb_encoding *enc = current_enc; if (func == -1) return tSTRING_END; + if (func == 0) { + set_yylval_num(term); + return tSTRING_SUFFIX; + } c = nextc(); if ((func & STR_FUNC_QWORDS) && ISSPACE(c)) { do {c = nextc();} while (ISSPACE(c)); @@ -6256,11 +6308,18 @@ parser_parse_string(struct parser_params https://github.com/ruby/ruby/blob/trunk/parse.y#L6308 if (c == term && !quote->nd_nest) { if (func & STR_FUNC_QWORDS) { quote->nd_func = -1; + quote->u2.id = 0; return ' '; } - if (!(func & STR_FUNC_REGEXP)) return tSTRING_END; - set_yylval_num(regx_options()); - return tREGEXP_END; + if (func & STR_FUNC_REGEXP) { + set_yylval_num(regx_options()); + return tREGEXP_END; + } + if ((func & STR_FUNC_OPTION) && (func = str_options()) != 0) { + quote->nd_func = 0; + quote->u2.id = func; + } + return tSTRING_END; } if (space) { pushback(c); @@ -6948,7 +7007,8 @@ parser_yylex(struct parser_params *parse https://github.com/ruby/ruby/blob/trunk/parse.y#L7007 } else { token = parse_string(lex_strterm); - if (token == tSTRING_END || token == tREGEXP_END) { + if ((token == tSTRING_END && lex_strterm->nd_func) || + token == tSTRING_SUFFIX || token == tREGEXP_END) { rb_gc_force_recycle((VALUE)lex_strterm); lex_strterm = 0; lex_state = EXPR_END; @@ -8496,6 +8556,37 @@ evstr2dstr_gen(struct parser_params *par https://github.com/ruby/ruby/blob/trunk/parse.y#L8556 } return node; } + +static NODE * +str_suffix_gen(struct parser_params *parser, NODE *node, long opt) +{ + if (nd_type(node) == NODE_STR) { +#if STR_OPTION_BINARY + if (opt & STR_OPTION_BINARY) { + rb_enc_associate_index(node->nd_lit, ENCINDEX_ASCII); + } +#endif +#if STR_OPTION_FROZEN + if (opt & STR_OPTION_FROZEN) { + OBJ_FREEZE(node->nd_lit); + nd_set_type(node, NODE_LIT); + } +#endif + } + else { +#if STR_OPTION_BINARY + if (opt & STR_OPTION_BINARY) { + node = NEW_CALL(node, rb_intern("b"), 0); + } +#endif +#if STR_OPTION_FROZEN + if (opt & STR_OPTION_FROZEN) { + node = NEW_CALL(node, rb_intern("freeze"), 0); + } +#endif + } + return node; +} static NODE * new_evstr_gen(struct parser_params *parser, NODE *node) Index: ext/ripper/eventids2.c =================================================================== --- ext/ripper/eventids2.c (revision 42772) +++ ext/ripper/eventids2.c (revision 42773) @@ -37,6 +37,7 @@ static ID ripper_id_symbeg; https://github.com/ruby/ruby/blob/trunk/ext/ripper/eventids2.c#L37 static ID ripper_id_tstring_beg; static ID ripper_id_tstring_content; static ID ripper_id_tstring_end; +static ID ripper_id_tstring_suffix; static ID ripper_id_words_beg; static ID ripper_id_qwords_beg; static ID ripper_id_qsymbols_beg; @@ -94,6 +95,7 @@ ripper_init_eventids2(void) https://github.com/ruby/ruby/blob/trunk/ext/ripper/eventids2.c#L95 ripper_id_tstring_beg = rb_intern_const("on_tstring_beg"); ripper_id_tstring_content = rb_intern_const("on_tstring_content"); ripper_id_tstring_end = rb_intern_const("on_tstring_end"); + ripper_id_tstring_suffix = rb_intern_const("on_tstring_suffix"); ripper_id_words_beg = rb_intern_const("on_words_beg"); ripper_id_qwords_beg = rb_intern_const("on_qwords_beg"); ripper_id_qsymbols_beg = rb_intern_const("on_qsymbols_beg"); @@ -252,6 +254,7 @@ static const struct token_assoc { https://github.com/ruby/ruby/blob/trunk/ext/ripper/eventids2.c#L254 {tSTRING_DEND, &ripper_id_embexpr_end}, {tSTRING_DVAR, &ripper_id_embvar}, {tSTRING_END, &ripper_id_tstring_end}, + {tSTRING_SUFFIX, &ripper_id_tstring_suffix}, {tSYMBEG, &ripper_id_symbeg}, {tUMINUS, &ripper_id_op}, {tUMINUS_NUM, &ripper_id_op}, Index: NEWS =================================================================== --- NEWS (revision 42772) +++ NEWS (revision 42773) @@ -24,6 +24,8 @@ with all sufficient information, see the https://github.com/ruby/ruby/blob/trunk/NEWS#L24 * def-expr now returns the symbol of its name instead of nil. +* Added 'f' suffix for string literals that returns a frozen String object. + === Core classes updates (outstanding ones only) * Bignum Index: test/ruby/test_string.rb =================================================================== --- test/ruby/test_string.rb (revision 42772) +++ test/ruby/test_string.rb (revision 42773) @@ -2192,6 +2192,49 @@ class TestString < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_string.rb#L2192 assert_equal(false, "\u3042".byteslice(0, 2).valid_encoding?) assert_equal(false, ("\u3042"*10).byteslice(0, 20).valid_encoding?) end + + def test_unknown_string_option + assert_raises(SyntaxError) do + eval(%{ + "hello"x + }) + end + end + + def test_frozen_string + assert_equal "hello", "hello"f + + assert_predicate "hello"f, :frozen? + + f = -> { "hello"f } + + assert_equal f.call.object_id, f.call.object_id + end + + def test_frozen_dstring + assert_equal "hello123", "hello#{123}"f + + assert_predicate "hello#{123}"f, :frozen? + + i = 0 + f = -> { "#{i += 1}"f } + assert_equal "1", f.call + assert_equal "2", f.call + end + + def test_frozen_string_cannot_be_adjacent + assert_raises(SyntaxError) do + eval(%{ + "hello"f "world" + }) + end + + assert_raises(SyntaxError) do + eval(%{ + "hello"f "world" + }) + end + end end class TestString2 < TestString Index: test/ripper/test_scanner_events.rb =================================================================== --- test/ripper/test_scanner_events.rb (revision 42772) +++ test/ripper/test_scanner_events.rb (revision 42773) @@ -591,6 +591,15 @@ class TestRipper::ScannerEvents < Test:: https://github.com/ruby/ruby/blob/trunk/test/ripper/test_scanner_events.rb#L591 scan('tstring_end', '%Q[abcdef]') end + def test_tstring_suffix + assert_equal ['"f'], + scan('tstring_end', '"abcdef"f') + assert_equal [']f'], + scan('tstring_end', '%q[abcdef]f') + assert_equal [']f'], + scan('tstring_end', '%Q[abcdef]f') + end + def test_regexp_beg assert_equal [], scan('regexp_beg', '') -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/