/************************************************ parse.y - $Author: matz $ $Date: 1995/01/12 08:54:50 $ created at: Fri May 28 18:02:42 JST 1993 Copyright (C) 1994 Yukihiro Matsumoto ************************************************/ %{ #define YYDEBUG 1 #include "ruby.h" #include "env.h" #include "node.h" #include "st.h" #include #include "ident.h" #define is_id_nonop(id) ((id)>LAST_TOKEN) #define is_local_id(id) (is_id_nonop(id)&&((id)&ID_SCOPE_MASK)==ID_LOCAL) #define is_global_id(id) (is_id_nonop(id)&&((id)&ID_SCOPE_MASK)==ID_GLOBAL) #define is_instance_id(id) (is_id_nonop(id)&&((id)&ID_SCOPE_MASK)==ID_INSTANCE) #define is_variable_id(id) (is_id_nonop(id)&&((id)&ID_VARMASK)) #define is_attrset_id(id) (is_id_nonop(id)&&((id)&ID_SCOPE_MASK)==ID_ATTRSET) #define is_const_id(id) (is_id_nonop(id)&&((id)&ID_SCOPE_MASK)==ID_CONST) struct op_tbl { ID token; char *name; }; NODE *eval_tree = Qnil; char *sourcefile; /* current source file */ int sourceline; /* current line no. */ static int yylex(); static enum lex_state { EXPR_BEG, /* ignore newline, +/- is a sign. */ EXPR_MID, /* newline significant, +/- is a sign. */ EXPR_END, /* newline significant, +/- is a operator. */ EXPR_FNAME, /* ignore newline, +/- is a operator. */ } lex_state; static int class_nest = 0; static int in_single = 0; static ID cur_mid = Qnil; static int value_expr(); static NODE *cond(); static NODE *cond2(); static NODE *block_append(); static NODE *list_append(); static NODE *list_concat(); static NODE *list_copy(); static NODE *expand_op(); static NODE *call_op(); static NODE *gettable(); static NODE *asignable(); static NODE *aryset(); static NODE *attrset(); static void local_push(); static void local_pop(); static int local_cnt(); static int local_id(); static ID *local_tbl(); #define cref_push() NEW_CREF(0) static void cref_pop(); static NODE *cref_list; struct global_entry* rb_global_entry(); static void top_local_init(); static void top_local_setup(); %} %union { NODE *node; VALUE val; ID id; int num; } %token CLASS MODULE DEF UNDEF INCLUDE BEGIN RESQUE ENSURE END IF THEN ELSIF ELSE CASE WHEN WHILE FOR IN REDO BREAK CONTINUE RETURN FAIL YIELD SUPER RETRY SELF NIL AND OR _FILE_ _LINE_ IF_MOD WHILE_MOD ALIAS %token IDENTIFIER GVAR IVAR CONSTANT %token INTEGER FLOAT STRING XSTRING REGEXP GLOB %token STRING2 XSTRING2 DREGEXP DGLOB %type singleton inc_list %type literal numeric %type compexpr exprs expr arg primary var_ref %type if_tail opt_else case_body cases resque ensure %type call_args call_args0 args args2 opt_args %type f_arglist f_args assoc_list assocs assoc %type mlhs mlhs_head mlhs_tail lhs iter_var opt_iter_var %type superclass variable symbol %type fname op rest_arg %type f_arg %token UPLUS /* unary+ */ %token UMINUS /* unary- */ %token POW /* ** */ %token CMP /* <=> */ %token EQ /* == */ %token NEQ /* != <> */ %token GEQ /* >= */ %token LEQ /* <= */ %token ANDOP OROP /* && and || */ %token MATCH NMATCH /* =~ and !~ */ %token DOT2 DOT3 /* .. and ... */ %token AREF ASET /* [] and []= */ %token LSHFT RSHFT /* << and >> */ %token COLON2 /* :: */ %token OP_ASGN /* +=, -= etc. */ %token ASSOC /* => */ %token LPAREN LBRACK LBRACE %token SYMBEG /* * precedence table */ %left IF_MOD WHILE_MOD %left OR %left AND %left YIELD RETURN FAIL %right '=' OP_ASGN %right COLON2 %nonassoc DOT2 DOT3 %left OROP %left ANDOP %nonassoc CMP EQ NEQ MATCH NMATCH %left '>' GEQ '<' LEQ %left '|' '^' %left '&' %left LSHFT RSHFT %left '+' '-' %left '*' '/' '%' %right '!' '~' UPLUS UMINUS %right POW %token LAST_TOKEN %% program : { lex_state = EXPR_BEG; top_local_init(); } compexpr { eval_tree = block_append(eval_tree, $2); top_local_setup(); } compexpr : exprs opt_term exprs : /* none */ { $$ = Qnil; } | expr | exprs term expr { $$ = block_append($1, $3); } | exprs error { lex_state = EXPR_BEG; } expr { yyerrok; $$ = block_append($1, $4); } expr : mlhs '=' args2 { value_expr($3); $1->nd_value = $3; $$ = $1; } | assocs { $$ = NEW_HASH($1); } | RETURN args2 { value_expr($2); if (!cur_mid && !in_single) Error("return appeared outside of method"); $$ = NEW_RET($2); } | FAIL args2 { value_expr($2); $$ = NEW_FAIL($2); } | YIELD args2 { value_expr($2); $$ = NEW_YIELD($2); } | IDENTIFIER call_args0 { $$ = NEW_CALL(Qnil, $1, $2); } | primary '.' IDENTIFIER call_args0 { value_expr($1); $$ = NEW_CALL($1, $3, $4); } | SUPER call_args0 { if (!cur_mid && !in_single) Error("super called outside of method"); $$ = NEW_SUPER($2); } | UNDEF fname { $$ = NEW_UNDEF($2); } | ALIAS fname {lex_state = EXPR_FNAME;} fname { $$ = NEW_ALIAS($2, $4); } | INCLUDE inc_list { if (cur_mid || in_single) Error("include appeared in method definition"); $$ = $2; } | expr IF_MOD expr { $$ = NEW_IF(cond($3), $1, Qnil); } | expr WHILE_MOD expr { $$ = NEW_WHILE2(cond($3), $1); } | expr AND expr { $$ = NEW_AND(cond($1), cond($3)); } | expr OR expr { $$ = NEW_OR(cond($1), cond($3)); } | arg mlhs : mlhs_head { $$ = NEW_MASGN(NEW_LIST($1), Qnil); } | mlhs_head '*' lhs { $$ = NEW_MASGN(NEW_LIST($1), $3); } | mlhs_head mlhs_tail { $$ = NEW_MASGN(list_concat(NEW_LIST($1),$2),Qnil); } | mlhs_head mlhs_tail comma '*' lhs { $$ = NEW_MASGN(list_concat(NEW_LIST($1),$2),$5); } mlhs_head : lhs comma mlhs_tail : lhs { $$ = NEW_LIST($1); } | mlhs_tail comma lhs { $$ = list_append($1, $3); } lhs : variable { $$ = asignable($1, Qnil); } | primary '[' opt_args rbracket { $$ = aryset($1, $3, Qnil); } | primary '.' IDENTIFIER { $$ = attrset($1, $3, Qnil); } inc_list : CONSTANT { $$ = NEW_INC($1); } | inc_list comma CONSTANT { $$ = block_append($1, NEW_INC($3)); } | error { lex_state = EXPR_BEG; $$ = Qnil; } | inc_list comma error { lex_state = EXPR_BEG; $$ = $1; } fname : IDENTIFIER | CONSTANT | op { lex_state = EXPR_END; $$ = $1; } op : COLON2 { $$ = COLON2; } | DOT2 { $$ = DOT2; } | '|' { $$ = '|'; } | '^' { $$ = '^'; } | '&' { $$ = '&'; } | CMP { $$ = CMP; } | EQ { $$ = EQ; } | MATCH { $$ = MATCH; } | '>' { $$ = '>'; } | GEQ { $$ = GEQ; } | '<' { $$ = '<'; } | LEQ { $$ = LEQ; } | LSHFT { $$ = LSHFT; } | RSHFT { $$ = RSHFT; } | '+' { $$ = '+'; } | '-' { $$ = '-'; } | '*' { $$ = '*'; } | '/' { $$ = '/'; } | '%' { $$ = '%'; } | POW { $$ = POW; } | '~' { $$ = '~'; } | UPLUS { $$ = UMINUS; } | UMINUS { $$ = UPLUS; } | AREF { $$ = AREF; } | ASET { $$ = ASET; } arg : variable '=' arg { value_expr($3); $$ = asignable($1, $3); } | primary '[' opt_args rbracket '=' arg { $$ = aryset($1, $3, $6); } | primary '.' IDENTIFIER '=' arg { $$ = attrset($1, $3, $5); } | variable OP_ASGN arg { NODE *val; value_expr($3); if (is_local_id($1)) { val = NEW_LVAR($1); } else if (is_global_id($1)) { val = NEW_GVAR($1); } else if (is_instance_id($1)) { val = NEW_IVAR($1); } else { val = NEW_CVAR($1); } $$ = asignable($1, call_op(val, $2, 1, $3)); } | primary '[' opt_args rbracket OP_ASGN arg { NODE *args = NEW_LIST($6); if ($3) list_concat(args, $3); $$ = NEW_OP_ASGN1($1, $5, args); } | primary '.' IDENTIFIER OP_ASGN arg { $$ = NEW_OP_ASGN2($1, $4, $5); } | arg DOT2 arg { $$ = call_op($1, DOT2, 1, $3); } | arg DOT3 arg { $$ = NEW_DOT3(cond2($1), cond2($3)); } | arg '+' arg { $$ = Qnil; if ($1 && $3 && (nd_type($3) == NODE_LIT || nd_type($3) == NODE_STR) && nd_type($1) == NODE_CALL && $1->nd_mid == '+') { if ($1->nd_args->nd_head == Qnil) Bug("bad operand for `+'"); if (nd_type($1->nd_args->nd_head) == NODE_LIT || nd_type($1->nd_args->nd_head) == NODE_STR) { $1->nd_args->nd_head = expand_op($1->nd_args->nd_head, '+', $3); $$ = $1; } } if ($$ == Qnil) { $$ = call_op($1, '+', 1, $3); } } | arg '-' arg { $$ = call_op($1, '-', 1, $3); } | arg '*' arg { $$ = call_op($1, '*', 1, $3); } | arg '/' arg { $$ = call_op($1, '/', 1, $3); } | arg '%' arg { $$ = call_op($1, '%', 1, $3); } | arg POW arg { $$ = call_op($1, POW, 1, $3); } | UPLUS arg { $$ = call_op($2, UPLUS, 0); } | UMINUS arg { $$ = call_op($2, UMINUS, 0); } | arg '|' arg { $$ = call_op($1, '|', 1, $3); } | arg '^' arg { $$ = call_op($1, '^', 1, $3); } | arg '&' arg { $$ = call_op($1, '&', 1, $3); } | arg CMP arg { $$ = call_op($1, CMP, 1, $3); } | arg '>' arg { $$ = call_op($1, '>', 1, $3); } | arg GEQ arg { $$ = call_op($1, GEQ, 1, $3); } | arg '<' arg { $$ = call_op($1, '<', 1, $3); } | arg LEQ arg { $$ = call_op($1, LEQ, 1, $3); } | arg EQ arg { $$ = call_op($1, EQ, 1, $3); } | arg NEQ arg { $$ = NEW_NOT(call_op($1, EQ, 1, $3)); } | arg MATCH arg { $$ = NEW_CALL($1, MATCH, NEW_LIST($3)); } | arg NMATCH arg { $$ = NEW_NOT(NEW_CALL($1, MATCH, NEW_LIST($3))); } | '!' arg { $$ = NEW_NOT(cond($2)); } | '~' arg { if ($2 && (nd_type($2) == NODE_STR || (nd_type($2) == NODE_LIT && (TYPE($2->nd_lit) == T_REGEXP || TYPE($2->nd_lit) == T_STRING)))) { $$ = NEW_CALL($2, '~', Qnil); } else { $$ = call_op($2, '~', 0); } } | arg LSHFT arg { $$ = call_op($1, LSHFT, 1, $3); } | arg RSHFT arg { $$ = call_op($1, RSHFT, 1, $3); } | arg COLON2 arg { $$ = call_op($1, COLON2, 1, $3); } | arg ANDOP arg { $$ = NEW_AND(cond($1), cond($3)); } | arg OROP arg { $$ = NEW_OR(cond($1), cond($3)); } | primary { $$ = $1; } call_args : /* none */ { $$ = Qnil; } | call_args0 | '*' arg { $$ = $2; } call_args0 : args | assocs { $$ = NEW_LIST(NEW_HASH($1)); } | args comma assocs { $$ = list_append($1, NEW_HASH($3)); } | args comma '*' arg { $$ = call_op($1, '+', 1, $4); } opt_args : /* none */ { $$ = Qnil; } | args args : arg { value_expr($1); $$ = NEW_LIST($1); } | args comma arg { value_expr($3); $$ = list_append($1, $3); } args2 : args { if ($1 && $1->nd_next == Qnil) { $$ = $1->nd_head; } else { $$ = $1; } } primary : literal { $$ = NEW_LIT($1); } | STRING { $$ = NEW_STR($1); } | STRING2 | XSTRING { $$ = NEW_XSTR($1); } | XSTRING2 | DREGEXP | DGLOB | var_ref | SUPER '(' call_args rparen { if (!cur_mid && !in_single) Error("super called outside of method"); $$ = NEW_SUPER($3); } | SUPER { if (!cur_mid && !in_single) Error("super called outside of method"); $$ = NEW_ZSUPER(); } | primary '[' opt_args rbracket { value_expr($1); $$ = NEW_CALL($1, AREF, $3); } | LBRACK opt_args rbracket { if ($2 == Qnil) $$ = NEW_ZARRAY(); /* zero length array*/ else { $$ = $2; } } | LBRACE assoc_list rbrace { $$ = NEW_HASH($2); } | REDO { $$ = NEW_REDO(); } | BREAK { $$ = NEW_BREAK(); } | CONTINUE { $$ = NEW_CONT(); } | RETRY { $$ = NEW_RETRY(); } | RETURN { if (!cur_mid && !in_single) Error("return appeared outside of method"); $$ = NEW_RET(Qnil); } | FAIL '(' args2 ')' { if (nd_type($3) == NODE_ARRAY) { Error("wrong number of argument to fail(0 or 1)"); } value_expr($3); $$ = NEW_FAIL($3); } | FAIL '(' ')' { $$ = NEW_FAIL(Qnil); } | FAIL { $$ = NEW_FAIL(Qnil); } | YIELD '(' args2 ')' { value_expr($3); $$ = NEW_YIELD($3); } | YIELD '(' ')' { $$ = NEW_YIELD(Qnil); } | YIELD { $$ = NEW_YIELD(Qnil); } | primary '{' opt_iter_var '|' compexpr rbrace { if (nd_type($1) == NODE_LVAR || nd_type($1) == NODE_LVAR2 || nd_type($1) == NODE_CVAR) { $1 = NEW_CALL(Qnil, $1->nd_vid, Qnil); } $$ = NEW_ITER($3, $1, $5); } | IDENTIFIER '(' call_args rparen { $$ = NEW_CALL(Qnil, $1, $3); } | primary '.' IDENTIFIER '(' call_args rparen { value_expr($1); $$ = NEW_CALL($1, $3, $5); } | primary '.' IDENTIFIER { value_expr($1); $$ = NEW_CALL($1, $3, Qnil); } | IF expr then compexpr if_tail END { $$ = NEW_IF(cond($2), $4, $5); } | WHILE expr term compexpr END { $$ = NEW_WHILE(cond($2), $4); } | CASE compexpr case_body END { value_expr($2); $$ = NEW_CASE($2, $3); } | FOR iter_var IN expr term compexpr END { value_expr($4); $$ = NEW_FOR($2, $4, $6); } | BEGIN compexpr resque ensure END { if ($3 == Qnil && $4 == Qnil) { $$ = $2; } else { $$ = NEW_BEGIN($2, $3, $4); } } | LPAREN expr opt_nl rparen { $$ = $2; } | CLASS CONSTANT superclass { if (cur_mid || in_single) Error("class definition in method body"); class_nest++; cref_push(); local_push(); } compexpr END { $$ = NEW_CLASS($2, $5, $3); local_pop(); cref_pop(); class_nest--; } | MODULE CONSTANT { if (cur_mid || in_single) Error("module definition in method body"); class_nest++; cref_push(); local_push(); } compexpr END { $$ = NEW_MODULE($2, $4); local_pop(); cref_pop(); class_nest--; } | DEF fname { if (cur_mid || in_single) Error("nested method definition"); cur_mid = $2; local_push(); } f_arglist compexpr END { $$ = NEW_DEFN($2, $4, $5, class_nest?1:0); local_pop(); cur_mid = Qnil; } | DEF singleton '.' fname { value_expr($2); in_single++; local_push(); } f_arglist compexpr END { $$ = NEW_DEFS($2, $4, $6, $7); local_pop(); in_single--; } then : term | THEN | term THEN if_tail : opt_else | ELSIF expr then compexpr if_tail { $$ = NEW_IF(cond($2), $4, $5); } opt_else : /* none */ { $$ = Qnil; } | ELSE compexpr { $$ = $2; } iter_var : lhs | mlhs opt_iter_var : /* none */ { $$ = Qnil; } | iter_var case_body : WHEN args then compexpr cases { $$ = NEW_WHEN($2, $4, $5); } cases : opt_else | case_body resque : /* none */ { $$ = Qnil; } | RESQUE compexpr { if ($2 == Qnil) $$ = (NODE*)1; else $$ = $2; } ensure : /* none */ { $$ = Qnil; } | ENSURE compexpr { $$ = $2; } literal : numeric | SYMBEG symbol { $$ = INT2FIX($2); } | REGEXP | GLOB symbol : fname | IVAR | GVAR numeric : INTEGER | FLOAT variable : IDENTIFIER | IVAR | GVAR | CONSTANT | NIL { $$ = NIL; } | SELF { $$ = SELF; } var_ref : variable { $$ = gettable($1); } superclass : term { $$ = Qnil; } | colon { lex_state = EXPR_BEG; } CONSTANT { $$ = $3; } f_arglist : '(' f_args rparen { $$ = $2; } | term { $$ = NEW_ARGS(0, -1); } f_args : /* no arg */ { $$ = NEW_ARGS(0, -1); } | f_arg { $$ = NEW_ARGS($1, -1); } | f_arg comma rest_arg { $$ = NEW_ARGS($1, $3); } | rest_arg { $$ = NEW_ARGS(Qnil, $1); } | f_arg error { lex_state = EXPR_BEG; $$ = NEW_ARGS($1, -1); } | error { lex_state = EXPR_BEG; $$ = NEW_ARGS(0, -1); } f_arg : IDENTIFIER { if (!is_local_id($1)) Error("formal argument must be local variable"); local_cnt($1); $$ = 1; } | f_arg comma IDENTIFIER { if (!is_local_id($3)) Error("formal argument must be local variable"); local_cnt($3); $$ += 1; } rest_arg : '*' IDENTIFIER { if (!is_local_id($2)) Error("rest argument must be local variable"); $$ = local_cnt($2); } singleton : var_ref { if (nd_type($1) == NODE_SELF) { $$ = NEW_SELF(); } else if (nd_type($1) == NODE_NIL) { Error("Can't define single method for nil."); $$ = Qnil; } else { $$ = $1; } } | LPAREN compexpr rparen { switch (nd_type($2)) { case NODE_STR: case NODE_STR2: case NODE_XSTR: case NODE_XSTR2: case NODE_DREGX: case NODE_DGLOB: case NODE_LIT: case NODE_ARRAY: case NODE_ZARRAY: Error("Can't define single method for literals."); default: break; } $$ = $2; } assoc_list : /* none */ { $$ = Qnil; } | assocs | args { if ($1->nd_alen%2 != 0) { Error("odd number list for Dict"); } $$ = $1; } assocs : assoc | assocs comma assoc { $$ = list_concat($1, $3); } assoc : arg ASSOC arg { $$ = list_append(NEW_LIST($1), $3); } opt_term : /* none */ | term opt_nl : /* none */ | nl term : sc | nl sc : ';' { yyerrok; } nl : '\n' { yyerrok; } colon : ':' | SYMBEG rparen : ')' { yyerrok; } rbracket : ']' { yyerrok; } rbrace : '}' { yyerrok; } comma : ',' { yyerrok; } %% #include #include #include "regex.h" #define is_identchar(c) ((c)!=-1&&(isalnum(c) || (c) == '_' || ismbchar(c))) static char *tokenbuf = NULL; static int tokidx, toksiz = 0; VALUE newregexp(); VALUE newstring(); VALUE newfloat(); VALUE newinteger(); char *strdup(); #define EXPAND_B 1 #define LEAVE_BS 2 static NODE *var_extend(); static void read_escape(); static char *lex_p; static int lex_len; void lex_setsrc(src, ptr, len) char *src; char *ptr; int len; { sourcefile = (char*)strdup(src); sourceline = 1; lex_p = ptr; lex_len = len; } #define nextc() ((--lex_len>=0)?(*lex_p++):-1) #define pushback() (lex_len++, lex_p--) #define tokfix() (tokenbuf[tokidx]='\0') #define tok() tokenbuf #define toklen() tokidx #define toknow() &toknbuf[tokidx] char * newtok() { tokidx = 0; if (!tokenbuf) { toksiz = 60; tokenbuf = ALLOC_N(char, 60); } if (toksiz > 1024) { REALLOC_N(tokenbuf, char, 60); } return tokenbuf; } void tokadd(c) char c; { if (tokidx >= toksiz) { toksiz *= 2; REALLOC_N(tokenbuf, char, toksiz); } tokenbuf[tokidx++] = c; } static int parse_regx() { register int c; int casefold = 0; int in_brack = 0; int re_start = sourceline; NODE *list = Qnil; newtok(); while (c = nextc()) { switch (c) { case '[': in_brack = 1; break; case ']': in_brack = 0; break; case '#': list = var_extend(list, '/'); if (list == (NODE*)-1) return 0; continue; case '\\': if ((c = nextc()) == -1) { sourceline = re_start; Error("unterminated regexp meets end of file"); return 0; } else if (c == '\n') { sourceline++; } else if (in_brack && c == 'b') { tokadd('\b'); } else { pushback(); read_escape(LEAVE_BS); } continue; case '/': /* end of the regexp */ if (in_brack) break; if ('i' == nextc()) { casefold = 1; } else { pushback(); } tokfix(); lex_state = EXPR_END; if (list) { if (toklen() > 0) { VALUE ss = str_new(tok(), toklen()); list_append(list, NEW_STR(ss)); } nd_set_type(list, NODE_DREGX); if (casefold) list->nd_cflag = 1; yylval.node = list; return DREGEXP; } else { yylval.val = regexp_new(tok(), toklen(), casefold); return REGEXP; } case -1: Error("unterminated regexp"); return 0; default: if (ismbchar(c)) { tokadd(c); c = nextc(); } break; } tokadd(c); } } static int parse_string(term) int term; { int c; NODE *list = Qnil; ID id; int strstart; strstart = sourceline; newtok(); while ((c = nextc()) != term) { if (c == -1) { unterm_str: sourceline = strstart; Error("unterminated string meets end of file"); return 0; } if (ismbchar(c)) { tokadd(c); c = nextc(); } else if (c == '\n') { sourceline++; } else if (c == '#') { list = var_extend(list, term); if (list == (NODE*)-1) goto unterm_str; continue; } else if (c == '\\') { c = nextc(); if (c == '\n') { sourceline++; } else if (c == term) { tokadd(c); } else { int flags = EXPAND_B; if (term != '"') flags |= LEAVE_BS; pushback(); read_escape(flags); } continue; } tokadd(c); } tokfix(); lex_state = EXPR_END; if (list == Qnil) { yylval.val = str_new(tok(), toklen()); return (term == '`') ? XSTRING : STRING; } else { if (toklen() > 0) { VALUE ss = str_new(tok(), toklen()); list_append(list, NEW_STR(ss)); } yylval.node = list; if (term == '`') { nd_set_type(list, NODE_XSTR2); return XSTRING2; } else { return STRING2; } } } #define LAST(v) ((v)-1 + sizeof(v)/sizeof(v[0])) static struct kwtable { char *name; int id; enum lex_state state; } kwtable [] = { "__END__", 0, EXPR_BEG, "__FILE__", _FILE_, EXPR_END, "__LINE__", _LINE_, EXPR_END, "alias", ALIAS, EXPR_FNAME, "and", AND, EXPR_BEG, "begin", BEGIN, EXPR_BEG, "break", BREAK, EXPR_END, "case", CASE, EXPR_BEG, "class", CLASS, EXPR_BEG, "continue", CONTINUE, EXPR_END, "def", DEF, EXPR_FNAME, "else", ELSE, EXPR_BEG, "elsif", ELSIF, EXPR_BEG, "end", END, EXPR_END, "ensure", ENSURE, EXPR_BEG, "fail", FAIL, EXPR_END, "for", FOR, EXPR_BEG, "if", IF, EXPR_BEG, "in", IN, EXPR_BEG, "include", INCLUDE, EXPR_BEG, "module", MODULE, EXPR_BEG, "nil", NIL, EXPR_END, "or", OR, EXPR_BEG, "redo", REDO, EXPR_END, "resque", RESQUE, EXPR_BEG, "retry", RETRY, EXPR_END, "return", RETURN, EXPR_MID, "self", SELF, EXPR_END, "super", SUPER, EXPR_END, "then", THEN, EXPR_BEG, "undef", UNDEF, EXPR_FNAME, "when", WHEN, EXPR_BEG, "while", WHILE, EXPR_BEG, "yield", YIELD, EXPR_END, }; static int yylex() { register int c; struct kwtable *low = kwtable, *mid, *high = LAST(kwtable); retry: switch (c = nextc()) { case '\0': /* NUL */ case '\004': /* ^D */ case '\032': /* ^Z */ case -1: /* end of script. */ return 0; /* white spaces */ case ' ': case '\t': case '\f': case '\r': case '\13': /* '\v' */ goto retry; case '#': /* it's a comment */ while ((c = nextc()) != '\n') { if (c == -1) return 0; if (c == '\\') { /* skip a char */ c = nextc(); if (c == '\n') sourceline++; } } /* fall through */ case '\n': sourceline++; if (lex_state == EXPR_BEG || lex_state == EXPR_FNAME) goto retry; lex_state = EXPR_BEG; return '\n'; case '*': lex_state = EXPR_BEG; if ((c = nextc()) == '*') { if (nextc() == '=') { yylval.id = POW; return OP_ASGN; } pushback(); return POW; } else if (c == '=') { yylval.id = '*'; return OP_ASGN; } pushback(); return '*'; case '!': lex_state = EXPR_BEG; if ((c = nextc()) == '=') { return NEQ; } if (c == '~') { return NMATCH; } pushback(); return '!'; case '=': lex_state = EXPR_BEG; if ((c = nextc()) == '=') { return EQ; } if (c == '~') { return MATCH; } else if (c == '>') { return ASSOC; } pushback(); return '='; case '<': if (lex_state == EXPR_BEG) { if (parse_string('>') == STRING) { yylval.val = glob_new(yylval.val); return GLOB; } nd_set_type(yylval.node, NODE_DGLOB); return DGLOB; } lex_state = EXPR_BEG; if ((c = nextc()) == '=') { if ((c = nextc()) == '>') { return CMP; } pushback(); return LEQ; } if (c == '<') { if (nextc() == '=') { yylval.id = LSHFT; return OP_ASGN; } pushback(); return LSHFT; } pushback(); return '<'; case '>': lex_state = EXPR_BEG; if ((c = nextc()) == '=') { return GEQ; } if (c == '>') { if (nextc() == '=') { yylval.id = RSHFT; return OP_ASGN; } pushback(); return RSHFT; } pushback(); return '>'; case '"': case '`': return parse_string(c); case '\'': { int strstart; strstart = sourceline; newtok(); while ((c = nextc()) != '\'') { if (c == -1) { sourceline = strstart; Error("unterminated string meets end of file"); return 0; } if (ismbchar(c)) { tokadd(c); c = nextc(); } else if (c == '\n') { sourceline++; } else if (c == '\\') { c = nextc(); switch (c) { case '\n': sourceline++; continue; case '\'': c = '\''; break; case '\\': c = '\\'; break; default: tokadd('\\'); } } tokadd(c); } tokfix(); yylval.val = str_new(tok(), toklen()); lex_state = EXPR_END; return STRING; } case '?': if ((c = nextc()) == '\\') { newtok(); read_escape(EXPAND_B); c = tok()[0]; } c &= 0xff; yylval.val = INT2FIX(c); lex_state = EXPR_END; return INTEGER; case '&': lex_state = EXPR_BEG; if ((c = nextc()) == '&') { return ANDOP; } else if (c == '=') { yylval.id = '&'; return OP_ASGN; } pushback(); return '&'; case '|': lex_state = EXPR_BEG; if ((c = nextc()) == '|') { return OROP; } else if (c == '=') { yylval.id = '|'; return OP_ASGN; } pushback(); return '|'; case '+': if (lex_state == EXPR_FNAME) { if ((c = nextc()) == '@') { return UPLUS; } pushback(); return '+'; } c = nextc(); if (lex_state != EXPR_END) { pushback(); if (isdigit(c)) { goto start_num; } lex_state = EXPR_BEG; return UMINUS; } lex_state = EXPR_BEG; if (c == '=') { yylval.id = '+'; return OP_ASGN; } pushback(); return '+'; case '-': if (lex_state == EXPR_FNAME) { if ((c = nextc()) == '@') { return UMINUS; } pushback(); return '-'; } c = nextc(); if (lex_state != EXPR_END) { pushback(); if (isdigit(c)) { c = '-'; goto start_num; } lex_state = EXPR_BEG; return UMINUS; } lex_state = EXPR_BEG; if (c == '=') { yylval.id = '-'; return OP_ASGN; } pushback(); return '-'; case '.': lex_state = EXPR_BEG; if ((c = nextc()) == '.') { if ((c = nextc()) == '.') { return DOT3; } pushback(); return DOT2; } pushback(); if (!isdigit(c)) { return '.'; } c = '.'; /* fall through */ start_num: case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { int is_float, seen_point, seen_e; lex_state = EXPR_END; newtok(); if (c == '0') { c = nextc(); if (c == 'x' || c == 'X') { /* hexadecimal */ while (c = nextc()) { if (!isxdigit(c)) break; tokadd(c); } pushback(); tokfix(); yylval.val = str2inum(tok(), 16); return INTEGER; } else if (c >= '0' && c <= '9') { /* octal */ do { tokadd(c); c = nextc(); } while (c >= '0' && c <= '9'); pushback(); tokfix(); yylval.val = str2inum(tok(), 8); return INTEGER; } } if (c == '-' || c == '+') { tokadd(c); c = nextc(); } is_float = seen_point = seen_e = 0; for (;;) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': tokadd(c); break; case '.': if (seen_point) { goto decode_num; } c = nextc(); if (!isdigit(c)) { pushback(); goto decode_num; } tokadd('.'); tokadd(c); is_float++; seen_point++; break; case 'e': case 'E': if (seen_e) { goto decode_num; } tokadd(c); seen_e++; is_float++; if ((c = nextc()) == '-' || c == '+') tokadd(c); else continue; break; case '_': /* `_' in decimal just ignored */ break; default: goto decode_num; } c = nextc(); } decode_num: pushback(); tokfix(); if (is_float) { double atof(); yylval.val = float_new(atof(tok())); return FLOAT; } yylval.val = str2inum(tok(), 10); return INTEGER; } case ']': case '}': case ')': lex_state = EXPR_END; return c; case ':': c = nextc(); if (c == ':') { lex_state = EXPR_BEG; return COLON2; } pushback(); if (isspace(c)) return ':'; return SYMBEG; case '/': if (lex_state == EXPR_BEG || lex_state == EXPR_MID) { return parse_regx(); } lex_state = EXPR_BEG; if (nextc() == '=') { yylval.id = '/'; return OP_ASGN; } pushback(); return c; case '^': lex_state = EXPR_BEG; if (nextc() == '=') { yylval.id = '^'; return OP_ASGN; } pushback(); return c; case ',': case ';': lex_state = EXPR_BEG; return c; case '~': if (lex_state == EXPR_FNAME) { if ((c = nextc()) != '@') { pushback(); } } lex_state = EXPR_BEG; return c; case '(': if (lex_state != EXPR_END) c = LPAREN; lex_state = EXPR_BEG; return c; case '[': if (lex_state == EXPR_BEG || lex_state == EXPR_MID) c = LBRACK; else if (lex_state == EXPR_FNAME) { if ((c = nextc()) == ']') { if ((c = nextc()) == '=') { return ASET; } pushback(); return AREF; } pushback(); return '['; } lex_state = EXPR_BEG; return c; case '{': if (lex_state != EXPR_END) c = LBRACE; lex_state = EXPR_BEG; return c; case '\\': c = nextc(); if (c == '\n') { sourceline++; goto retry; /* skip \\n */ } pushback(); return '\\'; case '%': lex_state = EXPR_BEG; if (nextc() == '=') { yylval.id = '%'; return OP_ASGN; } pushback(); return c; case '$': newtok(); tokadd(c); c = nextc(); switch (c) { case '*': /* $*: argv */ case '$': /* $$: pid */ case '?': /* $?: last status */ case '!': /* $!: error string */ case '@': /* $@: error position */ case '/': /* $/: input record separator */ case '\\': /* $\: output record separator */ case ',': /* $,: output field separator */ case '.': /* $.: last read line number */ case '_': /* $_: last read line string */ case '&': /* $&: last match */ case '`': /* $&: string before last match */ case '\'': /* $&: string after last match */ case '+': /* $&: string matches last paren. */ case '~': /* $~: match-data */ case '=': /* $=: ignorecase */ case ':': /* $:: load path */ case '<': /* $<: reading filename */ case '>': /* $>: default output handle */ case '"': /* $": already loaded files */ tokadd(c); tokfix(); yylval.id = rb_intern(tok()); lex_state = EXPR_END; return GVAR; default: if (!is_identchar(c)) { pushback(); return '$'; } } break; case '@': c = nextc(); if (!is_identchar(c)) { pushback(); return '@'; } newtok(); tokadd('@'); break; default: if (c != '_' && !isalpha(c) && !ismbchar(c)) { Error("Invalid char '%c' in expression", c); goto retry; } newtok(); break; } while (is_identchar(c)) { tokadd(c); if (ismbchar(c)) { c = nextc(); tokadd(c); } c = nextc(); } pushback(); tokfix(); { int result; switch (tok()[0]) { case '$': result = GVAR; break; case '@': result = IVAR; break; default: /* See if it is a reserved word. */ while (low <= high) { mid = low + (high - low)/2; if (( c = strcmp(mid->name, tok())) == 0) { enum lex_state state = lex_state; lex_state = mid->state; if (state != EXPR_BEG) { if (mid->id == IF) return IF_MOD; if (mid->id == WHILE) return WHILE_MOD; } return mid->id; } else if (c < 0) { low = mid + 1; } else { high = mid - 1; } } if (lex_state == EXPR_FNAME) { if ((c = nextc()) == '=') { tokadd(c); } else { pushback(); } } if (isupper(tok()[0])) { result = CONSTANT; } else { result = IDENTIFIER; } } lex_state = EXPR_END; yylval.id = rb_intern(tok()); return result; } } static NODE* var_extend(list, term) NODE *list; char term; { int c, t; VALUE ss; ID id; c = nextc(); switch (c) { default: tokadd('#'); pushback(); return list; case '@': t = nextc(); pushback(); if (!is_identchar(t)) { tokadd('#'); tokadd(c); return list; } case '$': case '{': break; } ss = str_new(tok(), toklen()); if (list == Qnil) { list = NEW_STR2(ss); } else if (toklen() > 0) { list_append(list, NEW_STR(ss)); } newtok(); if (c == '{') { while ((c = nextc()) != '}') { if (c == -1) { return (NODE*)-1; } if (isspace(c)) { Error("Invalid variable name in string"); break; } if (c == term) { Error("Inmature variable name in string"); pushback(); return list; } tokadd(c); } } else { switch (c) { case '$': tokadd(c); c = nextc(); if (c == -1) return (NODE*)-1; if (!is_identchar(c)) { tokadd(c); goto fetch_id; } /* through */ case '@': tokadd(c); c = nextc(); break; } while (is_identchar(c)) { tokadd(c); if (ismbchar(c)) { c = nextc(); tokadd(c); } c = nextc(); } pushback(); } fetch_id: tokfix(); if (strcmp("__LINE__", tok()) == 0) id = _LINE_; else if (strcmp("__FILE__", tok()) == 0) id = _FILE_; else id = rb_intern(tok()); list_append(list, gettable(id)); newtok(); return list; } static void read_escape(flag) int flag; { char c; switch (c = nextc()) { case '\\': /* Backslash */ tokadd('\\'); break; case 'n': /* newline */ tokadd('\n'); break; case 't': /* horizontal tab */ tokadd('\t'); break; case 'r': /* carriage-return */ tokadd('\r'); break; case 'f': /* form-feed */ tokadd('\f'); break; case 'v': /* vertical tab */ tokadd('\13'); break; case 'a': /* alarm(bell) */ tokadd('\007'); break; case 'e': /* escape */ tokadd(033); break; case 'M': if ((c = nextc()) != '-') { Error("Invalid escape character syntax"); tokadd('\0'); return; } if ((c = nextc()) == '\\') { read_escape(flag); tokenbuf[tokidx-1] |= 0200; /* kludge */ } else { tokadd((c & 0xff) | 0200); } break; case 'C': if ((c = nextc()) != '-') { Error("Invalid escape character syntax"); tokadd('\0'); return; } case '^': if ((c = nextc())== '\\') { read_escape (flag); tokenbuf[tokidx-1] &= 0237; /* kludge */ } else if (c == '?') tokadd(0177); else tokadd(c & 0237); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { /* octal constant */ register int i = c - '0'; register int count = 0; while (++count < 3) { if ((c = nextc()) >= '0' && c <= '7') { i *= 8; i += c - '0'; } else { pushback(); break; } } tokadd(i&0xff); } break; case 'x': /* hex constant */ { register int i = 0; register int count = 0; while (++count < 3) { if ((c = nextc()) >= '0' && c <= '9') { i *= 16; i += c - '0'; } else if ((int)strchr("abcdefABCDEF", (c = nextc()))) { i *= 16; i += toupper(c) - 'A' + 10; } else { pushback(); break; } } tokadd(i&0xff); } break; case 'b': /* backspace */ if (flag & EXPAND_B) { tokadd('\b'); return; } /* go turough */ default: if (flag & LEAVE_BS) { tokadd('\\'); } case '#': tokadd(c); break; } } NODE* newnode(type, a0, a1, a2) enum node_type type; NODE *a0, *a1, *a2; { NODE *n = (NODE*)newobj(); n->flags |= T_NODE; nd_set_type(n, type); n->line = sourceline; n->file = sourcefile; n->u1.node = a0; n->u2.node = a1; n->u3.node = a2; return n; } enum node_type nodetype(node) /* for debug */ NODE *node; { return (enum node_type)nd_type(node); } static NODE* block_append(head, tail) NODE *head, *tail; { extern int verbose; NODE *last; if (tail == Qnil) return head; if (head == Qnil) return tail; if (nd_type(head) != NODE_BLOCK) head = last = NEW_BLOCK(head); else { last = head; while (last->nd_next) { last = last->nd_next; } } if (verbose) { switch (nd_type(last->nd_head)) { case NODE_BREAK: case NODE_CONTINUE: case NODE_REDO: case NODE_RETURN: case NODE_RETRY: Warning("statement not reached"); break; default: break; } } if (nd_type(tail) != NODE_BLOCK) { tail = NEW_BLOCK(tail); } last->nd_next = tail; head->nd_alen += tail->nd_alen; return head; } static NODE* list_append(head, tail) NODE *head, *tail; { NODE *last; if (head == Qnil) return NEW_LIST(tail); last = head; while (last->nd_next) { last = last->nd_next; } last->nd_next = NEW_LIST(tail); head->nd_alen += 1; return head; } static NODE* list_concat(head, tail) NODE *head, *tail; { NODE *last; #if 0 if (nd_type(head) != NODE_ARRAY || nd_type(tail) != NODE_ARRAY) Bug("list_concat() called with non-list"); #endif last = head; while (last->nd_next) { last = last->nd_next; } last->nd_next = tail; head->nd_alen += tail->nd_alen; return head; } static NODE* list_copy(list) NODE *list; { NODE *tmp; if (list == Qnil) return Qnil; tmp = Qnil; while(list) { tmp = list_append(tmp, list->nd_head); list = list->nd_next; } return tmp; } struct call_arg { ID id; VALUE recv; int narg; VALUE arg; }; static VALUE call_lit(arg) struct call_arg *arg; { return rb_funcall(arg->recv, arg->id, arg->narg, arg->arg); } static VALUE except_lit() { extern VALUE errstr; Error("%s", RSTRING(errstr)->ptr); return Qnil; } static NODE * expand_op(recv, id, arg) NODE *recv, *arg; ID id; { struct call_arg arg_data; VALUE val; NODE *result; arg_data.recv = recv->nd_lit; arg_data.id = id; arg_data.narg = arg?1:0; arg_data.arg = arg->nd_lit; val = rb_resque(call_lit, &arg_data, except_lit, Qnil); if (TYPE(val) == T_STRING) { result = NEW_STR(val); } else { result = NEW_LIT(val); } return result; } #define NODE_IS_CONST(n) (nd_type(n) == NODE_LIT || nd_type(n) == NODE_STR) static NODE * call_op(recv, id, narg, arg1) NODE *recv; ID id; int narg; NODE *arg1; { value_expr(recv); if (narg == 1) { value_expr(arg1); } if (NODE_IS_CONST(recv) && (narg == 0 || NODE_IS_CONST(arg1))) { return expand_op(recv, id, (narg == 1)?arg1:Qnil); } return NEW_CALL(recv, id, narg==1?NEW_LIST(arg1):Qnil); } static NODE* gettable(id) ID id; { if (id == SELF) { return NEW_SELF(); } else if (id == NIL) { return NEW_NIL(); } else if (id == _LINE_) { return NEW_LIT(INT2FIX(sourceline)); } else if (id == _FILE_) { VALUE s = str_new2(sourcefile); return NEW_STR(s); } else if (is_local_id(id)) { if (local_id(id)) return NEW_LVAR(id); else return NEW_LVAR2(id); } else if (is_global_id(id)) { return NEW_GVAR(id); } else if (is_instance_id(id)) { return NEW_IVAR(id); } else if (is_const_id(id)) { return NEW_CVAR(id); } } static NODE* asignable(id, val) ID id; NODE *val; { NODE *lhs; if (id == SELF) { lhs = Qnil; Error("Can't change the value of self"); } else if (id == NIL) { lhs = Qnil; Error("Can't asign to nil"); } else if (id == _LINE_ || id == _FILE_) { Error("Can't asign to special identifier"); } else if (is_local_id(id)) { lhs = NEW_LASGN(id, val); } else if (is_global_id(id)) { lhs = NEW_GASGN(id, val); } else if (is_instance_id(id)) { lhs = NEW_IASGN(id, val); } else if (is_const_id(id)) { if (cur_mid || in_single) Error("class constant asigned in method body"); lhs = NEW_CASGN(id, val); } else { Bug("bad id for variable"); } return lhs; } static NODE * aryset(recv, idx, val) NODE *recv, *idx, *val; { value_expr(recv); value_expr(val); return NEW_CALL(recv, ASET, list_append(idx, val)); } static NODE * attrset(recv, id, val) NODE *recv, *val; ID id; { value_expr(recv); value_expr(val); id &= ~ID_SCOPE_MASK; id |= ID_ATTRSET; return NEW_CALL(recv, id, NEW_LIST(val)); } static int value_expr(node) NODE *node; { if (node == Qnil) return TRUE; switch (nd_type(node)) { case NODE_RETURN: case NODE_CONTINUE: case NODE_BREAK: case NODE_REDO: case NODE_RETRY: case NODE_FAIL: case NODE_WHILE: case NODE_WHILE2: case NODE_INC: case NODE_CLASS: case NODE_MODULE: case NODE_DEFN: case NODE_DEFS: Error("void value expression"); return FALSE; break; case NODE_BLOCK: while (node->nd_next) { node = node->nd_next; } return value_expr(node->nd_head); case NODE_IF: return value_expr(node->nd_body) && value_expr(node->nd_else); default: return TRUE; } } static NODE* cond0(node) NODE *node; { enum node_type type = nd_type(node); if (type == NODE_STR || type == NODE_STR2 || type == NODE_DREGX) { return call_op(NEW_GVAR(rb_intern("$_")),MATCH,1,node); } else if (type == NODE_LIT && TYPE(node->nd_lit) == T_REGEXP) { return call_op(node,MATCH,1,NEW_GVAR(rb_intern("$_"))); } return node; } static NODE* cond(node) NODE *node; { enum node_type type = nd_type(node); value_expr(node); switch (type) { case NODE_MASGN: case NODE_LASGN: case NODE_GASGN: case NODE_IASGN: case NODE_CASGN: Warning("asignment in condition"); break; } node = cond0(node); if (type == NODE_CALL && node->nd_mid == '!') { if (node->nd_args || node->nd_recv == Qnil) { Bug("method `!' called with wrong # of operand"); } node->nd_recv = cond0(node->nd_recv); } return node; } static NODE* cond2(node) NODE *node; { node = cond(node); if (nd_type(node) == NODE_LIT && FIXNUM_P(node->nd_lit)) { return call_op(node,EQ,1,NEW_GVAR(rb_intern("$."))); } return node; } st_table *new_idhash(); static struct local_vars { ID *tbl; int cnt; struct local_vars *prev; } *lvtbl; static void local_push() { struct local_vars *local; local = ALLOC(struct local_vars); local->prev = lvtbl; local->cnt = 0; local->tbl = Qnil; lvtbl = local; } static void local_pop() { struct local_vars *local = lvtbl; lvtbl = local->prev; if (local->tbl) local->tbl[0] = local->cnt; free(local); } static ID* local_tbl() { return lvtbl->tbl; } static int local_cnt(id) ID id; { int cnt, max; if (id == 0) return lvtbl->cnt; for (cnt=0, max=lvtbl->cnt; cnttbl[cnt+1] == id) return cnt; } if (lvtbl->tbl == Qnil) { lvtbl->tbl = ALLOC_N(ID, 2); lvtbl->tbl[0] = 0; } else { REALLOC_N(lvtbl->tbl, ID, lvtbl->cnt+2); } lvtbl->tbl[lvtbl->cnt+1] = id; return lvtbl->cnt++; } static int local_id(id) ID id; { int i, max; if (lvtbl == Qnil) return FALSE; for (i=1, max=lvtbl->cnt+1; itbl[i] == id) return TRUE; } return FALSE; } static void top_local_init() { if (lvtbl == Qnil) { local_push(); } else if (the_scope->local_tbl) { lvtbl->cnt = the_scope->local_tbl[0]; } else { lvtbl->cnt = 0; } if (lvtbl->cnt > 0) { lvtbl->tbl = ALLOC_N(ID, lvtbl->cnt+1); MEMCPY(lvtbl->tbl, the_scope->local_tbl, ID, lvtbl->cnt); } else { lvtbl->tbl = Qnil; } NEW_CREF0(); /* initialize constant c-ref */ } static void top_local_setup() { int len = lvtbl->cnt; int i; if (len > 0) { i = lvtbl->tbl[0]; if (i < len) { if (the_scope->flags & SCOPE_MALLOCED) { VALUE *vars = the_scope->local_vars; REALLOC_N(the_scope->local_vars, VALUE, len); MEMZERO(the_scope->local_vars+i, VALUE, len-i); free(the_scope->local_tbl); } else { VALUE *vars = the_scope->local_vars; the_scope->local_vars = ALLOC_N(VALUE, len); if (vars) { MEMCPY(the_scope->local_vars, vars, VALUE, i); MEMZERO(the_scope->local_vars+i, VALUE, len-i); } else { MEMZERO(the_scope->local_vars, VALUE, len); } } lvtbl->tbl[0] = len; the_scope->local_tbl = lvtbl->tbl; the_scope->flags |= SCOPE_MALLOCED; } else if (lvtbl->tbl) { free(lvtbl->tbl); } } cref_list = Qnil; } static void cref_pop() { NODE *cref = cref_list; cref_list = cref_list->nd_next; cref->nd_next = Qnil; } void yyappend_print() { eval_tree = block_append(eval_tree, NEW_CALL(Qnil, rb_intern("print"), NEW_ARRAY(NEW_GVAR(rb_intern("$_"))))); } void yywhole_loop(chop, split) int chop, split; { if (split) { eval_tree = block_append(NEW_GASGN(rb_intern("$F"), NEW_CALL(NEW_GVAR(rb_intern("$_")), rb_intern("split"), Qnil)), eval_tree); } if (chop) { eval_tree = block_append(NEW_CALL(NEW_GVAR(rb_intern("$_")), rb_intern("chop"), Qnil), eval_tree); } eval_tree = NEW_WHILE(NEW_CALL(0,rb_intern("gets"),0),eval_tree); } static struct op_tbl rb_op_tbl[] = { DOT2, "..", '+', "+", '-', "-", '+', "+(binary)", '-', "-(binary)", '*', "*", '/', "/", '%', "%", POW, "**", UPLUS, "+(unary)", UMINUS, "-(unary)", UPLUS, "+@", UMINUS, "-@", '|', "|", '^', "^", '&', "&", CMP, "<=>", '>', ">", GEQ, ">=", '<', "<", LEQ, "<=", EQ, "==", NEQ, "!=", MATCH, "=~", NMATCH, "!~", '!', "!", '~', "~", '!', "!(unary)", '~', "~(unary)", '!', "!@", '~', "~@", AREF, "[]", ASET, "[]=", LSHFT, "<<", RSHFT, ">>", COLON2, "::", Qnil, Qnil, }; char *rb_id2name(); char *rb_class2name(); st_table *rb_symbol_tbl; #define sym_tbl rb_symbol_tbl void Init_sym() { int strcmp(); sym_tbl = st_init_table(strcmp, st_strhash); rb_global_variable(&cref_list); } ID rb_intern(name) char *name; { static ID last_id = LAST_TOKEN; int id; int last; if (st_lookup(sym_tbl, name, &id)) return id; id = ++last_id; id <<= 3; switch (name[0]) { case '$': id |= ID_GLOBAL; break; case '@': id |= ID_INSTANCE; break; /* fall through */ default: if (name[0] != '_' && !isalpha(name[0]) && !ismbchar(name[0])) { /* operator */ int i; id = Qnil; for (i=0; rb_op_tbl[i].token; i++) { if (strcmp(rb_op_tbl[i].name, name) == 0) { id = rb_op_tbl[i].token; break; } } if (id == Qnil) Bug("Unknown operator `%s'", name); break; } last = strlen(name)-1; if (name[last] == '=') { /* attribute asignment */ char *buf = ALLOCA_N(char,last+1); strncpy(buf, name, last); buf[last] = '\0'; id = rb_intern(buf); id &= ~ID_SCOPE_MASK; id |= ID_ATTRSET; } else if (isupper(name[0])) { id |= ID_CONST; } else { id |= ID_LOCAL; } break; } st_add_direct(sym_tbl, strdup(name), id); return id; } static char *find_ok; static id_find(name, id1, id2) char *name; ID id1, id2; { if (id1 == id2) { find_ok = name; return ST_STOP; } return ST_CONTINUE; } char * rb_id2name(id) ID id; { find_ok = Qnil; if (id < LAST_TOKEN) { int i = 0; for (i=0; rb_op_tbl[i].token; i++) { if (rb_op_tbl[i].token == id) return rb_op_tbl[i].name; } } st_foreach(sym_tbl, id_find, id); if (!find_ok && is_attrset_id(id)) { char *res; ID id2; id2 = (id & ~ID_SCOPE_MASK) | ID_LOCAL; res = rb_id2name(id2); if (res) { char *buf = ALLOCA_N(char,strlen(res)+2); strcpy(buf, res); strcat(buf, "="); rb_intern(buf); return rb_id2name(id); } } return find_ok; } static int const_check(id, val, class) ID id; VALUE val; struct RClass *class; { if (is_const_id(id) && rb_const_bound(class, id)) { Warning("constant redefined for %s", rb_class2name(class)); return ST_STOP; } return ST_CONTINUE; } void rb_const_check(class, module) struct RClass *class, *module; { st_foreach(module->iv_tbl, const_check, class); }