/************************************************ 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 ID cur_class = Qnil, cur_mid = Qnil; static int in_module, in_single; 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 push_local(); static void pop_local(); static int local_cnt(); static int local_id(); static ID *local_tbl(); struct global_entry* rb_global_entry(); static void init_top_local(); static void setup_top_local(); %} %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; init_top_local(); } compexpr { eval_tree = block_append(eval_tree, $2); setup_top_local(); } 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 : IDENTIFIER { $$ = NEW_INC($1); } | inc_list comma IDENTIFIER { $$ = block_append($1, NEW_INC($3)); } | error { lex_state = EXPR_BEG; $$ = Qnil; } | inc_list comma error { lex_state = EXPR_BEG; $$ = $1; } fname : IDENTIFIER | 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_MVAR) { $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 compexpr rparen { $$ = $2; } | CLASS IDENTIFIER superclass { if (cur_class || cur_mid || in_single) Error("nested class definition"); cur_class = $2; push_local(); } compexpr END { $$ = NEW_CLASS($2, $5, $3); pop_local(); cur_class = Qnil; } | MODULE IDENTIFIER { if (cur_class != Qnil) Error("nested module definition"); cur_class = $2; in_module = 1; push_local(); } compexpr END { $$ = NEW_MODULE($2, $4); pop_local(); cur_class = Qnil; in_module = 0; } | DEF fname { if (cur_mid || in_single) Error("nested method definition"); cur_mid = $2; push_local(); } f_arglist compexpr END { $$ = NEW_DEFN($2, NEW_RFUNC($4, $5), cur_class?0:1); pop_local(); cur_mid = Qnil; } | DEF singleton '.' fname { value_expr($2); in_single++; push_local(); } f_arglist compexpr END { $$ = NEW_DEFS($2, $4, NEW_RFUNC($6, $7)); pop_local(); 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 | WHEN args then compexpr cases { $$ = NEW_WHEN($2, $4, $5); } 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 | CONSTANT 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; } IDENTIFIER { $$ = $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 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 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; 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); yylval.node = list; return DREGEXP; } else { yylval.val = regexp_new(tok(), toklen()); 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); int last; 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; } /* 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_BEG || lex_state == EXPR_MID) { c = nextc(); pushback(); if (isdigit(c)) { goto start_num; } lex_state = EXPR_BEG; return UPLUS; } else if (lex_state == EXPR_FNAME) { if ((c = nextc()) == '@') { return UPLUS; } pushback(); return '+'; } lex_state = EXPR_BEG; if ((c = nextc()) == '=') { yylval.id = '+'; return OP_ASGN; } pushback(); return '+'; case '-': if (lex_state == EXPR_BEG || lex_state == EXPR_MID) { c = nextc(); pushback(); if (isdigit(c)) { c = '-'; goto start_num; } lex_state = EXPR_BEG; return UMINUS; } else if (lex_state == EXPR_FNAME) { if ((c = nextc()) == '@') { return UMINUS; } pushback(); return '-'; } lex_state = EXPR_BEG; if ((c = nextc()) == '=') { 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 '%': if (lex_state == EXPR_BEG || lex_state == EXPR_MID) { /* class constant */ newtok(); tokadd('%'); c = nextc(); break; } else { 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); tokadd('\0'); goto id_fetch; default: if (is_identchar(c)) break; pushback(); return tok()[0]; } 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(); /* 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; } } id_fetch: { enum lex_state state = lex_state; lex_state = EXPR_END; yylval.id = rb_intern(tok()); switch (tok()[0]) { case '%': return CONSTANT; case '$': return GVAR; case '@': return IVAR; default: if (state == EXPR_FNAME) { if ((c = nextc()) == '=') { yylval.id &= ~ID_SCOPE_MASK; yylval.id |= ID_ATTRSET; } else { pushback(); } } return IDENTIFIER; } } } 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 '@': 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 '@': 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_MVAR(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: if (verbose) { 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 push_local() { struct local_vars *local; local = ALLOC(struct local_vars); local->prev = lvtbl; local->cnt = 0; local->tbl = Qnil; lvtbl = local; } static void pop_local() { 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 init_top_local() { if (lvtbl == Qnil) { push_local(); } 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; } } static void setup_top_local() { 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); } } } 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); } 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; case '%': if (name[1] != '\0') { id |= ID_CONST; 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 { 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; } char * rb_class2name(class) struct RClass *class; { extern st_table *rb_class_tbl; find_ok = Qnil; switch (TYPE(class)) { case T_ICLASS: class = (struct RClass*)RBASIC(class)->class; break; case T_CLASS: case T_MODULE: break; default: Fail("0x%x is not a class/module", class); } while (FL_TEST(class, FL_SINGLE)) { class = (struct RClass*)class->super; } while (TYPE(class) == T_ICLASS) { class = (struct RClass*)class->super; } st_foreach(rb_class_tbl, id_find, class); if (find_ok) { return rb_id2name((ID)find_ok); } Bug("class 0x%x not named", class); } 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); }