/* ** parse.y - mruby parser ** ** See Copyright Notice in mruby.h */ %{ #undef PARSER_DEBUG #ifdef PARSER_DEBUG # define YYDEBUG 1 #endif #define YYSTACK_USE_ALLOCA 1 #include #include #include #include #include #include #include #include #include #include #include #include #include "node.h" #define YYLEX_PARAM p #define mrbc_malloc(s) mrb_basic_alloc_func(NULL,(s)) #define mrbc_realloc(p,s) mrb_basic_alloc_func((p),(s)) #define mrbc_free(p) mrb_basic_alloc_func((p),0) typedef mrb_ast_node node; typedef struct mrb_parser_state parser_state; typedef struct mrb_parser_heredoc_info parser_heredoc_info; static int yyparse(parser_state *p); static int yylex(void *lval, void *lp, parser_state *p); static void yyerror(void *lp, parser_state *p, const char *s); static void yywarning(parser_state *p, const char *s); static void backref_error(parser_state *p, node *n); static void void_expr_error(parser_state *p, node *n); static void tokadd(parser_state *p, int32_t c); static const char* tok(parser_state *p); static int toklen(parser_state *p); /* Forward declarations for variable-sized simple node functions */ /* Forward declarations for variable-sized advanced node functions */ /* Helper function to check node type for both traditional and variable-sized nodes */ static mrb_bool node_type_p(node *n, enum node_type type); #define identchar(c) (ISALNUM(c) || (c) == '_' || !ISASCII(c)) typedef unsigned int stack_type; #define BITSTACK_PUSH(stack, n) ((stack) = ((stack)<<1)|((n)&1)) #define BITSTACK_POP(stack) ((stack) = (stack) >> 1) #define BITSTACK_LEXPOP(stack) ((stack) = ((stack) >> 1) | ((stack) & 1)) #define BITSTACK_SET_P(stack) ((stack)&1) #define COND_PUSH(n) BITSTACK_PUSH(p->cond_stack, (n)) #define COND_POP() BITSTACK_POP(p->cond_stack) #define COND_LEXPOP() BITSTACK_LEXPOP(p->cond_stack) #define COND_P() BITSTACK_SET_P(p->cond_stack) #define CMDARG_PUSH(n) BITSTACK_PUSH(p->cmdarg_stack, (n)) #define CMDARG_POP() BITSTACK_POP(p->cmdarg_stack) #define CMDARG_LEXPOP() BITSTACK_LEXPOP(p->cmdarg_stack) #define CMDARG_P() BITSTACK_SET_P(p->cmdarg_stack) #define SET_LINENO(c,n) (((struct mrb_ast_var_header*)(c))->lineno = (n)) #define NUM_SUFFIX_R (1<<0) #define NUM_SUFFIX_I (1<<1) static inline mrb_sym intern_cstr_gen(parser_state *p, const char *s) { return mrb_intern_cstr(p->mrb, s); } #define intern_cstr(s) intern_cstr_gen(p,(s)) static inline mrb_sym intern_gen(parser_state *p, const char *s, size_t len) { return mrb_intern(p->mrb, s, len); } #define intern(s,len) intern_gen(p,(s),(len)) #define intern_op(op) MRB_OPSYM(op) static mrb_sym intern_numparam_gen(parser_state *p, int num) { char buf[3]; buf[0] = '_'; buf[1] = '0'+num; buf[2] = '\0'; return intern(buf, 2); } #define intern_numparam(n) intern_numparam_gen(p,(n)) static void cons_free_gen(parser_state *p, node *cons) { cons->cdr = p->cells; p->cells = cons; } #define cons_free(c) cons_free_gen(p, (c)) static void* parser_palloc(parser_state *p, size_t size) { void *m = mempool_alloc(p->pool, size); if (!m) { MRB_THROW(p->mrb->jmp); } return m; } #define parser_pfree(ptr) do { if (sizeof(node) <= sizeof(*(ptr))) cons_free((node*)ptr);} while (0) static node* cons_gen(parser_state *p, node *car, node *cdr) { struct mrb_ast_node *c; /* Try to reuse from free list first - only for 16-byte nodes */ if (p->cells) { c = (struct mrb_ast_node*)p->cells; p->cells = p->cells->cdr; } else { c = (struct mrb_ast_node*)parser_palloc(p, sizeof(struct mrb_ast_node)); } c->car = car; c->cdr = cdr; /* Don't initialize location fields for structure nodes - saves CPU */ return (node*)c; } /* Head-only location optimization: separate functions for head vs structure nodes */ #define cons(a,b) cons_gen(p,(a),(b)) /* Structure nodes - no location */ /* Initialize variable node header */ static void init_var_header(struct mrb_ast_var_header *header, parser_state *p, enum node_type type) { header->lineno = p->lineno; header->filename_index = p->current_filename_index; header->node_type = (uint8_t)type; /* Handle file boundary edge case */ if (p->lineno == 0 && p->current_filename_index > 0) { header->filename_index--; } } /* Combined allocate + init header helper */ static inline void* new_node(parser_state *p, size_t size, enum node_type type) { void *n = parser_palloc(p, size); init_var_header((struct mrb_ast_var_header*)n, p, type); return n; } /* Type-safe macro wrapper for node allocation */ #define NEW_NODE(type_name, node_type) \ (struct mrb_ast_##type_name##_node*)new_node(p, sizeof(struct mrb_ast_##type_name##_node), node_type) static node* list1_gen(parser_state *p, node *a) { return cons(a, 0); } #define list1(a) list1_gen(p, (a)) static node* list2_gen(parser_state *p, node *a, node *b) { return cons(a, cons(b, 0)); } #define list2(a,b) list2_gen(p, (a),(b)) static node* list3_gen(parser_state *p, node *a, node *b, node *c) { return cons(a, cons(b, cons(c, 0))); } #define list3(a,b,c) list3_gen(p, (a),(b),(c)) static node* append_gen(parser_state *p, node *a, node *b) { node *c = a; if (!a) return b; if (!b) return a; while (c->cdr) { c = c->cdr; } c->cdr = b; return a; } #define append(a,b) append_gen(p,(a),(b)) #define push(a,b) append_gen(p,(a),list1(b)) static char* parser_strndup(parser_state *p, const char *s, size_t len) { char *b = (char*)parser_palloc(p, len+1); memcpy(b, s, len); b[len] = '\0'; return b; } #undef strndup #define strndup(s,len) parser_strndup(p, s, len) static char* parser_strdup(parser_state *p, const char *s) { return parser_strndup(p, s, strlen(s)); } #undef strdup #define strdup(s) parser_strdup(p, s) static void dump_int(uint16_t i, char *s) { char *p = s; char *t = s; while (i > 0) { *p++ = (i % 10)+'0'; i /= 10; } if (p == s) *p++ = '0'; *p = 0; p--; /* point the last char */ while (t < p) { char c = *t; *t++ = *p; *p-- = c; } } /* xxx ----------------------------- */ static node* local_switch(parser_state *p) { node *prev = p->locals; p->locals = cons(0, 0); return prev; } static void local_resume(parser_state *p, node *prev) { p->locals = prev; } static void local_nest(parser_state *p) { p->locals = cons(0, p->locals); } static void local_unnest(parser_state *p) { if (p->locals) { p->locals = p->locals->cdr; } } static mrb_bool local_var_p(parser_state *p, mrb_sym sym) { const struct RProc *u; node *l = p->locals; while (l) { node *n = l->car; while (n) { if (node_to_sym(n->car) == sym) return TRUE; n = n->cdr; } l = l->cdr; } u = p->upper; while (u && !MRB_PROC_CFUNC_P(u)) { const struct mrb_irep *ir = u->body.irep; const mrb_sym *v = ir->lv; int i; if (v) { for (i=0; i+1 < ir->nlocals; i++) { if (v[i] == sym) return TRUE; } } if (MRB_PROC_SCOPE_P(u)) break; u = u->upper; } return FALSE; } static void local_add_f(parser_state *p, mrb_sym sym) { if (p->locals) { node *n = p->locals->car; while (n) { if (node_to_sym(n->car) == sym) { mrb_int len; const char* name = mrb_sym_name_len(p->mrb, sym, &len); if (len > 0 && name[0] != '_') { yyerror(NULL, p, "duplicated argument name"); return; } } n = n->cdr; } p->locals->car = push(p->locals->car, sym_to_node(sym)); } } static void local_add(parser_state *p, mrb_sym sym) { if (!local_var_p(p, sym)) { local_add_f(p, sym); } } /* allocate register for block */ #define local_add_blk(p) local_add_f(p, 0) static void local_add_kw(parser_state *p, mrb_sym kwd) { /* allocate register for keywords hash */ local_add_f(p, kwd ? kwd : intern_op(pow)); } static node* locals_node(parser_state *p) { return p->locals ? p->locals->car : NULL; } /* Helper function to check node type for both traditional and variable-sized nodes */ static mrb_bool node_type_p(node *n, enum node_type type) { if (!n) return FALSE; /* Check if this is a variable-sized node */ struct mrb_ast_var_header *header = (struct mrb_ast_var_header*)n; return ((enum node_type)header->node_type == type); } /* Helper functions for variable-sized node detection */ static enum node_type node_type(node *n) { if (!n) return (enum node_type)0; /* Try to interpret as variable-sized node */ struct mrb_ast_var_header *header = (struct mrb_ast_var_header*)n; enum node_type type = (enum node_type)header->node_type; /* Validate that the node type is within valid range for variable-sized nodes */ if (type >= NODE_SCOPE && type < NODE_LAST) { return type; } /* If node type is invalid, this is likely a cons-list node */ /* Return a special sentinel value to indicate cons-list fallback */ return NODE_LAST; /* Use NODE_LAST as sentinel for cons-list nodes */ } static void nvars_nest(parser_state *p) { p->nvars = cons(int_to_node(0), p->nvars); } static void nvars_block(parser_state *p) { p->nvars = cons(int_to_node(-2), p->nvars); } static void nvars_unnest(parser_state *p) { p->nvars = p->nvars->cdr; } /* struct: scope_node(locals, body) */ static node* new_scope(parser_state *p, node *body) { struct mrb_ast_scope_node *scope_node = NEW_NODE(scope, NODE_SCOPE); scope_node->locals = locals_node(p); scope_node->body = body; return (node*)scope_node; } /* struct: stmts_node(stmts) - uses cons list */ static node* new_stmts(parser_state *p, node *body) { struct mrb_ast_stmts_node *n = NEW_NODE(stmts, NODE_STMTS); n->stmts = body ? list1(body) : 0; /* Wrap single statement in cons-list */ return (node*)n; } /* Helper: push statement to stmts node */ static node* stmts_push(parser_state *p, node *stmts, node *stmt) { struct mrb_ast_stmts_node *n = stmts_node(stmts); n->stmts = push(n->stmts, stmt); return stmts; } /* struct: begin_node(body) */ static node* new_begin(parser_state *p, node *body) { struct mrb_ast_begin_node *begin_node = NEW_NODE(begin, NODE_BEGIN); begin_node->body = body; return (node*)begin_node; } #define newline_node(n) (n) /* struct: rescue_node(body, rescue_clauses, else_clause) */ static node* new_rescue(parser_state *p, node *body, node *resq, node *els) { struct mrb_ast_rescue_node *n = NEW_NODE(rescue, NODE_RESCUE); n->body = body; n->rescue_clauses = resq; n->else_clause = els; return (node*)n; } static node* new_mod_rescue(parser_state *p, node *body, node *resq) { return new_rescue(p, body, list1(list3(0, 0, resq)), 0); } /* struct: ensure_node(body, ensure_clause) */ static node* new_ensure(parser_state *p, node *a, node *b) { struct mrb_ast_ensure_node *ensure_node = NEW_NODE(ensure, NODE_ENSURE); ensure_node->body = a; ensure_node->ensure_clause = b; return (node*)ensure_node; } /* struct: nil_node() */ static node* new_nil(parser_state *p) { struct mrb_ast_nil_node *n = NEW_NODE(nil, NODE_NIL); return (node*)n; } /* struct: true_node() */ static node* new_true(parser_state *p) { struct mrb_ast_true_node *n = NEW_NODE(true, NODE_TRUE); return (node*)n; } /* struct: false_node() */ static node* new_false(parser_state *p) { struct mrb_ast_false_node *n = NEW_NODE(false, NODE_FALSE); return (node*)n; } /* struct: alias_node(new_name, old_name) */ static node* new_alias(parser_state *p, mrb_sym a, mrb_sym b) { struct mrb_ast_alias_node *alias_node = NEW_NODE(alias, NODE_ALIAS); alias_node->new_name = a; alias_node->old_name = b; return (node*)alias_node; } /* struct: if_node(cond, then_body, else_body) */ static node* new_if(parser_state *p, node *condition, node *then_body, node *else_body) { void_expr_error(p, condition); struct mrb_ast_if_node *n = NEW_NODE(if, NODE_IF); n->condition = condition; n->then_body = then_body; n->else_body = else_body; return (node*)n; } /* struct: while_node(cond, body) */ static node* new_while(parser_state *p, node *condition, node *body) { void_expr_error(p, condition); struct mrb_ast_while_node *n = NEW_NODE(while, NODE_WHILE); n->condition = condition; n->body = body; return (node*)n; } /* struct: until_node(cond, body) */ static node* new_until(parser_state *p, node *condition, node *body) { void_expr_error(p, condition); struct mrb_ast_until_node *n = NEW_NODE(until, NODE_UNTIL); n->condition = condition; n->body = body; return (node*)n; } /* struct: while_node(cond, body) */ static node* new_while_mod(parser_state *p, node *condition, node *body) { node *while_node = new_while(p, condition, body); struct mrb_ast_while_node *n = (struct mrb_ast_while_node*)while_node; n->header.node_type = NODE_WHILE_MOD; return while_node; } /* struct: until_node(cond, body) */ static node* new_until_mod(parser_state *p, node *a, node *b) { node *until_node = new_until(p, a, b); struct mrb_ast_until_node *n = (struct mrb_ast_until_node*)until_node; n->header.node_type = NODE_UNTIL_MOD; return until_node; } /* struct: for_node(var, obj, body) */ static node* new_for(parser_state *p, node *v, node *o, node *b) { void_expr_error(p, o); struct mrb_ast_for_node *n = NEW_NODE(for, NODE_FOR); n->var = v; n->iterable = o; n->body = b; return (node*)n; } /* struct: case_node(expr, when_clauses) - uses cons list */ static node* new_case(parser_state *p, node *a, node *b) { void_expr_error(p, a); struct mrb_ast_case_node *n = NEW_NODE(case, NODE_CASE); n->value = a; n->body = b; return (node*)n; } /* Pattern matching case/in expression */ static node* new_case_match(parser_state *p, node *val, node *in_clauses) { void_expr_error(p, val); struct mrb_ast_case_match_node *n = NEW_NODE(case_match, NODE_CASE_MATCH); n->value = val; n->in_clauses = in_clauses; return (node*)n; } /* Create value pattern node */ static node* new_pat_value(parser_state *p, node *val) { struct mrb_ast_pat_value_node *n = NEW_NODE(pat_value, NODE_PAT_VALUE); n->value = val; return (node*)n; } /* Create variable pattern node */ static node* new_pat_var(parser_state *p, mrb_sym name) { struct mrb_ast_pat_var_node *n = NEW_NODE(pat_var, NODE_PAT_VAR); n->name = name; /* Register as local variable if not wildcard */ if (name) { local_add(p, name); } return (node*)n; } /* Create pin pattern node (^var) */ static node* new_pat_pin(parser_state *p, mrb_sym name) { struct mrb_ast_pat_pin_node *n = NEW_NODE(pat_pin, NODE_PAT_PIN); n->name = name; /* Pin operator references existing variable, does not create new binding */ return (node*)n; } /* Create as pattern node (pattern => var) */ static node* new_pat_as(parser_state *p, node *pattern, mrb_sym name) { struct mrb_ast_pat_as_node *n = NEW_NODE(pat_as, NODE_PAT_AS); n->pattern = pattern; n->name = name; local_add(p, name); return (node*)n; } /* Create alternative pattern node (pat1 | pat2) */ static node* new_pat_alt(parser_state *p, node *left, node *right) { struct mrb_ast_pat_alt_node *n = NEW_NODE(pat_alt, NODE_PAT_ALT); n->left = left; n->right = right; return (node*)n; } /* Create array pattern node [a, b, *rest, c] */ static node* new_pat_array(parser_state *p, node *pre, node *rest, node *post) { struct mrb_ast_pat_array_node *n = NEW_NODE(pat_array, NODE_PAT_ARRAY); n->pre = pre; n->rest = rest; n->post = post; return (node*)n; } /* Create find pattern node [*pre, elems, *post] */ static node* new_pat_find(parser_state *p, node *pre, node *elems, node *post) { struct mrb_ast_pat_find_node *n = NEW_NODE(pat_find, NODE_PAT_FIND); n->pre = pre; n->elems = elems; n->post = post; return (node*)n; } /* Create hash pattern node {a:, b: x, **rest} */ static node* new_pat_hash(parser_state *p, node *pairs, node *rest) { struct mrb_ast_pat_hash_node *n = NEW_NODE(pat_hash, NODE_PAT_HASH); n->pairs = pairs; n->rest = rest; return (node*)n; } /* Create one-line pattern matching node (expr in pattern / expr => pattern) */ static node* new_match_pat(parser_state *p, node *value, node *pattern, mrb_bool raise_on_fail) { struct mrb_ast_match_pat_node *n = NEW_NODE(match_pat, NODE_MATCH_PAT); n->value = value; n->pattern = pattern; n->raise_on_fail = raise_on_fail; return (node*)n; } /* Create in-clause node for case/in */ static node* new_in(parser_state *p, node *pattern, node *guard, node *body, mrb_bool guard_is_unless) { struct mrb_ast_in_node *n = NEW_NODE(in, NODE_IN); n->pattern = pattern; n->guard = guard; n->body = body; n->guard_is_unless = guard_is_unless; return (node*)n; } /* struct: postexe_node(body) */ static node* new_postexe(parser_state *p, node *a) { struct mrb_ast_postexe_node *postexe_node = NEW_NODE(postexe, NODE_POSTEXE); postexe_node->body = a; return (node*)postexe_node; } /* struct: self_node() */ static node* new_self(parser_state *p) { struct mrb_ast_self_node *n = NEW_NODE(self, NODE_SELF); return (node*)n; } /* struct: call_node(receiver, method, args) */ static node* new_call(parser_state *p, node *receiver, mrb_sym method, node *args, int pass) { /* Calculate size needed (fixed size now) */ struct mrb_ast_call_node *n = NEW_NODE(call, NODE_CALL); n->receiver = receiver; n->method_name = method; n->safe_call = (pass == 0); /* pass == 0 means safe call (&.) */ /* Store args pointer directly - no need to unpack and repack */ n->args = args; void_expr_error(p, receiver); return (node*)n; } /* struct: fcall_node(method, args) */ static node* new_fcall(parser_state *p, mrb_sym b, node *c) { return new_call(p, NULL, b, c, '.'); } /* (a b . c) */ static node* new_callargs(parser_state *p, node *a, node *b, node *c) { /* Allocate struct mrb_ast_callargs (fixed size, like new_args) */ struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)parser_palloc(p, sizeof(struct mrb_ast_callargs)); /* Initialize members directly */ callargs->regular_args = a; /* Cons list of regular arguments (preserves splat compatibility) */ callargs->keyword_args = b; /* Keyword arguments hash node */ callargs->block_arg = c; /* Block argument node */ /* Return direct cast to node (like new_args) */ return (node*)callargs; } /* struct: super_node(args) */ static node* new_super(parser_state *p, node *c) { struct mrb_ast_super_node *n = NEW_NODE(super, NODE_SUPER); n->args = c; return (node*)n; } /* struct: zsuper_node() */ static node* new_zsuper(parser_state *p) { struct mrb_ast_super_node *n = NEW_NODE(super, NODE_ZSUPER); n->args = NULL; /* zsuper initially has no args, but may be added by call_with_block */ return (node*)n; } /* struct: yield_node(args) */ static node* new_yield(parser_state *p, node *c) { /* Handle callargs structure - direct casting like new_args() */ if (c) { struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)c; if (callargs->block_arg) { yyerror(NULL, p, "both block arg and actual block given"); } } struct mrb_ast_yield_node *n = NEW_NODE(yield, NODE_YIELD); n->args = c; return (node*)n; } /* struct: return_node(value) */ static node* new_return(parser_state *p, node *c) { struct mrb_ast_return_node *n = NEW_NODE(return, NODE_RETURN); n->args = c; return (node*)n; } /* struct: break_node(value) */ static node* new_break(parser_state *p, node *c) { struct mrb_ast_break_node *n = NEW_NODE(break, NODE_BREAK); n->value = c; return (node*)n; } /* struct: next_node(value) */ static node* new_next(parser_state *p, node *c) { struct mrb_ast_next_node *n = NEW_NODE(next, NODE_NEXT); n->value = c; return (node*)n; } /* struct: redo_node() */ static node* new_redo(parser_state *p) { struct mrb_ast_redo_node *n = NEW_NODE(redo, NODE_REDO); return (node*)n; } /* struct: retry_node() */ static node* new_retry(parser_state *p) { struct mrb_ast_retry_node *n = NEW_NODE(retry, NODE_RETRY); return (node*)n; } /* struct: dot2_node(beg, end) */ static node* new_dot2(parser_state *p, node *a, node *b) { struct mrb_ast_dot2_node *n = NEW_NODE(dot2, NODE_DOT2); n->left = a; n->right = b; return (node*)n; } /* struct: dot3_node(beg, end) */ static node* new_dot3(parser_state *p, node *a, node *b) { struct mrb_ast_dot3_node *n = NEW_NODE(dot3, NODE_DOT3); n->left = a; n->right = b; return (node*)n; } /* struct: colon2_node(base, name) */ static node* new_colon2(parser_state *p, node *b, mrb_sym c) { void_expr_error(p, b); struct mrb_ast_colon2_node *colon2_node = NEW_NODE(colon2, NODE_COLON2); colon2_node->base = b; colon2_node->name = c; return (node*)colon2_node; } /* struct: colon3_node(name) */ static node* new_colon3(parser_state *p, mrb_sym c) { struct mrb_ast_colon3_node *colon3_node = NEW_NODE(colon3, NODE_COLON3); colon3_node->name = c; return (node*)colon3_node; } /* struct: and_node(left, right) */ static node* new_and(parser_state *p, node *a, node *b) { void_expr_error(p, a); struct mrb_ast_and_node *n = NEW_NODE(and, NODE_AND); n->left = a; n->right = b; return (node*)n; } /* struct: or_node(left, right) */ static node* new_or(parser_state *p, node *a, node *b) { void_expr_error(p, a); struct mrb_ast_or_node *n = NEW_NODE(or, NODE_OR); n->left = a; n->right = b; return (node*)n; } /* struct: array_node(elements) - uses cons list */ static node* new_array(parser_state *p, node *a) { struct mrb_ast_array_node *n = NEW_NODE(array, NODE_ARRAY); n->elements = a; return (node*)n; } /* struct: splat_node(value) */ static node* new_splat(parser_state *p, node *a) { void_expr_error(p, a); struct mrb_ast_splat_node *splat_node = NEW_NODE(splat, NODE_SPLAT); splat_node->value = a; return (node*)splat_node; } /* struct: hash_node(pairs) - uses cons list */ static node* new_hash(parser_state *p, node *a) { struct mrb_ast_hash_node *n = NEW_NODE(hash, NODE_HASH); n->pairs = a; return (node*)n; } /* (:sym . a) */ /* Symbol node creation - supports both variable and legacy modes */ static node* new_sym(parser_state *p, mrb_sym sym) { struct mrb_ast_sym_node *n = NEW_NODE(sym, NODE_SYM); n->symbol = sym; return (node*)n; } static node* new_xvar(parser_state *p, mrb_sym sym, enum node_type type) { struct mrb_ast_var_node *n = NEW_NODE(var, type); n->symbol = sym; return (node*)n; } #define new_lvar(p, sym) new_xvar(p, sym, NODE_LVAR) #define new_ivar(p, sym) new_xvar(p, sym, NODE_IVAR) #define new_gvar(p, sym) new_xvar(p, sym, NODE_GVAR) #define new_cvar(p, sym) new_xvar(p, sym, NODE_CVAR) static mrb_sym new_strsym(parser_state *p, node* str) { size_t len = (size_t)str->car; const char *s = (const char*)str->cdr; return mrb_intern(p->mrb, s, len); } /* (:nvar . a) */ static node* new_nvar(parser_state *p, int num) { int nvar; node *nvars = p->nvars->cdr; while (nvars) { nvar = node_to_int(nvars->car); if (nvar == -2) break; /* top of the scope */ if (nvar > 0) { yyerror(NULL, p, "numbered parameter used in outer block"); break; } nvars->car = int_to_node(-1); nvars = nvars->cdr; } nvar = node_to_int(p->nvars->car); if (nvar == -1) { yyerror(NULL, p, "numbered parameter used in inner block"); } else { p->nvars->car = int_to_node(nvar > num ? nvar : num); } struct mrb_ast_nvar_node *n = NEW_NODE(nvar, NODE_NVAR); n->num = num; return (node*)n; } /* struct: const_node(name) */ static node* new_const(parser_state *p, mrb_sym sym) { struct mrb_ast_const_node *n = NEW_NODE(const, NODE_CONST); n->symbol = sym; return (node*)n; } /* struct: undef_node(syms) - uses cons list */ static node* new_undef(parser_state *p, node *syms) { struct mrb_ast_undef_node *undef_node = NEW_NODE(undef, NODE_UNDEF); undef_node->syms = syms; return (node*)undef_node; } /* struct: class_node(path, super, body) */ static node* new_class(parser_state *p, node *c, node *s, node *b) { void_expr_error(p, s); struct mrb_ast_class_node *n = NEW_NODE(class, NODE_CLASS); n->name = c; n->superclass = s; n->body = cons(locals_node(p), b); return (node*)n; } /* struct: sclass_node(obj, body) */ static node* new_sclass(parser_state *p, node *o, node *b) { void_expr_error(p, o); struct mrb_ast_sclass_node *n = NEW_NODE(sclass, NODE_SCLASS); n->obj = o; n->body = cons(locals_node(p), b); return (node*)n; } /* struct: module_node(path, body) */ static node* new_module(parser_state *p, node *m, node *b) { struct mrb_ast_module_node *n = NEW_NODE(module, NODE_MODULE); n->name = m; n->body = cons(locals_node(p), b); return (node*)n; } /* struct: def_node(name, args, body) */ static node* new_def(parser_state *p, mrb_sym name) { struct mrb_ast_def_node *n = NEW_NODE(def, NODE_DEF); n->name = name; n->args = (struct mrb_ast_args *)int_to_node(p->cmdarg_stack); n->locals = local_switch(p); n->body = NULL; return (node*)n; } static void defn_setup(parser_state *p, node *d, node *a, node *b) { struct mrb_ast_def_node *n = def_node(d); node *locals = n->locals; n->locals = locals_node(p); p->cmdarg_stack = node_to_int(n->args); n->args = (struct mrb_ast_args *)a; n->body = b; local_resume(p, locals); } /* struct: sdef_node(obj, name, args, body) */ static node* new_sdef(parser_state *p, node *o, mrb_sym name) { void_expr_error(p, o); struct mrb_ast_sdef_node *sdef_node = NEW_NODE(sdef, NODE_SDEF); sdef_node->obj = o; sdef_node->name = name; sdef_node->args = (struct mrb_ast_args *)int_to_node(p->cmdarg_stack); sdef_node->locals = local_switch(p); sdef_node->body = NULL; return (node*)sdef_node; } static void local_add_margs(parser_state *p, node *n) { while (n) { if (node_type(n->car) == NODE_MARG) { struct mrb_ast_masgn_node *masgn_n = (struct mrb_ast_masgn_node*)n->car; node *rhs = masgn_n->rhs; /* For parameter destructuring, rhs contains the locals */ if (rhs) { node *t = rhs; while (t) { local_add_f(p, node_to_sym(t->car)); t = t->cdr; } /* Clear cons list RHS immediately after use */ masgn_n->rhs = NULL; } /* Process nested destructuring in lhs components */ if (masgn_n->pre) { local_add_margs(p, masgn_n->pre); } if (masgn_n->post) { local_add_margs(p, masgn_n->post); } } n = n->cdr; } } static void local_add_lv(parser_state *p, node *lv) { while (lv) { local_add_f(p, node_to_sym(lv->car)); lv = lv->cdr; } } /* (m o r m2 tail) */ /* m: (a b c) */ /* o: ((a . e1) (b . e2)) */ /* r: a */ /* m2: (a b c) */ /* b: a */ static node* new_args(parser_state *p, node *m, node *opt, mrb_sym rest, node *m2, node *tail) { local_add_margs(p, m); local_add_margs(p, m2); /* Save original optional arguments before processing */ node *orig_opt = opt; /* Process optional arguments (keep original side effects) */ while (opt) { /* opt: (sym . (opt . lv)) -> (sym . opt) */ local_add_lv(p, opt->car->cdr->cdr); opt->car->cdr = opt->car->cdr->car; opt = opt->cdr; } /* Allocate struct mrb_ast_args (no hdr) */ struct mrb_ast_args *args = (struct mrb_ast_args*)parser_palloc(p, sizeof(struct mrb_ast_args)); /* Initialize members */ args->mandatory_args = m; args->optional_args = orig_opt; args->rest_arg = rest; args->post_mandatory_args = m2; /* Deconstruct tail cons list: (kws . (kwrest . blk)) */ if (tail) { args->keyword_args = (node*)tail->car; /* kws */ args->kwrest_arg = (mrb_sym)(intptr_t)tail->cdr->car; /* kwrest */ args->block_arg = (mrb_sym)(intptr_t)tail->cdr->cdr; /* blk */ cons_free(tail->cdr); cons_free(tail); } else { args->keyword_args = NULL; args->kwrest_arg = 0; args->block_arg = 0; } return (node*)args; } /* struct: args_tail_node(kwargs, kwrest, block) */ static node* new_args_tail(parser_state *p, node *kws, mrb_sym kwrest, mrb_sym blk) { node *k; if (kws || kwrest) { local_add_kw(p, kwrest); } local_add_blk(p); if (blk && blk != MRB_SYM(nil)) local_add_f(p, blk); /* allocate register for keywords arguments */ /* order is for Proc#parameters */ for (k = kws; k; k = k->cdr) { if (!k->car->cdr) { /* allocate required keywords - simplified structure: (key . NULL) */ local_add_f(p, node_to_sym(k->car->car)); } } for (k = kws; k; k = k->cdr) { if (k->car->cdr) { /* allocate keywords with default - simplified structure: (key . value) */ local_add_lv(p, k->car->cdr->cdr); /* value->cdr for default args */ k->car->cdr = k->car->cdr->car; /* value->car for default args */ local_add_f(p, node_to_sym(k->car->car)); } } /* Return cons list: (keyword . (kwrest . blk)) */ return cons(kws, cons(sym_to_node(kwrest), sym_to_node(blk))); } /* (kw_sym . def_arg) - simplified from NODE_KW_ARG wrapper */ static node* new_kw_arg(parser_state *p, mrb_sym kw, node *def_arg) { mrb_assert(kw); return cons(sym_to_node(kw), def_arg); } /* (:kw_rest_args . a) */ static node* new_kw_rest_args(parser_state *p, mrb_sym sym) { return sym_to_node(intern_op(pow)); /* Use ** symbol as direct marker */ } static node* new_args_dots(parser_state *p, node *m) { mrb_sym r = intern_op(mul); mrb_sym k = intern_op(pow); mrb_sym b = intern_op(and); local_add_f(p, r); return new_args(p, m, 0, r, 0, new_args_tail(p, NULL, k, b)); } /* struct: block_arg_node(value) */ static node* new_block_arg(parser_state *p, node *a) { struct mrb_ast_block_arg_node *block_arg_node = NEW_NODE(block_arg, NODE_BLOCK_ARG); block_arg_node->value = a; return (node*)block_arg_node; } static node* setup_numparams(parser_state *p, node *a) { int nvars = node_to_int(p->nvars->car); if (nvars > 0) { int i; mrb_sym sym; // Check if any arguments are already defined struct mrb_ast_args *args = (struct mrb_ast_args *)a; if (a && (args->mandatory_args || args->optional_args || args->rest_arg || args->post_mandatory_args || args->keyword_args || args->kwrest_arg)) { yyerror(NULL, p, "ordinary parameter is defined"); } else if (p->locals) { /* p->locals should not be NULL unless error happens before the point */ node* args = 0; for (i = nvars; i > 0; i--) { char buf[3]; buf[0] = '_'; buf[1] = i+'0'; buf[2] = '\0'; sym = intern_cstr(buf); args = cons(new_lvar(p, sym), args); p->locals->car = cons(sym_to_node(sym), p->locals->car); } a = new_args(p, args, 0, 0, 0, 0); } } return a; } /* struct: block_node(args, body) */ static node* new_block(parser_state *p, node *a, node *b) { a = setup_numparams(p, a); struct mrb_ast_block_node *n = NEW_NODE(block, NODE_BLOCK); n->locals = locals_node(p); n->args = (struct mrb_ast_args *)a; n->body = b; return (node*)n; } /* struct: lambda_node(args, body) */ static node* new_lambda(parser_state *p, node *a, node *b) { a = setup_numparams(p, a); struct mrb_ast_lambda_node *lambda_node = NEW_NODE(lambda, NODE_LAMBDA); lambda_node->locals = locals_node(p); lambda_node->args = (struct mrb_ast_args *)a; lambda_node->body = b; return (node*)lambda_node; } /* struct: asgn_node(lhs, rhs) */ static node* new_asgn(parser_state *p, node *a, node *b) { void_expr_error(p, b); struct mrb_ast_asgn_node *n = NEW_NODE(asgn, NODE_ASGN); n->lhs = a; n->rhs = b; return (node*)n; } /* Helper function to create MASGN/MARG nodes */ static node* new_masgn_helper(parser_state *p, node *a, node *b, enum node_type node_type) { struct mrb_ast_masgn_node *n = NEW_NODE(masgn, node_type); /* Extract pre, rest, post from cons list structure (a b c) */ if (a) { n->pre = a->car; /* Pre-splat variables */ if (a->cdr) { n->rest = a->cdr->car; /* Splat variable (or -1 for anonymous) */ if (a->cdr->cdr) { n->post = a->cdr->cdr->car; /* Post-splat variables */ cons_free(a->cdr->cdr); } else { n->post = NULL; } cons_free(a->cdr); } else { n->rest = NULL; n->post = NULL; } cons_free(a); } else { n->pre = NULL; n->rest = NULL; n->post = NULL; } n->rhs = b; return (node*)n; } /* struct: masgn_node(lhs, rhs) */ static node* new_masgn(parser_state *p, node *a, node *b) { void_expr_error(p, b); return new_masgn_helper(p, a, b, NODE_MASGN); } /* (:marg mlhs mrhs) no check - for parameter destructuring */ static node* new_marg(parser_state *p, node *a) { return new_masgn_helper(p, a, p->locals->car, NODE_MARG); } /* struct: op_asgn_node(lhs, op, rhs) */ static node* new_op_asgn(parser_state *p, node *a, mrb_sym op, node *b) { void_expr_error(p, b); struct mrb_ast_op_asgn_node *n = NEW_NODE(op_asgn, NODE_OP_ASGN); n->lhs = a; n->op = op; n->rhs = b; return (node*)n; } static node* new_int_n(parser_state *p, int32_t val) { struct mrb_ast_int_node *n = NEW_NODE(int, NODE_INT); n->value = val; return (node*)n; } static node* new_imaginary(parser_state *p, node *imaginary) { return new_fcall(p, MRB_SYM(Complex), new_callargs(p, list2(new_int_n(p, 0), imaginary), 0, 0)); } static node* new_rational(parser_state *p, node *rational) { return new_fcall(p, MRB_SYM(Rational), new_callargs(p, list1(rational), 0, 0)); } /* Read integer into int32_t with overflow detection */ static mrb_bool read_int32(const char *p, int base, int32_t *result) { const char *e = p + strlen(p); int32_t value = 0; mrb_bool neg = FALSE; if (base < 2 || base > 16) { return FALSE; } if (*p == '+') { p++; } else if (*p == '-') { neg = TRUE; p++; } while (p < e) { int n; char c = *p; /* Skip underscores */ if (c == '_') { p++; continue; } /* Parse digit */ if (c >= '0' && c <= '9') { n = c - '0'; } else if (c >= 'a' && c <= 'f') { n = c - 'a' + 10; } else if (c >= 'A' && c <= 'F') { n = c - 'A' + 10; } else { /* Invalid character */ return FALSE; } if (n >= base) { /* Digit not valid for this base */ return FALSE; } /* Check for multiplication overflow */ if (value > INT32_MAX / base) { return FALSE; } value *= base; /* Check for addition overflow */ if (value > INT32_MAX - n) { /* Special case: -INT32_MIN is valid */ if (neg && value == (INT32_MAX - n + 1) && p + 1 == e) { *result = INT32_MIN; return TRUE; } return FALSE; } value += n; p++; } *result = neg ? -value : value; return TRUE; } static node* new_int(parser_state *p, const char *s, int base, int suffix) { int32_t val; node* result; /* Try to parse as int32_t first */ if (read_int32(s, base, &val)) { result = new_int_n(p, val); } else { /* Big integer - create NODE_BIGINT */ struct mrb_ast_bigint_node *n = NEW_NODE(bigint, NODE_BIGINT); n->string = strdup(s); n->base = base; result = (node*)n; } /* Handle suffix modifiers */ if (suffix & NUM_SUFFIX_R) { result = new_rational(p, result); } if (suffix & NUM_SUFFIX_I) { result = new_imaginary(p, result); } return result; } #ifndef MRB_NO_FLOAT /* struct: float_node(value) */ static node* new_float(parser_state *p, const char *s, int suffix) { struct mrb_ast_float_node *n = NEW_NODE(float, NODE_FLOAT); n->value = strdup(s); node* result = (node*)n; if (suffix & NUM_SUFFIX_R) { result = new_rational(p, result); } if (suffix & NUM_SUFFIX_I) { result = new_imaginary(p, result); } return result; } #endif /* Create string node from cons list */ /* struct: str_node(str) */ static node* new_str(parser_state *p, node *a) { struct mrb_ast_str_node *n = NEW_NODE(str, NODE_STR); n->list = a; return (node*)n; } /* struct: xstr_node(str) */ static node* new_xstr(parser_state *p, node *a) { struct mrb_ast_xstr_node *n = NEW_NODE(xstr, NODE_XSTR); n->list = a; return (node*)n; } /* struct: dsym_node(parts) - uses cons list */ static node* new_dsym(parser_state *p, node *a) { struct mrb_ast_str_node *n = NEW_NODE(str, NODE_DSYM); n->list = a; return (node*)n; } /* struct: regx_node(pattern, flags, encoding) */ static node* new_regx(parser_state *p, node *list, const char *flags, const char *encoding) { struct mrb_ast_regx_node *n = NEW_NODE(regx, NODE_REGX); n->list = list; n->flags = flags; n->encoding = encoding; return (node*)n; } /* struct: back_ref_node(n) */ static node* new_back_ref(parser_state *p, int n) { struct mrb_ast_back_ref_node *backref_node = NEW_NODE(back_ref, NODE_BACK_REF); backref_node->type = n; return (node*)backref_node; } /* struct: nth_ref_node(n) */ static node* new_nth_ref(parser_state *p, int n) { struct mrb_ast_nth_ref_node *nthref_node = NEW_NODE(nth_ref, NODE_NTH_REF); nthref_node->nth = n; return (node*)nthref_node; } /* struct: heredoc_node(str) */ static node* new_heredoc(parser_state *p, struct mrb_parser_heredoc_info **infop) { struct mrb_ast_heredoc_node *n = NEW_NODE(heredoc, NODE_HEREDOC); /* Initialize embedded heredoc info struct */ n->info.allow_indent = FALSE; n->info.remove_indent = FALSE; n->info.line_head = FALSE; n->info.indent = 0; n->info.indented = NULL; n->info.type = str_not_parsing; // Will be set by heredoc processing n->info.term = NULL; // Will be set by heredoc processing n->info.term_len = 0; n->info.doc = NULL; /* Return pointer to embedded info if requested */ *infop = &n->info; return (node*)n; } static void new_bv(parser_state *p, mrb_sym id) { } static node* new_literal_delim(parser_state *p) { return cons((node*)0, (node*)0); } /* Helper for creating string representation cons (length . string_ptr) */ static node* new_str_rep(parser_state *p, const char *str, int len) { return cons(int_to_node(len), (node*)strndup(str, len)); } /* Helper for creating string representation from current token */ static node* new_str_tok(parser_state *p) { return new_str_rep(p, tok(p), toklen(p)); } /* Helper for creating empty string representation */ static node* new_str_empty(parser_state *p) { return new_str_rep(p, "", 0); } /* (:words . a) */ static node* new_words(parser_state *p, node *a) { struct mrb_ast_words_node *words_node = NEW_NODE(words, NODE_WORDS); words_node->args = a; return (node*)words_node; } /* (:symbols . a) */ static node* new_symbols(parser_state *p, node *a) { struct mrb_ast_symbols_node *symbols_node = NEW_NODE(symbols, NODE_SYMBOLS); symbols_node->args = a; return (node*)symbols_node; } /* xxx ----------------------------- */ /* (:call a op) */ static node* call_uni_op(parser_state *p, node *recv, const char *m) { void_expr_error(p, recv); return new_call(p, recv, intern_cstr(m), 0, '.'); } /* (:call a op b) */ static node* call_bin_op(parser_state *p, node *recv, const char *m, node *arg1) { return new_call(p, recv, intern_cstr(m), new_callargs(p, list1(arg1), 0, 0), '.'); } static void args_with_block(parser_state *p, node *a, node *b) { if (b) { /* Handle callargs structure - direct casting like new_args() */ struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)a; if (callargs->block_arg) { yyerror(NULL, p, "both block arg and actual block given"); } callargs->block_arg = b; } } static void endless_method_name(parser_state *p, node *defn) { struct mrb_ast_def_node *def = (struct mrb_ast_def_node*)defn; mrb_sym sym = def->name; mrb_int len; const char *name = mrb_sym_name_len(p->mrb, sym, &len); if (len > 1 && name[len-1] == '=') { for (int i=0; inode_type; switch (var_type) { case NODE_SUPER: case NODE_ZSUPER: /* For variable-sized super/zsuper nodes, update the args field directly */ { struct mrb_ast_super_node *super_n = super_node(a); if (!super_n->args) { super_n->args = new_callargs(p, 0, 0, b); } else { args_with_block(p, super_n->args, b); } } break; case NODE_YIELD: /* Variable-sized yield nodes should generate an error when given a block */ yyerror(NULL, p, "block given to yield"); break; case NODE_RETURN: /* Variable-sized return nodes - recursively call with args */ { struct mrb_ast_return_node *return_n = return_node(a); if (return_n->args != NULL) { call_with_block(p, return_n->args, b); } } break; case NODE_BREAK: /* Variable-sized break nodes - recursively call with value */ { struct mrb_ast_break_node *break_n = (struct mrb_ast_break_node*)a; if (break_n->value != NULL) { call_with_block(p, break_n->value, b); } } break; case NODE_NEXT: /* Variable-sized next nodes - recursively call with value */ { struct mrb_ast_next_node *next_n = (struct mrb_ast_next_node*)a; if (next_n->value != NULL) { call_with_block(p, next_n->value, b); } } break; case NODE_CALL: /* Variable-sized call nodes - add block to existing args */ { struct mrb_ast_call_node *call = call_node(a); if (call->args && callargs_node(call->args)->block_arg) { yyerror(NULL, p, "both block arg and actual block given"); return; } /* Use existing args and add block */ if (call->args) { /* Modify existing callargs structure to add block */ args_with_block(p, call->args, b); } else { /* Create new callargs with just the block */ call->args = new_callargs(p, NULL, NULL, b); } } break; default: /* For other variable-sized nodes, do nothing */ break; } } static node* new_negate(parser_state *p, node *n) { struct mrb_ast_negate_node *negate_node = NEW_NODE(negate, NODE_NEGATE); negate_node->operand = n; return (node*)negate_node; } static node* cond(node *n) { return n; } static node* ret_args(parser_state *p, node *n) { /* Handle callargs structure - direct casting like new_args() */ struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)n; if (callargs->block_arg) { yyerror(NULL, p, "block argument should not be given"); return NULL; } if (!callargs->regular_args) return NULL; if (!callargs->regular_args->cdr) return callargs->regular_args->car; return new_array(p, callargs->regular_args); } static void assignable(parser_state *p, node *lhs) { switch (node_type(lhs)) { case NODE_LVAR: local_add(p, var_node(lhs)->symbol); break; case NODE_CONST: if (p->in_def) yyerror(NULL, p, "dynamic constant assignment"); break; default: /* Other node types don't need special handling in assignable */ break; } } static node* var_reference(parser_state *p, node *lhs) { /* Check if this is a variable-sized node */ if (node_type_p(lhs, NODE_LVAR)) { mrb_sym sym = var_node(lhs)->symbol; if (!local_var_p(p, sym)) { node *n = new_fcall(p, sym, 0); /* Don't free variable-sized nodes - they're managed by the parser allocator */ return n; } } return lhs; } static node* label_reference(parser_state *p, mrb_sym sym) { const char *name = mrb_sym_name(p->mrb, sym); if (local_var_p(p, sym)) { return new_lvar(p, sym); } else if (ISUPPER(name[0])) { return new_const(p, sym); } else { return new_fcall(p, sym, 0); } } typedef enum mrb_string_type string_type; typedef struct parser_lex_strterm { int type; int level; int term; int paren; struct parser_lex_strterm *prev; } parser_lex_strterm; static parser_lex_strterm* new_strterm(parser_state *p, string_type type, int term, int paren) { parser_lex_strterm *lex = (parser_lex_strterm*)parser_palloc(p, sizeof(parser_lex_strterm)); lex->type = type; lex->level = 0; lex->term = term; lex->paren = paren; lex->prev = p->lex_strterm; return lex; } static void end_strterm(parser_state *p) { parser_lex_strterm *term = p->lex_strterm->prev; parser_pfree(p->lex_strterm); p->lex_strterm = term; } static node* push_strterm(parser_state *p) { node *n = cons((node*)p->lex_strterm, p->parsing_heredoc); p->lex_strterm = NULL; return n; } static void pop_strterm(parser_state *p, node *n) { p->lex_strterm = (parser_lex_strterm*)n->car; p->parsing_heredoc = n->cdr; cons_free(n); } static parser_heredoc_info * parsing_heredoc_info(parser_state *p) { node *nd = p->parsing_heredoc; if (nd == NULL) return NULL; /* mrb_assert(nd->car->car == NODE_HEREDOC); */ if (node_type(nd->car) == NODE_HEREDOC) { /* Variable-sized heredoc node - return address of embedded info struct */ struct mrb_ast_heredoc_node *heredoc = (struct mrb_ast_heredoc_node*)nd->car; return &heredoc->info; } return (parser_heredoc_info*)nd->car->cdr; } static void heredoc_treat_nextline(parser_state *p) { if (p->heredocs_from_nextline == NULL) return; if (p->parsing_heredoc && p->lex_strterm) { append(p->heredocs_from_nextline, p->parsing_heredoc); } p->parsing_heredoc = p->heredocs_from_nextline; p->lex_strterm = new_strterm(p, parsing_heredoc_info(p)->type, 0, 0); p->heredocs_from_nextline = NULL; } static void heredoc_end(parser_state *p) { p->parsing_heredoc = p->parsing_heredoc->cdr; if (p->parsing_heredoc == NULL) { p->lstate = EXPR_BEG; end_strterm(p); } else { /* next heredoc */ p->lex_strterm->type = parsing_heredoc_info(p)->type; } } #define is_strterm_type(p,str_func) ((p)->lex_strterm->type & (str_func)) static void prohibit_literals(parser_state *p, node *n) { if (n == 0) { yyerror(NULL, p, "can't define singleton method for ()."); } else { enum node_type nt = node_type(n); switch (nt) { case NODE_INT: case NODE_STR: case NODE_XSTR: case NODE_REGX: case NODE_FLOAT: case NODE_ARRAY: case NODE_HEREDOC: yyerror(NULL, p, "can't define singleton method for literals"); default: break; } } } /* xxx ----------------------------- */ %} %define parse.error verbose %define api.pure %parse-param {parser_state *p} %lex-param {parser_state *p} %union { node *nd; mrb_sym id; int num; stack_type stack; const struct vtable *vars; } %token keyword_class "'class'" keyword_module "'module'" keyword_def "'def'" keyword_begin "'begin'" keyword_if "'if'" keyword_unless "'unless'" keyword_while "'while'" keyword_until "'until'" keyword_for "'for'" %token keyword_undef "'undef'" keyword_rescue "'rescue'" keyword_ensure "'ensure'" keyword_end "'end'" keyword_then "'then'" keyword_elsif "'elsif'" keyword_else "'else'" keyword_case "'case'" keyword_when "'when'" keyword_break "'break'" keyword_next "'next'" keyword_redo "'redo'" keyword_retry "'retry'" keyword_in "'in'" keyword_do "'do'" keyword_do_cond "'do' for condition" keyword_do_block "'do' for block" keyword_do_LAMBDA "'do' for lambda" keyword_return "'return'" keyword_yield "'yield'" keyword_super "'super'" keyword_self "'self'" keyword_nil "'nil'" keyword_true "'true'" keyword_false "'false'" keyword_and "'and'" keyword_or "'or'" keyword_not "'not'" modifier_if "'if' modifier" modifier_unless "'unless' modifier" modifier_while "'while' modifier" modifier_until "'until' modifier" modifier_rescue "'rescue' modifier" keyword_alias "'alias'" keyword_BEGIN "'BEGIN'" keyword_END "'END'" keyword__LINE__ "'__LINE__'" keyword__FILE__ "'__FILE__'" keyword__ENCODING__ "'__ENCODING__'" %token tIDENTIFIER "local variable or method" %token tFID "method" %token tGVAR "global variable" %token tIVAR "instance variable" %token tCONSTANT "constant" %token tCVAR "class variable" %token tLABEL_TAG "label" %token tINTEGER "integer literal" %token tFLOAT "float literal" %token tCHAR "character literal" %token tXSTRING tREGEXP %token tSTRING tSTRING_PART tSTRING_MID %token tNTH_REF tBACK_REF %token tREGEXP_END %token tNUMPARAM "numbered parameter" %type singleton string string_fragment string_rep string_interp xstring regexp %type literal numeric cpath symbol defn_head defs_head %type top_compstmt top_stmts top_stmt %type bodystmt compstmt stmts stmt expr arg primary command command_call method_call %type expr_value arg_rhs primary_value %type if_tail opt_else case_body cases opt_rescue exc_list exc_var opt_ensure %type args call_args opt_call_args %type paren_args opt_paren_args variable %type command_args aref_args opt_block_arg block_arg var_ref var_lhs %type command_asgn command_rhs mrhs superclass block_call block_command %type f_block_optarg f_block_opt %type f_opt_arglist_paren f_arglist_paren f_arglist %type f_args f_arg f_arg_item f_optarg f_margs %type assoc_list assocs assoc undef_list backref for_var %type block_param opt_block_param block_param_def f_opt %type bv_decls opt_bv_decl bvar f_larglist lambda_body %type brace_block cmd_brace_block do_block lhs none f_bad_arg %type mlhs mlhs_list mlhs_post mlhs_basic mlhs_item mlhs_node mlhs_inner %type fsym sym basic_symbol operation operation2 operation3 %type cname fname op f_rest_arg f_block_arg opt_f_block_arg f_norm_arg f_opt_asgn %type heredoc words symbols %type call_op call_op2 /* 0:'&.', 1:'.', 2:'::' */ %type args_tail opt_args_tail f_kwarg f_kw %type f_block_kwarg f_block_kw block_args_tail opt_block_args_tail %type f_label f_kwrest /* pattern matching */ %type in_clauses p_expr p_alt p_value p_var p_as p_array p_array_body p_array_elems p_rest p_hash p_hash_body p_hash_elems p_hash_elem p_kwrest p_args_head p_args_post p_const %token tUPLUS "unary plus" %token tUMINUS "unary minus" %token tCMP "<=>" %token tEQ "==" %token tEQQ "===" %token tNEQ "!=" %token tGEQ ">=" %token tLEQ "<=" %token tANDOP "&&" %token tOROP "||" %token tMATCH "=~" %token tNMATCH "!~" %token tDOT2 ".." %token tDOT3 "..." %token tBDOT2 tBDOT3 /* (.. and (... */ %token tAREF tASET /* [] and []= */ %token tLSHFT "<<" %token tRSHFT ">>" %token tCOLON2 "::" %token tCOLON3 /* :: at EXPR_BEG */ %token tOP_ASGN /* +=, -= etc. */ %token tASSOC "=>" %token tLPAREN tLPAREN_ARG "(" %token tRPAREN ")" %token tLBRACK "[" %token tLBRACE tLBRACE_ARG "{" %token tSTAR "*" %token tPOW tDSTAR "**" %token tAMPER "&" %token tLAMBDA "->" %token tANDDOT "&." %token tSYMBEG "symbol" %token tSTRING_BEG "string literal" %token tXSTRING_BEG tSTRING_DVAR tREGEXP_BEG tWORDS_BEG tSYMBOLS_BEG tLAMBEG %token tHEREDOC_BEG "here document" %token tHEREDOC_END tLITERAL_DELIM tHD_LITERAL_DELIM %token tHD_STRING_PART tHD_STRING_MID /* * precedence table */ %nonassoc tLOWEST %nonassoc tLBRACE_ARG %nonassoc modifier_if modifier_unless modifier_while modifier_until keyword_in %left keyword_or keyword_and %right keyword_not %right '=' tOP_ASGN %left modifier_rescue %right '?' ':' tLABEL_TAG %nonassoc tDOT2 tDOT3 tBDOT2 tBDOT3 %left tOROP %left tANDOP %nonassoc tCMP tEQ tEQQ tNEQ tMATCH tNMATCH %left '>' tGEQ '<' tLEQ %left '|' '^' %left '&' %left tLSHFT tRSHFT %left '+' '-' %left '*' '/' '%' %right tUMINUS_NUM tUMINUS %right tPOW %right '!' '~' tUPLUS %token tLAST_TOKEN %% program : { p->lstate = EXPR_BEG; if (!p->locals) p->locals = cons(0,0); } top_compstmt { p->tree = new_scope(p, $2); } ; top_compstmt : top_stmts opt_terms { $$ = $1; } ; top_stmts : none { $$ = new_stmts(p, 0); } | top_stmt { $$ = new_stmts(p, $1); } | top_stmts terms top_stmt { $$ = stmts_push(p, $1, newline_node($3)); } | error top_stmt { $$ = new_stmts(p, 0); } ; top_stmt : stmt | keyword_BEGIN { $$ = local_switch(p); nvars_block(p); } '{' top_compstmt '}' { yyerror(&@1, p, "BEGIN not supported"); local_resume(p, $2); nvars_unnest(p); $$ = 0; } ; bodystmt : compstmt opt_rescue opt_else opt_ensure { if ($2) { $$ = new_rescue(p, $1, $2, $3); } else if ($3) { yywarning(p, "else without rescue is useless"); $$ = stmts_push(p, $1, $3); } else { $$ = $1; } if ($4) { if ($$) { $$ = new_ensure(p, $$, $4); } else { $$ = push($4, new_nil(p)); } } } ; compstmt : stmts opt_terms { $$ = $1; } ; stmts : none { $$ = new_stmts(p, 0); } | stmt { $$ = new_stmts(p, $1); } | stmts terms stmt { $$ = stmts_push(p, $1, newline_node($3)); } | error stmt { $$ = new_stmts(p, $2); } ; stmt : keyword_alias fsym {p->lstate = EXPR_FNAME;} fsym { $$ = new_alias(p, $2, $4); } | keyword_undef undef_list { $$ = new_undef(p, $2); } | stmt modifier_if expr_value { $$ = new_if(p, cond($3), $1, 0); } | stmt modifier_unless expr_value { $$ = new_if(p, cond($3), 0, $1); } | stmt modifier_while expr_value { if ($1 && node_type_p($1, NODE_BEGIN)) { $$ = new_while_mod(p, cond($3), $1); } else { $$ = new_while(p, cond($3), $1); } } | stmt modifier_until expr_value { if ($1 && node_type_p($1, NODE_BEGIN)) { $$ = new_until_mod(p, cond($3), $1); } else { $$ = new_until(p, cond($3), $1); } } | stmt modifier_rescue stmt { $$ = new_mod_rescue(p, $1, $3); } | keyword_END '{' compstmt '}' { yyerror(&@1, p, "END not supported"); $$ = new_postexe(p, $3); } | command_asgn | mlhs '=' command_call { $$ = new_masgn(p, $1, $3); } | lhs '=' mrhs { $$ = new_asgn(p, $1, new_array(p, $3)); } | mlhs '=' arg { $$ = new_masgn(p, $1, $3); } | mlhs '=' mrhs { $$ = new_masgn(p, $1, new_array(p, $3)); } | expr ; command_asgn : lhs '=' command_rhs { $$ = new_asgn(p, $1, $3); } | var_lhs tOP_ASGN command_rhs { $$ = new_op_asgn(p, $1, $2, $3); } | primary_value '[' opt_call_args ']' tOP_ASGN command_rhs { $$ = new_op_asgn(p, new_call(p, $1, intern_op(aref), $3, '.'), $5, $6); } | primary_value call_op tIDENTIFIER tOP_ASGN command_rhs { $$ = new_op_asgn(p, new_call(p, $1, $3, 0, $2), $4, $5); } | primary_value call_op tCONSTANT tOP_ASGN command_rhs { $$ = new_op_asgn(p, new_call(p, $1, $3, 0, $2), $4, $5); } | primary_value tCOLON2 tCONSTANT tOP_ASGN command_call { yyerror(&@1, p, "constant re-assignment"); $$ = 0; } | primary_value tCOLON2 tIDENTIFIER tOP_ASGN command_rhs { $$ = new_op_asgn(p, new_call(p, $1, $3, 0, tCOLON2), $4, $5); } | defn_head f_opt_arglist_paren '=' command { $$ = $1; endless_method_name(p, $1); void_expr_error(p, $4); defn_setup(p, $$, $2, $4); nvars_unnest(p); p->in_def--; } | defn_head f_opt_arglist_paren '=' command modifier_rescue arg { $$ = $1; endless_method_name(p, $1); void_expr_error(p, $4); defn_setup(p, $$, $2, new_mod_rescue(p, $4, $6)); nvars_unnest(p); p->in_def--; } | defs_head f_opt_arglist_paren '=' command { $$ = $1; void_expr_error(p, $4); defn_setup(p, $$, $2, $4); nvars_unnest(p); p->in_def--; p->in_single--; } | defs_head f_opt_arglist_paren '=' command modifier_rescue arg { $$ = $1; void_expr_error(p, $4); defn_setup(p, $$, $2, new_mod_rescue(p, $4, $6)); nvars_unnest(p); p->in_def--; p->in_single--; } | backref tOP_ASGN command_rhs { backref_error(p, $1); $$ = new_stmts(p, 0); } ; command_rhs : command_call %prec tOP_ASGN | command_call modifier_rescue stmt { $$ = new_mod_rescue(p, $1, $3); } | command_asgn ; expr : command_call | expr keyword_and expr { $$ = new_and(p, $1, $3); } | expr keyword_or expr { $$ = new_or(p, $1, $3); } | keyword_not opt_nl expr { $$ = call_uni_op(p, cond($3), "!"); } | '!' command_call { $$ = call_uni_op(p, cond($2), "!"); } | arg tASSOC {p->in_kwarg++;} p_expr { /* expr => pattern (raises NoMatchingPatternError on failure) */ p->in_kwarg--; $$ = new_match_pat(p, $1, $4, TRUE); } | arg keyword_in {p->in_kwarg++;} p_expr { /* expr in pattern (returns true/false) */ p->in_kwarg--; $$ = new_match_pat(p, $1, $4, FALSE); } | arg %prec tLOWEST ; defn_head : keyword_def fname { $$ = new_def(p, $2); p->cmdarg_stack = 0; p->in_def++; nvars_block(p); } ; defs_head : keyword_def singleton dot_or_colon { p->lstate = EXPR_FNAME; } fname { $$ = new_sdef(p, $2, $5); p->cmdarg_stack = 0; p->in_def++; p->in_single++; nvars_block(p); p->lstate = EXPR_ENDFN; /* force for args */ } ; expr_value : expr { if (!$1) $$ = new_nil(p); else { $$ = $1; } } ; command_call : command | block_command ; block_command : block_call | block_call call_op2 operation2 command_args { $$ = new_call(p, $1, $3, $4, $2); } ; cmd_brace_block : tLBRACE_ARG { local_nest(p); nvars_nest(p); } opt_block_param compstmt '}' { $$ = new_block(p, $3, $4); local_unnest(p); nvars_unnest(p); } ; command : operation command_args %prec tLOWEST { $$ = new_fcall(p, $1, $2); } | operation command_args cmd_brace_block { args_with_block(p, $2, $3); $$ = new_fcall(p, $1, $2); } | primary_value call_op operation2 command_args %prec tLOWEST { $$ = new_call(p, $1, $3, $4, $2); } | primary_value call_op operation2 command_args cmd_brace_block { args_with_block(p, $4, $5); $$ = new_call(p, $1, $3, $4, $2); } | primary_value tCOLON2 operation2 command_args %prec tLOWEST { $$ = new_call(p, $1, $3, $4, tCOLON2); } | primary_value tCOLON2 operation2 command_args cmd_brace_block { args_with_block(p, $4, $5); $$ = new_call(p, $1, $3, $4, tCOLON2); } | keyword_super command_args { $$ = new_super(p, $2); } | keyword_yield command_args { $$ = new_yield(p, $2); } | keyword_return call_args { $$ = new_return(p, ret_args(p, $2)); } | keyword_break call_args { $$ = new_break(p, ret_args(p, $2)); } | keyword_next call_args { $$ = new_next(p, ret_args(p, $2)); } ; mlhs : mlhs_basic { $$ = $1; } | tLPAREN mlhs_inner rparen { $$ = $2; } ; mlhs_inner : mlhs_basic | tLPAREN mlhs_inner rparen { $$ = $2; } ; mlhs_basic : mlhs_list { $$ = list1($1); } | mlhs_list mlhs_item { $$ = list1(push($1,$2)); } | mlhs_list tSTAR mlhs_node { $$ = list2($1, $3); } | mlhs_list tSTAR mlhs_node ',' mlhs_post { $$ = list3($1, $3, $5); } | mlhs_list tSTAR { $$ = list2($1, new_nil(p)); } | mlhs_list tSTAR ',' mlhs_post { $$ = list3($1, new_nil(p), $4); } | tSTAR mlhs_node { $$ = list2(0, $2); } | tSTAR mlhs_node ',' mlhs_post { $$ = list3(0, $2, $4); } | tSTAR { $$ = list2(0, new_nil(p)); } | tSTAR ',' mlhs_post { $$ = list3(0, new_nil(p), $3); } ; mlhs_item : mlhs_node | tLPAREN mlhs_inner rparen { $$ = new_masgn(p, $2, NULL); } ; mlhs_list : mlhs_item ',' { $$ = list1($1); } | mlhs_list mlhs_item ',' { $$ = push($1, $2); } ; mlhs_post : mlhs_item { $$ = list1($1); } | mlhs_list mlhs_item { $$ = push($1, $2); } ; mlhs_node : variable { assignable(p, $1); } | primary_value '[' opt_call_args ']' { $$ = new_call(p, $1, intern_op(aref), $3, '.'); } | primary_value call_op tIDENTIFIER { $$ = new_call(p, $1, $3, 0, $2); } | primary_value tCOLON2 tIDENTIFIER { $$ = new_call(p, $1, $3, 0, tCOLON2); } | primary_value call_op tCONSTANT { $$ = new_call(p, $1, $3, 0, $2); } | primary_value tCOLON2 tCONSTANT { if (p->in_def || p->in_single) yyerror(&@1, p, "dynamic constant assignment"); $$ = new_colon2(p, $1, $3); } | tCOLON3 tCONSTANT { if (p->in_def || p->in_single) yyerror(&@1, p, "dynamic constant assignment"); $$ = new_colon3(p, $2); } | backref { backref_error(p, $1); $$ = 0; } ; lhs : variable { assignable(p, $1); } | primary_value '[' opt_call_args ']' { $$ = new_call(p, $1, intern_op(aref), $3, '.'); } | primary_value call_op tIDENTIFIER { $$ = new_call(p, $1, $3, 0, $2); } | primary_value tCOLON2 tIDENTIFIER { $$ = new_call(p, $1, $3, 0, tCOLON2); } | primary_value call_op tCONSTANT { $$ = new_call(p, $1, $3, 0, $2); } | primary_value tCOLON2 tCONSTANT { if (p->in_def || p->in_single) yyerror(&@1, p, "dynamic constant assignment"); $$ = new_colon2(p, $1, $3); } | tCOLON3 tCONSTANT { if (p->in_def || p->in_single) yyerror(&@1, p, "dynamic constant assignment"); $$ = new_colon3(p, $2); } | backref { backref_error(p, $1); $$ = 0; } | tNUMPARAM { yyerror(&@1, p, "can't assign to numbered parameter"); } ; cname : tIDENTIFIER { yyerror(&@1, p, "class/module name must be CONSTANT"); } | tCONSTANT ; cpath : tCOLON3 cname { $$ = cons(int_to_node(1), sym_to_node($2)); } | cname { $$ = cons(int_to_node(0), sym_to_node($1)); } | primary_value tCOLON2 cname { void_expr_error(p, $1); $$ = cons($1, sym_to_node($3)); } ; fname : tIDENTIFIER | tCONSTANT | tFID | op { p->lstate = EXPR_ENDFN; $$ = $1; } | reswords { p->lstate = EXPR_ENDFN; $$ = $1; } ; fsym : fname | basic_symbol ; undef_list : fsym { $$ = cons(sym_to_node($1), 0); } | undef_list ',' {p->lstate = EXPR_FNAME;} fsym { $$ = push($1, sym_to_node($4)); } ; op : '|' { $$ = intern_op(or); } | '^' { $$ = intern_op(xor); } | '&' { $$ = intern_op(and); } | tCMP { $$ = intern_op(cmp); } | tEQ { $$ = intern_op(eq); } | tEQQ { $$ = intern_op(eqq); } | tMATCH { $$ = intern_op(match); } | tNMATCH { $$ = intern_op(nmatch); } | '>' { $$ = intern_op(gt); } | tGEQ { $$ = intern_op(ge); } | '<' { $$ = intern_op(lt); } | tLEQ { $$ = intern_op(le); } | tNEQ { $$ = intern_op(neq); } | tLSHFT { $$ = intern_op(lshift); } | tRSHFT { $$ = intern_op(rshift); } | '+' { $$ = intern_op(add); } | '-' { $$ = intern_op(sub); } | '*' { $$ = intern_op(mul); } | tSTAR { $$ = intern_op(mul); } | '/' { $$ = intern_op(div); } | '%' { $$ = intern_op(mod); } | tPOW { $$ = intern_op(pow); } | tDSTAR { $$ = intern_op(pow); } | '!' { $$ = intern_op(not); } | '~' { $$ = intern_op(neg); } | tUPLUS { $$ = intern_op(plus); } | tUMINUS { $$ = intern_op(minus); } | tAREF { $$ = intern_op(aref); } | tASET { $$ = intern_op(aset); } | '`' { $$ = intern_op(tick); } ; reswords : keyword__LINE__ | keyword__FILE__ | keyword__ENCODING__ | keyword_BEGIN | keyword_END | keyword_alias | keyword_and | keyword_begin | keyword_break | keyword_case | keyword_class | keyword_def | keyword_do | keyword_else | keyword_elsif | keyword_end | keyword_ensure | keyword_false | keyword_for | keyword_in | keyword_module | keyword_next | keyword_nil | keyword_not | keyword_or | keyword_redo | keyword_rescue | keyword_retry | keyword_return | keyword_self | keyword_super | keyword_then | keyword_true | keyword_undef | keyword_when | keyword_yield | keyword_if | keyword_unless | keyword_while | keyword_until ; arg : lhs '=' arg_rhs { $$ = new_asgn(p, $1, $3); } | var_lhs tOP_ASGN arg_rhs { $$ = new_op_asgn(p, $1, $2, $3); } | primary_value '[' opt_call_args ']' tOP_ASGN arg_rhs { $$ = new_op_asgn(p, new_call(p, $1, intern_op(aref), $3, '.'), $5, $6); } | primary_value call_op tIDENTIFIER tOP_ASGN arg_rhs { $$ = new_op_asgn(p, new_call(p, $1, $3, 0, $2), $4, $5); } | primary_value call_op tCONSTANT tOP_ASGN arg_rhs { $$ = new_op_asgn(p, new_call(p, $1, $3, 0, $2), $4, $5); } | primary_value tCOLON2 tIDENTIFIER tOP_ASGN arg_rhs { $$ = new_op_asgn(p, new_call(p, $1, $3, 0, tCOLON2), $4, $5); } | primary_value tCOLON2 tCONSTANT tOP_ASGN arg_rhs { yyerror(&@1, p, "constant re-assignment"); $$ = new_stmts(p, 0); } | tCOLON3 tCONSTANT tOP_ASGN arg_rhs { yyerror(&@1, p, "constant re-assignment"); $$ = new_stmts(p, 0); } | backref tOP_ASGN arg_rhs { backref_error(p, $1); $$ = new_stmts(p, 0); } | arg tDOT2 arg { $$ = new_dot2(p, $1, $3); } | arg tDOT2 { $$ = new_dot2(p, $1, new_nil(p)); } | tBDOT2 arg { $$ = new_dot2(p, new_nil(p), $2); } | arg tDOT3 arg { $$ = new_dot3(p, $1, $3); } | arg tDOT3 { $$ = new_dot3(p, $1, new_nil(p)); } | tBDOT3 arg { $$ = new_dot3(p, new_nil(p), $2); } | arg '+' arg { $$ = call_bin_op(p, $1, "+", $3); } | arg '-' arg { $$ = call_bin_op(p, $1, "-", $3); } | arg '*' arg { $$ = call_bin_op(p, $1, "*", $3); } | arg '/' arg { $$ = call_bin_op(p, $1, "/", $3); } | arg '%' arg { $$ = call_bin_op(p, $1, "%", $3); } | arg tPOW arg { $$ = call_bin_op(p, $1, "**", $3); } | tUMINUS_NUM tINTEGER tPOW arg { $$ = new_negate(p, call_bin_op(p, $2, "**", $4)); } | tUMINUS_NUM tFLOAT tPOW arg { $$ = new_negate(p, call_bin_op(p, $2, "**", $4)); } | tUPLUS arg { $$ = call_uni_op(p, $2, "+@"); } | tUMINUS arg { $$ = new_negate(p, $2); } | arg '|' arg { $$ = call_bin_op(p, $1, "|", $3); } | arg '^' arg { $$ = call_bin_op(p, $1, "^", $3); } | arg '&' arg { $$ = call_bin_op(p, $1, "&", $3); } | arg tCMP arg { $$ = call_bin_op(p, $1, "<=>", $3); } | arg '>' arg { $$ = call_bin_op(p, $1, ">", $3); } | arg tGEQ arg { $$ = call_bin_op(p, $1, ">=", $3); } | arg '<' arg { $$ = call_bin_op(p, $1, "<", $3); } | arg tLEQ arg { $$ = call_bin_op(p, $1, "<=", $3); } | arg tEQ arg { $$ = call_bin_op(p, $1, "==", $3); } | arg tEQQ arg { $$ = call_bin_op(p, $1, "===", $3); } | arg tNEQ arg { $$ = call_bin_op(p, $1, "!=", $3); } | arg tMATCH arg { $$ = call_bin_op(p, $1, "=~", $3); } | arg tNMATCH arg { $$ = call_bin_op(p, $1, "!~", $3); } | '!' arg { $$ = call_uni_op(p, cond($2), "!"); } | '~' arg { $$ = call_uni_op(p, cond($2), "~"); } | arg tLSHFT arg { $$ = call_bin_op(p, $1, "<<", $3); } | arg tRSHFT arg { $$ = call_bin_op(p, $1, ">>", $3); } | arg tANDOP arg { $$ = new_and(p, $1, $3); } | arg tOROP arg { $$ = new_or(p, $1, $3); } | arg '?' arg opt_nl ':' arg { $$ = new_if(p, cond($1), $3, $6); } | arg '?' arg opt_nl tLABEL_TAG arg { $$ = new_if(p, cond($1), $3, $6); } | defn_head f_opt_arglist_paren '=' arg { $$ = $1; endless_method_name(p, $1); void_expr_error(p, $4); defn_setup(p, $$, $2, $4); nvars_unnest(p); p->in_def--; } | defn_head f_opt_arglist_paren '=' arg modifier_rescue arg { $$ = $1; endless_method_name(p, $1); void_expr_error(p, $4); defn_setup(p, $$, $2, new_mod_rescue(p, $4, $6)); nvars_unnest(p); p->in_def--; } | defs_head f_opt_arglist_paren '=' arg { $$ = $1; void_expr_error(p, $4); defn_setup(p, $$, $2, $4); nvars_unnest(p); p->in_def--; p->in_single--; } | defs_head f_opt_arglist_paren '=' arg modifier_rescue arg { $$ = $1; void_expr_error(p, $4); defn_setup(p, $$, $2, new_mod_rescue(p, $4, $6)); nvars_unnest(p); p->in_def--; p->in_single--; } | primary { $$ = $1; } ; aref_args : none | args trailer { $$ = $1; } | args comma assocs trailer { $$ = push($1, new_hash(p, $3)); } | assocs trailer { $$ = cons(new_hash(p, $1), 0); } ; arg_rhs : arg %prec tOP_ASGN { $$ = $1; } | arg modifier_rescue arg { void_expr_error(p, $1); $$ = new_mod_rescue(p, $1, $3); } ; paren_args : '(' opt_call_args ')' { $$ = $2; } | '(' args comma tBDOT3 rparen { mrb_sym r = intern_op(mul); mrb_sym k = intern_op(pow); mrb_sym b = intern_op(and); $$ = new_callargs(p, push($2, new_splat(p, new_lvar(p, r))), list1(cons(new_kw_rest_args(p, 0), new_lvar(p, k))), new_block_arg(p, new_lvar(p, b))); } | '(' tBDOT3 rparen { mrb_sym r = intern_op(mul); mrb_sym k = intern_op(pow); mrb_sym b = intern_op(and); if (local_var_p(p, r) && local_var_p(p, k) && local_var_p(p, b)) { $$ = new_callargs(p, list1(new_splat(p, new_lvar(p, r))), list1(cons(new_kw_rest_args(p, 0), new_lvar(p, k))), new_block_arg(p, new_lvar(p, b))); } else { yyerror(&@1, p, "unexpected argument forwarding ..."); $$ = 0; } } ; opt_paren_args : none | paren_args ; opt_call_args : none | call_args opt_terms | args comma { $$ = new_callargs(p,$1,0,0); } | args comma assocs comma { $$ = new_callargs(p,$1,$3,0); } | assocs comma { $$ = new_callargs(p,0,$1,0); } ; call_args : command { void_expr_error(p, $1); $$ = new_callargs(p, list1($1), 0, 0); } | args opt_block_arg { $$ = new_callargs(p, $1, 0, $2); } | assocs opt_block_arg { $$ = new_callargs(p, 0, $1, $2); } | args comma assocs opt_block_arg { $$ = new_callargs(p, $1, $3, $4); } | block_arg { $$ = new_callargs(p, 0, 0, $1); } ; command_args : { $$ = p->cmdarg_stack; CMDARG_PUSH(1); } call_args { p->cmdarg_stack = $1; $$ = $2; } ; block_arg : tAMPER arg { $$ = new_block_arg(p, $2); } | tAMPER { $$ = new_block_arg(p, 0); } ; opt_block_arg : comma block_arg { $$ = $2; } | none { $$ = 0; } ; comma : ',' opt_nl ; args : arg { void_expr_error(p, $1); $$ = list1($1); } | tSTAR { $$ = list1(new_splat(p, new_lvar(p, intern_op(mul)))); } | tSTAR arg { $$ = list1(new_splat(p, $2)); } | args comma arg { void_expr_error(p, $3); $$ = push($1, $3); } | args comma tSTAR { $$ = push($1, new_splat(p, new_lvar(p, intern_op(mul)))); } | args comma tSTAR arg { $$ = push($1, new_splat(p, $4)); } ; mrhs : args comma arg { void_expr_error(p, $3); $$ = push($1, $3); } | args comma tSTAR arg { $$ = push($1, new_splat(p, $4)); } | tSTAR arg { $$ = list1(new_splat(p, $2)); } ; primary : literal | string { $$ = new_str(p, $1); } | xstring { $$ = new_xstr(p, $1); } | regexp | heredoc | var_ref | backref | tFID { $$ = new_fcall(p, $1, 0); } | keyword_begin { $$ = p->cmdarg_stack; p->cmdarg_stack = 0; } bodystmt keyword_end { p->cmdarg_stack = $2; $$ = new_begin(p, $3); } | tLPAREN_ARG { $$ = p->cmdarg_stack; p->cmdarg_stack = 0; } compstmt {p->lstate = EXPR_ENDARG;} rparen { p->cmdarg_stack = $2; $$ = $3; } | tLPAREN_ARG {p->lstate = EXPR_ENDARG;} rparen { $$ = new_nil(p); } | tLPAREN compstmt ')' { $$ = $2; } | primary_value tCOLON2 tCONSTANT { $$ = new_colon2(p, $1, $3); } | tCOLON3 tCONSTANT { $$ = new_colon3(p, $2); } | tLBRACK aref_args ']' { $$ = new_array(p, $2); } | tLBRACE assoc_list '}' { $$ = new_hash(p, $2); } | keyword_return { $$ = new_return(p, 0); } | keyword_yield opt_paren_args { $$ = new_yield(p, $2); } | keyword_not '(' expr rparen { $$ = call_uni_op(p, cond($3), "!"); } | keyword_not '(' rparen { $$ = call_uni_op(p, new_nil(p), "!"); } | operation brace_block { $$ = new_fcall(p, $1, new_callargs(p, 0, 0, $2)); } | method_call | method_call brace_block { call_with_block(p, $1, $2); $$ = $1; } | tLAMBDA { local_nest(p); nvars_nest(p); $$ = p->lpar_beg; p->lpar_beg = ++p->paren_nest; } f_larglist { $$ = p->cmdarg_stack; p->cmdarg_stack = 0; } lambda_body { p->lpar_beg = $2; $$ = new_lambda(p, $3, $5); local_unnest(p); nvars_unnest(p); p->cmdarg_stack = $4; CMDARG_LEXPOP(); } | keyword_if expr_value then compstmt if_tail keyword_end { $$ = new_if(p, cond($2), $4, $5); SET_LINENO($$, $1); } | keyword_unless expr_value then compstmt opt_else keyword_end { $$ = new_if(p, cond($2), $5, $4); SET_LINENO($$, $1); } | keyword_while {COND_PUSH(1);} expr_value do {COND_POP();} compstmt keyword_end { $$ = new_while(p, cond($3), $6); SET_LINENO($$, $1); } | keyword_until {COND_PUSH(1);} expr_value do {COND_POP();} compstmt keyword_end { $$ = new_until(p, cond($3), $6); SET_LINENO($$, $1); } | keyword_case expr_value opt_terms case_body keyword_end { $$ = new_case(p, $2, $4); } | keyword_case opt_terms case_body keyword_end { $$ = new_case(p, 0, $3); } | keyword_case expr_value opt_terms keyword_in p_expr then compstmt in_clauses keyword_end { node *in_clause = new_in(p, $5, NULL, $7, FALSE); $$ = new_case_match(p, $2, cons(in_clause, $8)); } | keyword_case expr_value opt_terms keyword_in p_expr modifier_if expr_value then compstmt in_clauses keyword_end { node *in_clause = new_in(p, $5, $7, $9, FALSE); $$ = new_case_match(p, $2, cons(in_clause, $10)); } | keyword_case expr_value opt_terms keyword_in p_expr modifier_unless expr_value then compstmt in_clauses keyword_end { node *in_clause = new_in(p, $5, $7, $9, TRUE); $$ = new_case_match(p, $2, cons(in_clause, $10)); } | keyword_for for_var keyword_in {COND_PUSH(1);} expr_value do {COND_POP();} compstmt keyword_end { $$ = new_for(p, $2, $5, $8); SET_LINENO($$, $1); } | keyword_class cpath superclass { if (p->in_def || p->in_single) yyerror(&@1, p, "class definition in method body"); $$ = local_switch(p); nvars_block(p); } bodystmt keyword_end { $$ = new_class(p, $2, $3, $5); SET_LINENO($$, $1); local_resume(p, $4); nvars_unnest(p); } | keyword_class tLSHFT expr { $$ = p->in_def; p->in_def = 0; } term { $$ = cons(local_switch(p), int_to_node(p->in_single)); nvars_block(p); p->in_single = 0; } bodystmt keyword_end { $$ = new_sclass(p, $3, $7); SET_LINENO($$, $1); local_resume(p, $6->car); nvars_unnest(p); p->in_def = $4; p->in_single = node_to_int($6->cdr); } | keyword_module cpath { if (p->in_def || p->in_single) yyerror(&@1, p, "module definition in method body"); $$ = local_switch(p); nvars_block(p); } bodystmt keyword_end { $$ = new_module(p, $2, $4); SET_LINENO($$, $1); local_resume(p, $3); nvars_unnest(p); } | defn_head f_arglist bodystmt keyword_end { $$ = $1; defn_setup(p, $$, $2, $3); nvars_unnest(p); p->in_def--; } | defs_head f_arglist bodystmt keyword_end { $$ = $1; defn_setup(p, $$, $2, $3); nvars_unnest(p); p->in_def--; p->in_single--; } | keyword_break { $$ = new_break(p, 0); } | keyword_next { $$ = new_next(p, 0); } | keyword_redo { $$ = new_redo(p); } | keyword_retry { $$ = new_retry(p); } ; primary_value : primary { $$ = $1; if (!$$) $$ = new_nil(p); } ; then : term | keyword_then | term keyword_then ; do : term | keyword_do_cond ; if_tail : opt_else | keyword_elsif expr_value then compstmt if_tail { $$ = new_if(p, cond($2), $4, $5); } ; opt_else : none | keyword_else compstmt { $$ = $2; } ; for_var : lhs { $$ = list1(list1($1)); } | mlhs ; f_margs : f_arg { $$ = list3($1,0,0); } | f_arg ',' tSTAR f_norm_arg { $$ = list3($1, new_lvar(p, $4), 0); } | f_arg ',' tSTAR f_norm_arg ',' f_arg { $$ = list3($1, new_lvar(p, $4), $6); } | f_arg ',' tSTAR { local_add_f(p, intern_op(mul)); $$ = list3($1, int_to_node(-1), 0); } | f_arg ',' tSTAR ',' f_arg { $$ = list3($1, int_to_node(-1), $5); } | tSTAR f_norm_arg { $$ = list3(0, new_lvar(p, $2), 0); } | tSTAR f_norm_arg ',' f_arg { $$ = list3(0, new_lvar(p, $2), $4); } | tSTAR { local_add_f(p, intern_op(mul)); $$ = list3(0, int_to_node(-1), 0); } | tSTAR ',' { local_add_f(p, intern_op(mul)); } f_arg { $$ = list3(0, int_to_node(-1), $4); } ; block_args_tail : f_block_kwarg ',' f_kwrest opt_f_block_arg { $$ = new_args_tail(p, $1, $3, $4); } | f_block_kwarg opt_f_block_arg { $$ = new_args_tail(p, $1, 0, $2); } | f_kwrest opt_f_block_arg { $$ = new_args_tail(p, 0, $1, $2); } | f_block_arg { $$ = new_args_tail(p, 0, 0, $1); } ; opt_block_args_tail : ',' block_args_tail { $$ = $2; } | /* none */ { $$ = new_args_tail(p, 0, 0, 0); } ; block_param : f_arg ',' f_block_optarg ',' f_rest_arg opt_block_args_tail { $$ = new_args(p, $1, $3, $5, 0, $6); } | f_arg ',' f_block_optarg ',' f_rest_arg ',' f_arg opt_block_args_tail { $$ = new_args(p, $1, $3, $5, $7, $8); } | f_arg ',' f_block_optarg opt_block_args_tail { $$ = new_args(p, $1, $3, 0, 0, $4); } | f_arg ',' f_block_optarg ',' f_arg opt_block_args_tail { $$ = new_args(p, $1, $3, 0, $5, $6); } | f_arg ',' f_rest_arg opt_block_args_tail { $$ = new_args(p, $1, 0, $3, 0, $4); } | f_arg ',' opt_block_args_tail { $$ = new_args(p, $1, 0, 0, 0, $3); } | f_arg ',' f_rest_arg ',' f_arg opt_block_args_tail { $$ = new_args(p, $1, 0, $3, $5, $6); } | f_arg opt_block_args_tail { $$ = new_args(p, $1, 0, 0, 0, $2); } | f_block_optarg ',' f_rest_arg opt_block_args_tail { $$ = new_args(p, 0, $1, $3, 0, $4); } | f_block_optarg ',' f_rest_arg ',' f_arg opt_block_args_tail { $$ = new_args(p, 0, $1, $3, $5, $6); } | f_block_optarg opt_block_args_tail { $$ = new_args(p, 0, $1, 0, 0, $2); } | f_block_optarg ',' f_arg opt_block_args_tail { $$ = new_args(p, 0, $1, 0, $3, $4); } | f_rest_arg opt_block_args_tail { $$ = new_args(p, 0, 0, $1, 0, $2); } | f_rest_arg ',' f_arg opt_block_args_tail { $$ = new_args(p, 0, 0, $1, $3, $4); } | block_args_tail { $$ = new_args(p, 0, 0, 0, 0, $1); } ; opt_block_param : none { local_add_blk(p); $$ = 0; } | block_param_def { p->cmd_start = TRUE; $$ = $1; } ; block_param_def : '|' {local_add_blk(p);} opt_bv_decl '|' { $$ = 0; } | tOROP { local_add_blk(p); $$ = 0; } | '|' block_param opt_bv_decl '|' { $$ = $2; } ; opt_bv_decl : opt_nl { $$ = 0; } | opt_nl ';' bv_decls opt_nl { $$ = 0; } ; bv_decls : bvar | bv_decls ',' bvar ; bvar : tIDENTIFIER { local_add_f(p, $1); new_bv(p, $1); } | f_bad_arg ; f_larglist : '(' f_args opt_bv_decl ')' { $$ = $2; } | f_args { $$ = $1; } ; lambda_body : tLAMBEG compstmt '}' { $$ = $2; } | keyword_do_LAMBDA bodystmt keyword_end { $$ = $2; } ; do_block : keyword_do_block { local_nest(p); nvars_nest(p); $$ = p->lineno; } opt_block_param bodystmt keyword_end { $$ = new_block(p,$3,$4); SET_LINENO($$, $2); local_unnest(p); nvars_unnest(p); } ; block_call : command do_block { call_with_block(p, $1, $2); $$ = $1; } | block_call call_op2 operation2 opt_paren_args { $$ = new_call(p, $1, $3, $4, $2); } | block_call call_op2 operation2 opt_paren_args brace_block { $$ = new_call(p, $1, $3, $4, $2); call_with_block(p, $$, $5); } | block_call call_op2 operation2 command_args do_block { $$ = new_call(p, $1, $3, $4, $2); call_with_block(p, $$, $5); } ; method_call : operation paren_args { $$ = new_fcall(p, $1, $2); } | primary_value call_op operation2 opt_paren_args { $$ = new_call(p, $1, $3, $4, $2); } | primary_value tCOLON2 operation2 paren_args { $$ = new_call(p, $1, $3, $4, tCOLON2); } | primary_value tCOLON2 operation3 { $$ = new_call(p, $1, $3, 0, tCOLON2); } | primary_value call_op paren_args { $$ = new_call(p, $1, MRB_SYM(call), $3, $2); } | primary_value tCOLON2 paren_args { $$ = new_call(p, $1, MRB_SYM(call), $3, tCOLON2); } | keyword_super paren_args { $$ = new_super(p, $2); } | keyword_super { $$ = new_zsuper(p); } | primary_value '[' opt_call_args ']' { $$ = new_call(p, $1, intern_op(aref), $3, '.'); } ; brace_block : '{' { local_nest(p); nvars_nest(p); $$ = p->lineno; } opt_block_param compstmt '}' { $$ = new_block(p,$3,$4); SET_LINENO($$, $2); local_unnest(p); nvars_unnest(p); } | keyword_do { local_nest(p); nvars_nest(p); $$ = p->lineno; } opt_block_param bodystmt keyword_end { $$ = new_block(p,$3,$4); SET_LINENO($$, $2); local_unnest(p); nvars_unnest(p); } ; case_body : keyword_when args then compstmt cases { $$ = cons(cons($2, $4), $5); } ; cases : opt_else { if ($1) { $$ = cons(cons(0, $1), 0); } else { $$ = 0; } } | case_body ; /* Pattern matching in-clauses for case/in */ /* in_kwarg is set by lexer when keyword_in is returned */ in_clauses : opt_else { $$ = $1 ? list1(new_in(p, NULL, NULL, $1, FALSE)) : 0; } | keyword_in p_expr {p->in_kwarg--;} then compstmt in_clauses { node *in_clause = new_in(p, $2, NULL, $5, FALSE); $$ = cons(in_clause, $6); } | keyword_in p_expr {p->in_kwarg--;} modifier_if expr_value then compstmt in_clauses { node *in_clause = new_in(p, $2, $5, $7, FALSE); $$ = cons(in_clause, $8); } | keyword_in p_expr {p->in_kwarg--;} modifier_unless expr_value then compstmt in_clauses { node *in_clause = new_in(p, $2, $5, $7, TRUE); $$ = cons(in_clause, $8); } ; /* Pattern expressions for case/in */ /* Bracket-less array patterns: in 1, 2, x is same as in [1, 2, x] */ /* Brace-less hash patterns: in a:, b: x is same as in {a:, b: x} */ p_expr : p_as | p_args_head p_as { $$ = new_pat_array(p, push($1, $2), 0, 0); } | p_args_head p_rest { $$ = new_pat_array(p, $1, $2, 0); } | p_args_head p_rest ',' p_args_post { $$ = new_pat_array(p, $1, $2, $4); } | p_rest { $$ = new_pat_array(p, 0, $1, 0); } | p_rest ',' p_args_post { $$ = new_pat_array(p, 0, $1, $3); } | p_hash_elems { /* Brace-less hash pattern: in a:, b: x */ $$ = new_pat_hash(p, $1, 0); } | p_hash_elems ',' p_kwrest { /* Brace-less hash pattern with kwrest: in a:, **rest */ $$ = new_pat_hash(p, $1, $3); } | p_kwrest { /* Brace-less kwrest only: in **rest */ $$ = new_pat_hash(p, 0, $1); } ; /* Comma-separated pattern list (prefix) */ p_args_head : p_as ',' { $$ = list1($1); } | p_args_head p_as ',' { $$ = push($1, $2); } ; /* Comma-separated pattern list (suffix, no trailing comma) */ p_args_post : p_as { $$ = list1($1); } | p_args_post ',' p_as { $$ = push($1, $3); } ; p_as : p_alt | p_alt tASSOC tIDENTIFIER { $$ = new_pat_as(p, $1, $3); } ; p_alt : p_value | p_alt '|' p_value { $$ = new_pat_alt(p, $1, $3); } ; p_value : p_var | numeric { $$ = new_pat_value(p, $1); } | symbol { $$ = new_pat_value(p, $1); } | tSTRING { $$ = new_pat_value(p, new_str(p, list1($1))); } | keyword_nil { $$ = new_pat_value(p, new_nil(p)); } | keyword_true { $$ = new_pat_value(p, new_true(p)); } | keyword_false { $$ = new_pat_value(p, new_false(p)); } | p_const { $$ = new_pat_value(p, $1); } | p_array | p_hash | '^' tIDENTIFIER { $$ = new_pat_pin(p, $2); } ; /* Array pattern: [a, b, *rest, c] */ p_array : tLBRACK p_array_body ']' { $$ = $2; } | tLBRACK ']' { $$ = new_pat_array(p, 0, 0, 0); } ; /* Array pattern body - pre elements, optional rest, post elements */ p_array_body : p_array_elems { /* Just pre elements, no rest */ $$ = new_pat_array(p, $1, 0, 0); } | p_array_elems ',' p_rest { /* Pre elements + rest, no post */ $$ = new_pat_array(p, $1, $3, 0); } | p_array_elems ',' p_rest ',' p_array_elems { /* Pre + rest + post */ $$ = new_pat_array(p, $1, $3, $5); } | p_rest { /* Just rest, no pre or post */ $$ = new_pat_array(p, 0, $1, 0); } | p_rest ',' p_array_elems { /* Rest + post, no pre */ $$ = new_pat_array(p, 0, $1, $3); } | p_rest ',' p_array_elems ',' p_rest { /* Find pattern: [*pre, elems, *post] */ $$ = new_pat_find(p, $1, $3, $5); } ; /* Non-rest array pattern elements - use p_as, not p_expr to avoid bracket-less recursion */ p_array_elems : p_as { $$ = list1($1); } | p_array_elems ',' p_as { $$ = push($1, $3); } ; /* Rest pattern in array: *var, *_, or just * */ p_rest : tSTAR tIDENTIFIER { $$ = new_pat_var(p, $2); } | tSTAR { /* Anonymous rest pattern */ $$ = (node*)-1; } ; /* Constant path for pattern matching: Foo, Foo::Bar, ::Foo */ p_const : tCONSTANT { $$ = new_const(p, $1); } | p_const tCOLON2 tCONSTANT { $$ = new_colon2(p, $1, $3); } | tCOLON3 tCONSTANT { $$ = new_colon3(p, $2); } ; /* Hash pattern: {a:, b: x, **rest} */ p_hash : tLBRACE p_hash_body '}' { $$ = $2; } | tLBRACE '}' { $$ = new_pat_hash(p, 0, 0); } ; /* Hash pattern body - pairs and optional kwrest */ p_hash_body : p_hash_elems { $$ = new_pat_hash(p, $1, 0); } | p_hash_elems ',' p_kwrest { $$ = new_pat_hash(p, $1, $3); } | p_kwrest { $$ = new_pat_hash(p, 0, $1); } ; /* Hash pattern element list */ p_hash_elems : p_hash_elem { $$ = list1($1); } | p_hash_elems ',' p_hash_elem { $$ = push($1, $3); } ; /* Hash pattern element: key: pattern or key: (shorthand) */ /* Use p_as, not p_expr to avoid brace-less recursion inside hash patterns */ /* Note: CRuby only supports label syntax (foo:), not hashrocket (:foo =>) */ p_hash_elem : tIDENTIFIER tLABEL_TAG p_as { /* {key: pattern} */ $$ = cons(new_sym(p, $1), $3); } | tIDENTIFIER tLABEL_TAG { /* {key:} shorthand - binds to variable with same name */ $$ = cons(new_sym(p, $1), new_pat_var(p, $1)); } ; /* Keyword rest pattern: **var, **nil, or ** */ p_kwrest : tDSTAR tIDENTIFIER { $$ = new_pat_var(p, $2); } | tDSTAR keyword_nil { /* **nil - exact match, no extra keys allowed */ $$ = (node*)-1; } | tDSTAR { /* ** - anonymous rest, discards extra keys */ $$ = (node*)-2; } ; p_var : tIDENTIFIER { $$ = new_pat_var(p, $1); } ; opt_rescue : keyword_rescue exc_list exc_var then compstmt opt_rescue { $$ = list1(list3($2, $3, $5)); if ($6) $$ = append($$, $6); } | none ; exc_list : arg { $$ = list1($1); } | mrhs | none ; exc_var : tASSOC lhs { $$ = $2; } | none ; opt_ensure : keyword_ensure compstmt { $$ = $2; } | none ; literal : numeric | symbol | words | symbols ; string : string_fragment | string string_fragment { $$ = append($1, $2); } ; string_fragment : tCHAR { /* tCHAR is (len . str), wrap as cons list */ $$ = list1($1); } | tSTRING { /* tSTRING is (len . str), wrap as cons list */ $$ = list1($1); } | tSTRING_BEG tSTRING { /* $2 is (len . str), wrap as cons list */ $$ = list1($2); } | tSTRING_BEG string_rep tSTRING { $$ = push($2, $3); } ; string_rep : string_interp | string_rep string_interp { $$ = append($1, $2); } ; string_interp : tSTRING_MID { /* $1 is already in (len . str) format */ $$ = list1($1); } | tSTRING_PART { $$ = push_strterm(p); } compstmt '}' { pop_strterm(p,$2); /* $1 is already in (len . str) format, create (-1 . node) for expression */ node *expr_elem = cons(int_to_node(-1), $3); $$ = list2($1, expr_elem); } | tLITERAL_DELIM { $$ = list1(new_literal_delim(p)); } | tHD_LITERAL_DELIM heredoc_bodies { $$ = list1(new_literal_delim(p)); } ; xstring : tXSTRING_BEG tXSTRING { $$ = cons($2, (node*)NULL); } | tXSTRING_BEG string_rep tXSTRING { $$ = push($2, $3); } ; regexp : tREGEXP_BEG tREGEXP { node *data = $2; /* ((len . pattern) . (flags . encoding)) */ const char *flags = (const char*)data->cdr->car; const char *encoding = (const char*)data->cdr->cdr; /* Use data->car directly as pattern_list: (len . pattern) */ node *pattern_list = cons(data->car, (node*)NULL); $$ = new_regx(p, pattern_list, flags, encoding); } | tREGEXP_BEG string_rep tREGEXP { node *data = $3; /* ((len . pattern) . (flags . encoding)) */ const char *flags = (const char*)data->cdr->car; const char *encoding = (const char*)data->cdr->cdr; /* Append the pattern from $3->car to the string list $2 */ node *complete_list = push($2, data->car); $$ = new_regx(p, complete_list, flags, encoding); } ; heredoc : tHEREDOC_BEG ; heredoc_bodies : heredoc_body | heredoc_bodies heredoc_body ; heredoc_body : tHEREDOC_END { parser_heredoc_info *info = parsing_heredoc_info(p); info->doc = push(info->doc, new_str_empty(p)); heredoc_end(p); } | heredoc_string_rep tHEREDOC_END { heredoc_end(p); } ; heredoc_string_rep : heredoc_string_interp | heredoc_string_rep heredoc_string_interp ; heredoc_string_interp : tHD_STRING_MID { parser_heredoc_info *info = parsing_heredoc_info(p); info->doc = push(info->doc, $1); heredoc_treat_nextline(p); } | tHD_STRING_PART { $$ = push_strterm(p); } compstmt '}' { pop_strterm(p, $2); parser_heredoc_info *info = parsing_heredoc_info(p); /* $1 is already in (len . str) format, create (-1 . node) for expression */ node *expr_elem = cons(int_to_node(-1), $3); info->doc = push(push(info->doc, $1), expr_elem); } ; words : tWORDS_BEG tSTRING { $$ = new_words(p, list1($2)); } | tWORDS_BEG string_rep tSTRING { node *n = $2; n = push(n, $3); $$ = new_words(p, n); } ; symbol : basic_symbol { $$ = new_sym(p, $1); } | tSYMBEG tSTRING_BEG string_rep tSTRING { node *n = $3; p->lstate = EXPR_ENDARG; if (node_to_int($4->car) > 0) { n = push(n, $4); } else { cons_free($4); } $$ = new_dsym(p, n); } | tSYMBEG tNUMPARAM { mrb_sym sym = intern_numparam($2); $$ = new_sym(p, sym); } ; basic_symbol : tSYMBEG sym { p->lstate = EXPR_END; $$ = $2; } ; sym : fname | tIVAR | tGVAR | tCVAR | tSTRING { $$ = new_strsym(p, $1); } | tSTRING_BEG tSTRING { $$ = new_strsym(p, $2); } ; symbols : tSYMBOLS_BEG tSTRING { $$ = new_symbols(p, list1($2)); } | tSYMBOLS_BEG string_rep tSTRING { node *n = $2; n = push(n, $3); $$ = new_symbols(p, n); } ; numeric : tINTEGER | tFLOAT | tUMINUS_NUM tINTEGER %prec tLOWEST { $$ = new_negate(p, $2); } | tUMINUS_NUM tFLOAT %prec tLOWEST { $$ = new_negate(p, $2); } ; variable : tIDENTIFIER { $$ = new_lvar(p, $1); } | tIVAR { $$ = new_ivar(p, $1); } | tGVAR { $$ = new_gvar(p, $1); } | tCVAR { $$ = new_cvar(p, $1); } | tCONSTANT { $$ = new_const(p, $1); } ; var_lhs : variable { assignable(p, $1); } | tNUMPARAM { yyerror(&@1, p, "can't assign to numbered parameter"); } ; var_ref : variable { $$ = var_reference(p, $1); } | tNUMPARAM { $$ = new_nvar(p, $1); } | keyword_nil { $$ = new_nil(p); } | keyword_self { $$ = new_self(p); } | keyword_true { $$ = new_true(p); } | keyword_false { $$ = new_false(p); } | keyword__FILE__ { const char *fn = mrb_sym_name_len(p->mrb, p->filename_sym, NULL); if (!fn) { fn = "(null)"; } $$ = new_str(p, cons(cons(int_to_node(strlen(fn)), (node*)fn), (node*)NULL)); } | keyword__LINE__ { char buf[16]; dump_int(p->lineno, buf); $$ = new_int(p, buf, 10, 0); } | keyword__ENCODING__ { $$ = new_fcall(p, MRB_SYM(__ENCODING__), 0); } ; backref : tNTH_REF | tBACK_REF ; superclass : /* term */ { $$ = 0; } | '<' { p->lstate = EXPR_BEG; p->cmd_start = TRUE; } expr_value term { $$ = $3; } /* | error term { yyerrok; $$ = 0; } */ ; f_opt_arglist_paren : f_arglist_paren | none ; f_arglist_paren : '(' f_args rparen { $$ = $2; p->lstate = EXPR_BEG; p->cmd_start = TRUE; } | '(' f_arg ',' tBDOT3 rparen { $$ = new_args_dots(p, $2); } | '(' tBDOT3 rparen { $$ = new_args_dots(p, 0); } ; f_arglist : f_arglist_paren | f_args term { $$ = $1; } | f_arg ',' tBDOT3 term { $$ = new_args_dots(p, $1); } | tDOT3 term { $$ = new_args_dots(p, 0); } ; f_label : tIDENTIFIER tLABEL_TAG { $$ = $1; local_nest(p); p->lstate = EXPR_MID; /* make newlines significant after label */ } | tNUMPARAM tLABEL_TAG { $$ = intern_numparam($1); local_nest(p); p->lstate = EXPR_MID; /* make newlines significant after label */ } ; f_kw : f_label arg { void_expr_error(p, $2); $$ = new_kw_arg(p, $1, cons($2, locals_node(p))); local_unnest(p); } | f_label { $$ = new_kw_arg(p, $1, 0); local_unnest(p); } ; f_block_kw : f_label primary_value { void_expr_error(p, $2); $$ = new_kw_arg(p, $1, cons($2, locals_node(p))); local_unnest(p); } | f_label { $$ = new_kw_arg(p, $1, 0); local_unnest(p); } ; f_block_kwarg : f_block_kw { $$ = list1($1); } | f_block_kwarg ',' f_block_kw { $$ = push($1, $3); } ; f_kwarg : f_kw { $$ = list1($1); } | f_kwarg ',' f_kw { $$ = push($1, $3); } ; kwrest_mark : tPOW | tDSTAR ; f_kwrest : kwrest_mark tIDENTIFIER { $$ = $2; } | kwrest_mark { $$ = intern_op(pow); } ; args_tail : f_kwarg ',' f_kwrest opt_f_block_arg { $$ = new_args_tail(p, $1, $3, $4); } | f_kwarg opt_f_block_arg { $$ = new_args_tail(p, $1, 0, $2); } | f_kwrest opt_f_block_arg { $$ = new_args_tail(p, 0, $1, $2); } | f_block_arg { $$ = new_args_tail(p, 0, 0, $1); } ; opt_args_tail : ',' args_tail { $$ = $2; } | ',' { $$ = new_args_tail(p, 0, 0, 0); } | /* none */ { $$ = new_args_tail(p, 0, 0, 0); } ; f_args : f_arg ',' f_optarg ',' f_rest_arg opt_args_tail { $$ = new_args(p, $1, $3, $5, 0, $6); } | f_arg ',' f_optarg ',' f_rest_arg ',' f_arg opt_args_tail { $$ = new_args(p, $1, $3, $5, $7, $8); } | f_arg ',' f_optarg opt_args_tail { $$ = new_args(p, $1, $3, 0, 0, $4); } | f_arg ',' f_optarg ',' f_arg opt_args_tail { $$ = new_args(p, $1, $3, 0, $5, $6); } | f_arg ',' f_rest_arg opt_args_tail { $$ = new_args(p, $1, 0, $3, 0, $4); } | f_arg ',' f_rest_arg ',' f_arg opt_args_tail { $$ = new_args(p, $1, 0, $3, $5, $6); } | f_arg opt_args_tail { $$ = new_args(p, $1, 0, 0, 0, $2); } | f_optarg ',' f_rest_arg opt_args_tail { $$ = new_args(p, 0, $1, $3, 0, $4); } | f_optarg ',' f_rest_arg ',' f_arg opt_args_tail { $$ = new_args(p, 0, $1, $3, $5, $6); } | f_optarg opt_args_tail { $$ = new_args(p, 0, $1, 0, 0, $2); } | f_optarg ',' f_arg opt_args_tail { $$ = new_args(p, 0, $1, 0, $3, $4); } | f_rest_arg opt_args_tail { $$ = new_args(p, 0, 0, $1, 0, $2); } | f_rest_arg ',' f_arg opt_args_tail { $$ = new_args(p, 0, 0, $1, $3, $4); } | args_tail { $$ = new_args(p, 0, 0, 0, 0, $1); } | /* none */ { local_add_f(p, 0); $$ = new_args(p, 0, 0, 0, 0, 0); } ; f_bad_arg : tCONSTANT { yyerror(&@1, p, "formal argument cannot be a constant"); $$ = 0; } | tIVAR { yyerror(&@1, p, "formal argument cannot be an instance variable"); $$ = 0; } | tGVAR { yyerror(&@1, p, "formal argument cannot be a global variable"); $$ = 0; } | tCVAR { yyerror(&@1, p, "formal argument cannot be a class variable"); $$ = 0; } | tNUMPARAM { yyerror(&@1, p, "formal argument cannot be a numbered parameter"); $$ = 0; } ; f_norm_arg : f_bad_arg { $$ = 0; } | tIDENTIFIER { local_add_f(p, $1); $$ = $1; } ; f_arg_item : f_norm_arg { $$ = new_lvar(p, $1); } | tLPAREN { $$ = local_switch(p); } f_margs rparen { $$ = new_marg(p, $3); local_resume(p, $2); local_add_f(p, 0); } ; f_arg : f_arg_item { $$ = list1($1); } | f_arg ',' f_arg_item { $$ = push($1, $3); } ; f_opt_asgn : tIDENTIFIER '=' { local_add_f(p, $1); local_nest(p); $$ = $1; } ; f_opt : f_opt_asgn arg { void_expr_error(p, $2); $$ = cons(sym_to_node($1), cons($2, locals_node(p))); local_unnest(p); } ; f_block_opt : f_opt_asgn primary_value { void_expr_error(p, $2); $$ = cons(sym_to_node($1), cons($2, locals_node(p))); local_unnest(p); } ; f_block_optarg : f_block_opt { $$ = list1($1); } | f_block_optarg ',' f_block_opt { $$ = push($1, $3); } ; f_optarg : f_opt { $$ = list1($1); } | f_optarg ',' f_opt { $$ = push($1, $3); } ; restarg_mark : '*' | tSTAR ; f_rest_arg : restarg_mark tIDENTIFIER { local_add_f(p, $2); $$ = $2; } | restarg_mark { $$ = intern_op(mul); local_add_f(p, $$); } ; blkarg_mark : '&' | tAMPER ; f_block_arg : blkarg_mark tIDENTIFIER { $$ = $2; } | blkarg_mark keyword_nil { $$ = MRB_SYM(nil); } | blkarg_mark { $$ = intern_op(and); } ; opt_f_block_arg : ',' f_block_arg { $$ = $2; } | ',' { $$ = 0; } | none { $$ = 0; } ; singleton : var_ref { prohibit_literals(p, $1); $$ = $1; if (!$$) $$ = new_nil(p); } | '(' {p->lstate = EXPR_BEG;} expr rparen { prohibit_literals(p, $3); $$ = $3; } ; assoc_list : none | assocs trailer { $$ = $1; } ; assocs : assoc { $$ = list1($1); } | assocs comma assoc { $$ = push($1, $3); } ; assoc : arg tASSOC arg { void_expr_error(p, $1); void_expr_error(p, $3); $$ = cons($1, $3); } | tIDENTIFIER tLABEL_TAG arg { void_expr_error(p, $3); $$ = cons(new_sym(p, $1), $3); } | tIDENTIFIER tLABEL_TAG { $$ = cons(new_sym(p, $1), label_reference(p, $1)); } | tNUMPARAM tLABEL_TAG { mrb_sym sym = intern_numparam($1); $$ = cons(new_sym(p, sym), label_reference(p, sym)); } | tNUMPARAM tLABEL_TAG arg { void_expr_error(p, $3); $$ = cons(new_sym(p, intern_numparam($1)), $3); } | string_fragment tLABEL_TAG arg { void_expr_error(p, $3); if ($1->cdr) { /* Multiple fragments - create dynamic symbol */ $$ = cons(new_dsym(p, $1), $3); } else if (node_to_int($1->car->car) < 0) { /* Single fragment but it's an expression (-1 . node) - create dynamic symbol */ $$ = cons(new_dsym(p, $1), $3); } else { /* Single string fragment - create simple symbol */ $$ = cons(new_sym(p, new_strsym(p, $1->car)), $3); } } | tDSTAR arg { void_expr_error(p, $2); $$ = cons(new_kw_rest_args(p, 0), $2); } | tDSTAR { $$ = cons(new_kw_rest_args(p, 0), new_lvar(p, intern_op(pow))); } ; operation : tIDENTIFIER | tCONSTANT | tFID ; operation2 : tIDENTIFIER | tCONSTANT | tFID | op ; operation3 : tIDENTIFIER | tFID | op ; dot_or_colon : '.' | tCOLON2 ; call_op : '.' { $$ = '.'; } | tANDDOT { $$ = 0; } ; call_op2 : call_op | tCOLON2 { $$ = tCOLON2; } ; opt_terms : /* none */ | terms ; opt_nl : /* none */ | opt_nl nl ; rparen : opt_terms ')' ; trailer : /* none */ | terms | comma ; term : ';' {yyerrok;} | nl ; nl : '\n' { p->lineno += $1; p->column = 0; } | heredoc_body ; terms : term | terms term ; none : /* none */ { $$ = 0; } ; %% #define pylval (*((YYSTYPE*)(p->ylval))) static void yyerror(void *lp, parser_state *p, const char *s) { char* c; size_t n; if (! p->capture_errors) { #ifndef MRB_NO_STDIO if (p->filename_sym) { const char *filename = mrb_sym_name_len(p->mrb, p->filename_sym, NULL); fprintf(stderr, "%s:%d:%d: %s\n", filename, p->lineno, p->column, s); } else { fprintf(stderr, "line %d:%d: %s\n", p->lineno, p->column, s); } #endif } else if (p->nerr < sizeof(p->error_buffer) / sizeof(p->error_buffer[0])) { n = strlen(s); c = (char*)parser_palloc(p, n + 1); memcpy(c, s, n + 1); p->error_buffer[p->nerr].message = c; p->error_buffer[p->nerr].lineno = p->lineno; p->error_buffer[p->nerr].column = p->column; } p->nerr++; } static void yyerror_c(parser_state *p, const char *msg, char c) { char buf[256]; strncpy(buf, msg, sizeof(buf) - 2); buf[sizeof(buf) - 2] = '\0'; strncat(buf, &c, 1); yyerror(NULL, p, buf); } static void yywarning(parser_state *p, const char *s) { char* c; size_t n; if (! p->capture_errors) { #ifndef MRB_NO_STDIO if (p->filename_sym) { const char *filename = mrb_sym_name_len(p->mrb, p->filename_sym, NULL); fprintf(stderr, "%s:%d:%d: warning: %s\n", filename, p->lineno, p->column, s); } else { fprintf(stderr, "line %d:%d: warning: %s\n", p->lineno, p->column, s); } #endif } else if (p->nwarn < sizeof(p->warn_buffer) / sizeof(p->warn_buffer[0])) { n = strlen(s); c = (char*)parser_palloc(p, n + 1); memcpy(c, s, n + 1); p->warn_buffer[p->nwarn].message = c; p->warn_buffer[p->nwarn].lineno = p->lineno; p->warn_buffer[p->nwarn].column = p->column; } p->nwarn++; } static void yywarning_s(parser_state *p, const char *msg, const char *s) { char buf[256]; strncpy(buf, msg, sizeof(buf) - 1); buf[sizeof(buf) - 1] = '\0'; strncat(buf, ": ", sizeof(buf) - strlen(buf) - 1); strncat(buf, s, sizeof(buf) - strlen(buf) - 1); yywarning(p, buf); } static void backref_error(parser_state *p, node *n) { int c; c = node_to_int(n->car); if (c == NODE_NTH_REF) { yyerror_c(p, "can't set variable $", (char)node_to_int(n->cdr)+'0'); } else if (c == NODE_BACK_REF) { yyerror_c(p, "can't set variable $", (char)node_to_int(n->cdr)); } else { yyerror(NULL, p, "Internal error in backref_error()"); } } static void void_expr_error(parser_state *p, node *n) { if (n == NULL) return; /* Check if this is a variable-sized node first */ struct mrb_ast_var_header *header = (struct mrb_ast_var_header*)n; if (header) { /* Handle variable-sized nodes */ switch ((enum node_type)header->node_type) { case NODE_BREAK: case NODE_RETURN: case NODE_NEXT: case NODE_REDO: case NODE_RETRY: yyerror(NULL, p, "void value expression"); return; case NODE_AND: case NODE_OR: { struct mrb_ast_and_node *and_n = (struct mrb_ast_and_node*)n; void_expr_error(p, (node*)and_n->left); void_expr_error(p, (node*)and_n->right); } return; case NODE_STMTS: { struct mrb_ast_stmts_node *stmts = (struct mrb_ast_stmts_node*)n; node *last = stmts->stmts; if (last) { /* Find the last statement in the cons list */ while (last->cdr) { last = last->cdr; } void_expr_error(p, last->car); } } return; case NODE_BEGIN: { struct mrb_ast_begin_node *begin_n = (struct mrb_ast_begin_node*)n; if (begin_n->body) { void_expr_error(p, (node*)begin_n->body); } } return; default: /* Other variable-sized nodes are OK */ return; } } /* Should not reach here - all nodes should be variable-sized now */ } static void pushback(parser_state *p, int c); static mrb_bool peeks(parser_state *p, const char *s); static mrb_bool skips(parser_state *p, const char *s); static inline int nextc0(parser_state *p) { if (p->s && p->s < p->send) { return (unsigned char)*p->s++; } else { #ifndef MRB_NO_STDIO int c; if (p->f) { c = fgetc(p->f); if (!feof(p->f)) return c; } #endif return -1; } } static inline int nextc(parser_state *p) { int c; if (p->pb) { node *tmp; c = node_to_int(p->pb->car); tmp = p->pb; p->pb = p->pb->cdr; cons_free(tmp); } else { c = nextc0(p); if (c < 0) goto eof; } if (c >= 0) { p->column++; } if (c == '\r') { const int lf = nextc0(p); if (lf == '\n') { return '\n'; } if (lf > 0) pushback(p, lf); } return c; eof: if (!p->cxt) return -1; else { if (p->cxt->partial_hook(p) < 0) return -1; /* end of program(s) */ return -2; /* end of a file in the program files */ } } static void pushback(parser_state *p, int c) { if (c >= 0) { p->column--; } p->pb = cons(int_to_node(c), p->pb); } static void skip(parser_state *p, char term) { int c; for (;;) { c = nextc(p); if (c < 0) break; if (c == term) break; } } static int peekc_n(parser_state *p, int n) { node *list = 0; int c0; do { c0 = nextc(p); if (c0 == -1) return c0; /* do not skip partial EOF */ if (c0 >= 0) --p->column; list = push(list, int_to_node(c0)); } while(n--); if (p->pb) { p->pb = append(list, p->pb); } else { p->pb = list; } return c0; } static mrb_bool peek_n(parser_state *p, int c, int n) { return peekc_n(p, n) == c && c >= 0; } #define peek(p,c) peek_n((p), (c), 0) static mrb_bool peeks(parser_state *p, const char *s) { size_t len = strlen(s); #ifndef MRB_NO_STDIO if (p->f) { int n = 0; while (*s) { if (!peek_n(p, *s++, n++)) return FALSE; } return TRUE; } else #endif if (p->s && p->s + len <= p->send) { if (memcmp(p->s, s, len) == 0) return TRUE; } return FALSE; } static mrb_bool skips(parser_state *p, const char *s) { int c; for (;;) { /* skip until first char */ for (;;) { c = nextc(p); if (c < 0) return FALSE; if (c == '\n') { p->lineno++; p->column = 0; } if (c == *s) break; } s++; if (peeks(p, s)) { size_t len = strlen(s); while (len--) { if (nextc(p) == '\n') { p->lineno++; p->column = 0; } } return TRUE; } else { s--; } } return FALSE; } static int newtok(parser_state *p) { if (p->tokbuf != p->buf) { mrbc_free(p->tokbuf); p->tokbuf = p->buf; p->tsiz = MRB_PARSER_TOKBUF_SIZE; } p->tidx = 0; return p->column - 1; } static void tokadd(parser_state *p, int32_t c) { char utf8[4]; int i, len; /* mrb_assert(-0x10FFFF <= c && c <= 0xFF); */ if (c >= 0) { /* Single byte from source or non-Unicode escape */ utf8[0] = (char)c; len = 1; } else { /* Unicode character (negative c indicates codepoint) */ len = (int)mrb_utf8_to_buf(utf8, (uint32_t)(-c)); } if (p->tidx+len >= p->tsiz) { if (p->tsiz >= MRB_PARSER_TOKBUF_MAX) { p->tidx += len; return; } p->tsiz *= 2; if (p->tokbuf == p->buf) { p->tokbuf = (char*)mrbc_malloc(p->tsiz); memcpy(p->tokbuf, p->buf, MRB_PARSER_TOKBUF_SIZE); } else { p->tokbuf = (char*)mrbc_realloc(p->tokbuf, p->tsiz); } } for (i = 0; i < len; i++) { p->tokbuf[p->tidx++] = utf8[i]; } } static int toklast(parser_state *p) { return p->tokbuf[p->tidx-1]; } static void tokfix(parser_state *p) { if (p->tidx >= MRB_PARSER_TOKBUF_MAX) { p->tidx = MRB_PARSER_TOKBUF_MAX-1; yyerror(NULL, p, "string too long (truncated)"); } p->tokbuf[p->tidx] = '\0'; } static const char* tok(parser_state *p) { return p->tokbuf; } static int toklen(parser_state *p) { return p->tidx; } #define IS_ARG() (p->lstate == EXPR_ARG || p->lstate == EXPR_CMDARG) #define IS_END() (p->lstate == EXPR_END || p->lstate == EXPR_ENDARG || p->lstate == EXPR_ENDFN) #define IS_BEG() (p->lstate == EXPR_BEG || p->lstate == EXPR_MID || p->lstate == EXPR_VALUE || p->lstate == EXPR_CLASS) #define IS_SPCARG(c) (IS_ARG() && space_seen && !ISSPACE(c)) #define IS_LABEL_POSSIBLE() ((p->lstate == EXPR_BEG && !cmd_state) || IS_ARG() || p->lstate == EXPR_VALUE) #define IS_LABEL_SUFFIX(n) (peek_n(p, ':',(n)) && !peek_n(p, ':', (n)+1)) static int32_t scan_oct(const int *start, int len, int *retlen) { const int *s = start; int32_t retval = 0; /* mrb_assert(len <= 3) */ while (len-- && *s >= '0' && *s <= '7') { retval <<= 3; retval |= *s++ - '0'; } *retlen = (int)(s - start); return retval; } static int32_t scan_hex(parser_state *p, const int *start, int len, int *retlen) { static const char hexdigit[] = "0123456789abcdef0123456789ABCDEF"; const int *s = start; uint32_t retval = 0; char *tmp; /* mrb_assert(len <= 8) */ while (len-- && *s && (tmp = (char*)strchr(hexdigit, *s))) { retval <<= 4; retval |= (tmp - hexdigit) & 15; s++; } *retlen = (int)(s - start); return (int32_t)retval; } static int32_t read_escape_unicode(parser_state *p, int limit) { int buf[9]; int i; int32_t hex; /* Look for opening brace */ i = 0; buf[0] = nextc(p); if (buf[0] < 0) { eof: yyerror(NULL, p, "invalid escape character syntax"); return -1; } if (ISXDIGIT(buf[0])) { /* \uxxxx form */ for (i=1; i 0x10FFFF || (hex & 0xFFFFF800) == 0xD800) { yyerror(NULL, p, "invalid Unicode code point"); return -1; } return hex; } /* Return negative to indicate Unicode code point */ static int32_t read_escape(parser_state *p) { int32_t c; switch (c = nextc(p)) { case '\\':/* Backslash */ return c; case 'n':/* newline */ return '\n'; case 't':/* horizontal tab */ return '\t'; case 'r':/* carriage-return */ return '\r'; case 'f':/* form-feed */ return '\f'; case 'v':/* vertical tab */ return '\13'; case 'a':/* alarm(bell) */ return '\007'; case 'e':/* escape */ return 033; case '0': case '1': case '2': case '3': /* octal constant */ case '4': case '5': case '6': case '7': { int buf[3]; int i; buf[0] = c; for (i=1; i<3; i++) { buf[i] = nextc(p); if (buf[i] < 0) goto eof; if (buf[i] < '0' || '7' < buf[i]) { pushback(p, buf[i]); break; } } c = scan_oct(buf, i, &i); } return c; case 'x': /* hex constant */ { int buf[2]; int i; for (i=0; i<2; i++) { buf[i] = nextc(p); if (buf[i] < 0) goto eof; if (!ISXDIGIT(buf[i])) { pushback(p, buf[i]); break; } } if (i == 0) { yyerror(NULL, p, "invalid hex escape"); return -1; } return scan_hex(p, buf, i, &i); } case 'u': /* Unicode */ if (peek(p, '{')) { /* \u{xxxxxxxx} form */ nextc(p); c = read_escape_unicode(p, 8); if (c < 0) return 0; if (nextc(p) != '}') goto eof; } else { c = read_escape_unicode(p, 4); if (c < 0) return 0; } return -c; case 'b':/* backspace */ return '\010'; case 's':/* space */ return ' '; case 'M': if ((c = nextc(p)) != '-') { yyerror(NULL, p, "Invalid escape character syntax"); pushback(p, c); return '\0'; } if ((c = nextc(p)) == '\\') { return read_escape(p) | 0x80; } else if (c < 0) goto eof; else { return ((c & 0xff) | 0x80); } case 'C': if ((c = nextc(p)) != '-') { yyerror(NULL, p, "Invalid escape character syntax"); pushback(p, c); return '\0'; } case 'c': if ((c = nextc(p))== '\\') { c = read_escape(p); } else if (c == '?') return 0177; else if (c < 0) goto eof; return c & 0x9f; eof: case -1: case -2: /* end of a file */ yyerror(NULL, p, "Invalid escape character syntax"); return '\0'; default: return c; } } static void heredoc_count_indent(parser_heredoc_info *hinfo, const char *str, size_t len, size_t spaces, size_t *offset) { size_t indent = 0; *offset = 0; for (size_t i = 0; i < len; i++) { size_t size; if (str[i] == '\n') break; else if (str[i] == '\t') size = 8; else if (ISSPACE(str[i])) size = 1; else break; size_t nindent = indent + size; if (nindent > spaces || nindent > hinfo->indent) break; indent = nindent; *offset += 1; } } static void heredoc_remove_indent(parser_state *p, parser_heredoc_info *hinfo) { if (!hinfo->remove_indent || hinfo->indent == 0) return; node *indented, *n, *pair, *escaped, *nspaces; const char *str; size_t len, spaces, offset, start, end; indented = hinfo->indented; while (indented) { n = indented->car; pair = n->car; len = (size_t)pair->car; str = (char*)pair->cdr; escaped = n->cdr->car; nspaces = n->cdr->cdr; if (escaped) { char *newstr = strndup(str, len); size_t newlen = 0; start = 0; while (start < len) { end = escaped ? (size_t)escaped->car : len; if (end > len) end = len; spaces = (size_t)nspaces->car; size_t esclen = end - start; heredoc_count_indent(hinfo, str + start, esclen, spaces, &offset); esclen -= offset; memcpy(newstr + newlen, str + start + offset, esclen); newlen += esclen; start = end; if (escaped) escaped = escaped->cdr; nspaces = nspaces->cdr; } if (newlen < len) newstr[newlen] = '\0'; pair->car = (node*)newlen; pair->cdr = (node*)newstr; } else { spaces = (size_t)nspaces->car; heredoc_count_indent(hinfo, str, len, spaces, &offset); pair->car = (node*)(len - offset); pair->cdr = (node*)(str + offset); } indented = indented->cdr; } } static void heredoc_push_indented(parser_state *p, parser_heredoc_info *hinfo, node *pair, node *escaped, node *nspaces, mrb_bool empty_line) { hinfo->indented = push(hinfo->indented, cons(pair, cons(escaped, nspaces))); while (nspaces) { size_t tspaces = (size_t)nspaces->car; if ((hinfo->indent == ~0U || tspaces < hinfo->indent) && !empty_line) hinfo->indent = tspaces; nspaces = nspaces->cdr; } } static int parse_string(parser_state *p) { int c; string_type type = (string_type)p->lex_strterm->type; int nest_level = p->lex_strterm->level; int beg = p->lex_strterm->paren; int end = p->lex_strterm->term; parser_heredoc_info *hinfo = (type & STR_FUNC_HEREDOC) ? parsing_heredoc_info(p) : NULL; mrb_bool unindent = hinfo && hinfo->remove_indent; mrb_bool head = hinfo && hinfo->line_head; mrb_bool empty = TRUE; size_t spaces = 0; size_t pos = -1; node *escaped = NULL; node *nspaces = NULL; if (beg == 0) beg = -3; /* should never happen */ if (end == 0) end = -3; newtok(p); while ((c = nextc(p)) != end || nest_level != 0) { pos++; if (hinfo && (c == '\n' || c < 0)) { mrb_bool line_head; tokadd(p, '\n'); tokfix(p); p->lineno++; p->column = 0; line_head = hinfo->line_head; hinfo->line_head = TRUE; if (line_head) { /* check whether end of heredoc */ const char *s = tok(p); int len = toklen(p); if (hinfo->allow_indent) { while (ISSPACE(*s) && len > 0) { s++; len--; } } if (hinfo->term_len > 0 && len-1 == hinfo->term_len && strncmp(s, hinfo->term, len-1) == 0) { heredoc_remove_indent(p, hinfo); return tHEREDOC_END; } } if (c < 0) { char buf[256]; const char s1[] = "can't find heredoc delimiter \""; const char s2[] = "\" anywhere before EOF"; if (sizeof(s1)+sizeof(s2)+strlen(hinfo->term)+1 >= sizeof(buf)) { yyerror(NULL, p, "can't find heredoc delimiter anywhere before EOF"); } else { strcpy(buf, s1); strcat(buf, hinfo->term); strcat(buf, s2); yyerror(NULL, p, buf); } return 0; } pylval.nd = new_str_tok(p); if (unindent && head) { nspaces = push(nspaces, int_to_node(spaces)); heredoc_push_indented(p, hinfo, pylval.nd, escaped, nspaces, empty && line_head); } return tHD_STRING_MID; } if (unindent && empty) { if (c == '\t') spaces += 8; else if (ISSPACE(c)) spaces++; else empty = FALSE; } if (c < 0) { yyerror(NULL, p, "unterminated string meets end of file"); return 0; } else if (c == beg) { nest_level++; p->lex_strterm->level = nest_level; } else if (c == end) { nest_level--; p->lex_strterm->level = nest_level; } else if (c == '\\') { c = nextc(p); if (type & STR_FUNC_EXPAND) { if (c == end || c == beg) { tokadd(p, c); } else if (c == '\n') { p->lineno++; p->column = 0; if (unindent) { nspaces = push(nspaces, int_to_node(spaces)); escaped = push(escaped, int_to_node(pos)); pos--; empty = TRUE; spaces = 0; } if (type & STR_FUNC_ARRAY) { tokadd(p, '\n'); } } else if (type & STR_FUNC_REGEXP) { tokadd(p, '\\'); tokadd(p, c); } else if (c == 'u' && peek(p, '{')) { /* \u{xxxx xxxx xxxx} form */ nextc(p); while (1) { do c = nextc(p); while (ISSPACE(c)); if (c == '}') break; pushback(p, c); c = read_escape_unicode(p, 8); if (c < 0) break; tokadd(p, -c); } if (hinfo) hinfo->line_head = FALSE; } else { pushback(p, c); tokadd(p, read_escape(p)); if (hinfo) hinfo->line_head = FALSE; } } else { if (c != beg && c != end) { if (c == '\n') { p->lineno++; p->column = 0; } if (!(c == '\\' || ((type & STR_FUNC_ARRAY) && ISSPACE(c)))) { tokadd(p, '\\'); } } tokadd(p, c); } continue; } else if ((c == '#') && (type & STR_FUNC_EXPAND)) { c = nextc(p); if (c == '{') { tokfix(p); p->lstate = EXPR_BEG; p->cmd_start = TRUE; pylval.nd = new_str_tok(p); if (hinfo) { if (unindent && head) { nspaces = push(nspaces, int_to_node(spaces)); heredoc_push_indented(p, hinfo, pylval.nd, escaped, nspaces, FALSE); } hinfo->line_head = FALSE; return tHD_STRING_PART; } return tSTRING_PART; } tokadd(p, '#'); pushback(p, c); continue; } if ((type & STR_FUNC_ARRAY) && ISSPACE(c)) { if (toklen(p) == 0) { do { if (c == '\n') { p->lineno++; p->column = 0; heredoc_treat_nextline(p); if (p->parsing_heredoc != NULL) { return tHD_LITERAL_DELIM; } } c = nextc(p); } while (ISSPACE(c)); pushback(p, c); return tLITERAL_DELIM; } else { pushback(p, c); tokfix(p); pylval.nd = new_str_tok(p); return tSTRING_MID; } } if (c == '\n') { p->lineno++; p->column = 0; } tokadd(p, c); } tokfix(p); p->lstate = EXPR_END; end_strterm(p); if (type & STR_FUNC_XQUOTE) { pylval.nd = new_str_tok(p); return tXSTRING; } if (type & STR_FUNC_REGEXP) { int f = 0; int re_opt; int pattern_len = toklen(p); char *s = strndup(tok(p), pattern_len); char flags[3]; char *flag = flags; char enc = '\0'; char *encp; char *dup; newtok(p); while (re_opt = nextc(p), re_opt >= 0 && ISALPHA(re_opt)) { switch (re_opt) { case 'i': f |= 1; break; case 'x': f |= 2; break; case 'm': f |= 4; break; case 'u': f |= 16; break; case 'n': f |= 32; break; case 'o': break; default: tokadd(p, re_opt); break; } } pushback(p, re_opt); if (toklen(p)) { char msg[128]; strcpy(msg, "unknown regexp option"); tokfix(p); if (toklen(p) > 1) { strcat(msg, "s"); } strcat(msg, " - "); strncat(msg, tok(p), sizeof(msg) - strlen(msg) - 1); yyerror(NULL, p, msg); } if (f != 0) { if (f & 1) *flag++ = 'i'; if (f & 2) *flag++ = 'x'; if (f & 4) *flag++ = 'm'; if (f & 16) enc = 'u'; if (f & 32) enc = 'n'; } if (flag > flags) { dup = strndup(flags, (size_t)(flag - flags)); } else { dup = NULL; } if (enc) { encp = strndup(&enc, 1); } else { encp = NULL; } pylval.nd = cons(cons(int_to_node(pattern_len), (node*)s), cons((node*)dup, (node*)encp)); return tREGEXP; } pylval.nd = new_str_tok(p); return tSTRING; } static int number_literal_suffix(parser_state *p) { int c, result = 0; node *list = 0; int column = p->column; int mask = NUM_SUFFIX_R|NUM_SUFFIX_I; while ((c = nextc(p)) != -1) { list = push(list, int_to_node(c)); if ((mask & NUM_SUFFIX_I) && c == 'i') { result |= (mask & NUM_SUFFIX_I); mask &= ~NUM_SUFFIX_I; /* r after i, rational of complex is disallowed */ mask &= ~NUM_SUFFIX_R; continue; } if ((mask & NUM_SUFFIX_R) && c == 'r') { result |= (mask & NUM_SUFFIX_R); mask &= ~NUM_SUFFIX_R; continue; } if (!ISASCII(c) || ISALPHA(c) || c == '_') { p->column = column; if (p->pb) { p->pb = append(list, p->pb); } else { p->pb = list; } return 0; } pushback(p, c); break; } return result; } static int heredoc_identifier(parser_state *p) { int c; int type = str_heredoc; mrb_bool indent = FALSE; mrb_bool squiggly = FALSE; mrb_bool quote = FALSE; node *newnode; parser_heredoc_info *info; c = nextc(p); if (ISSPACE(c) || c == '=') { pushback(p, c); return 0; } if (c == '-' || c == '~') { if (c == '-') indent = TRUE; if (c == '~') squiggly = TRUE; c = nextc(p); } if (c == '\'' || c == '"') { int term = c; if (c == '\'') quote = TRUE; newtok(p); while ((c = nextc(p)) >= 0 && c != term) { if (c == '\n') { c = -1; break; } tokadd(p, c); } if (c < 0) { yyerror(NULL, p, "unterminated here document identifier"); return 0; } } else { if (c < 0) { return 0; /* missing here document identifier */ } if (! identchar(c)) { pushback(p, c); if (indent) pushback(p, '-'); if (squiggly) pushback(p, '~'); return 0; } newtok(p); do { tokadd(p, c); } while ((c = nextc(p)) >= 0 && identchar(c)); pushback(p, c); } tokfix(p); newnode = new_heredoc(p, &info); info->term = strndup(tok(p), toklen(p)); info->term_len = toklen(p); if (! quote) type |= STR_FUNC_EXPAND; info->type = (string_type)type; info->allow_indent = indent || squiggly; info->remove_indent = squiggly; info->indent = ~0U; info->indented = NULL; info->line_head = TRUE; info->doc = NULL; p->heredocs_from_nextline = push(p->heredocs_from_nextline, newnode); p->lstate = EXPR_END; pylval.nd = newnode; return tHEREDOC_BEG; } static int arg_ambiguous(parser_state *p) { yywarning(p, "ambiguous first argument; put parentheses or even spaces"); return 1; } #include "lex.def" static int parser_yylex(parser_state *p) { int32_t c; int nlines = 1; int space_seen = 0; int cmd_state; enum mrb_lex_state_enum last_state; int token_column; /* Early termination if too many errors - prevents DoS from malformed input */ if (p->nerr > 10) { return 0; /* EOF */ } if (p->lex_strterm) { if (is_strterm_type(p, STR_FUNC_HEREDOC)) { if (p->parsing_heredoc != NULL) return parse_string(p); } else return parse_string(p); } cmd_state = p->cmd_start; p->cmd_start = FALSE; retry: last_state = p->lstate; switch (c = nextc(p)) { case '\004': /* ^D */ case '\032': /* ^Z */ case '\0': /* NUL */ case -1: /* end of script. */ if (p->heredocs_from_nextline) goto maybe_heredoc; return 0; /* white spaces */ case ' ': case '\t': case '\f': case '\r': case '\13': /* '\v' */ space_seen = 1; goto retry; case '#': /* it's a comment */ skip(p, '\n'); /* fall through */ case -2: /* end of a file */ case '\n': maybe_heredoc: heredoc_treat_nextline(p); p->column = 0; switch (p->lstate) { case EXPR_BEG: case EXPR_FNAME: case EXPR_DOT: case EXPR_CLASS: case EXPR_VALUE: p->lineno++; if (p->parsing_heredoc != NULL) { if (p->lex_strterm) { return parse_string(p); } } goto retry; default: break; } if (p->parsing_heredoc != NULL) { pylval.num = nlines; return '\n'; } while ((c = nextc(p))) { switch (c) { case ' ': case '\t': case '\f': case '\r': case '\13': /* '\v' */ space_seen = 1; break; case '#': /* comment as a whitespace */ skip(p, '\n'); nlines++; break; case '.': if (!peek(p, '.')) { pushback(p, '.'); p->lineno+=nlines; nlines=1; goto retry; } pushback(p, c); goto normal_newline; case '&': if (peek(p, '.')) { pushback(p, '&'); p->lineno+=nlines; nlines=1; goto retry; } pushback(p, c); goto normal_newline; case -1: /* EOF */ case -2: /* end of a file */ goto normal_newline; default: pushback(p, c); goto normal_newline; } } normal_newline: p->cmd_start = TRUE; p->lstate = EXPR_BEG; pylval.num = nlines; return '\n'; case '*': if ((c = nextc(p)) == '*') { if ((c = nextc(p)) == '=') { pylval.id = intern_op(pow); p->lstate = EXPR_BEG; return tOP_ASGN; } pushback(p, c); if (IS_SPCARG(c)) { yywarning(p, "'**' interpreted as argument prefix"); c = tDSTAR; } else if (IS_BEG()) { c = tDSTAR; } else { c = tPOW; /* "**", "argument prefix" */ } } else { if (c == '=') { pylval.id = intern_op(mul); p->lstate = EXPR_BEG; return tOP_ASGN; } pushback(p, c); if (IS_SPCARG(c)) { yywarning(p, "'*' interpreted as argument prefix"); c = tSTAR; } else if (IS_BEG()) { c = tSTAR; } else { c = '*'; } } if (p->lstate == EXPR_FNAME || p->lstate == EXPR_DOT) { p->lstate = EXPR_ARG; } else { p->lstate = EXPR_BEG; } return c; case '!': c = nextc(p); if (p->lstate == EXPR_FNAME || p->lstate == EXPR_DOT) { p->lstate = EXPR_ARG; if (c == '@') { return '!'; } } else { p->lstate = EXPR_BEG; } if (c == '=') { return tNEQ; } if (c == '~') { return tNMATCH; } pushback(p, c); return '!'; case '=': if (p->column == 1) { static const char begin[] = "begin"; static const char end[] = "\n=end"; if (peeks(p, begin)) { c = peekc_n(p, sizeof(begin)-1); if (c < 0 || ISSPACE(c)) { do { if (!skips(p, end)) { yyerror(NULL, p, "embedded document meets end of file"); return 0; } c = nextc(p); } while (!(c < 0 || ISSPACE(c))); if (c != '\n') skip(p, '\n'); p->lineno+=nlines; nlines=1; p->column = 0; goto retry; } } } if (p->lstate == EXPR_FNAME || p->lstate == EXPR_DOT) { p->lstate = EXPR_ARG; } else { p->lstate = EXPR_BEG; } if ((c = nextc(p)) == '=') { if ((c = nextc(p)) == '=') { return tEQQ; } pushback(p, c); return tEQ; } if (c == '~') { return tMATCH; } else if (c == '>') { return tASSOC; } pushback(p, c); return '='; case '<': c = nextc(p); if (c == '<' && p->lstate != EXPR_DOT && p->lstate != EXPR_CLASS && !IS_END() && (!IS_ARG() || space_seen)) { int token = heredoc_identifier(p); if (token) return token; } if (p->lstate == EXPR_FNAME || p->lstate == EXPR_DOT) { p->lstate = EXPR_ARG; } else { p->lstate = EXPR_BEG; if (p->lstate == EXPR_CLASS) { p->cmd_start = TRUE; } } if (c == '=') { if ((c = nextc(p)) == '>') { return tCMP; } pushback(p, c); return tLEQ; } if (c == '<') { if ((c = nextc(p)) == '=') { pylval.id = intern_op(lshift); p->lstate = EXPR_BEG; return tOP_ASGN; } pushback(p, c); return tLSHFT; } pushback(p, c); return '<'; case '>': if (p->lstate == EXPR_FNAME || p->lstate == EXPR_DOT) { p->lstate = EXPR_ARG; } else { p->lstate = EXPR_BEG; } if ((c = nextc(p)) == '=') { return tGEQ; } if (c == '>') { if ((c = nextc(p)) == '=') { pylval.id = intern_op(rshift); p->lstate = EXPR_BEG; return tOP_ASGN; } pushback(p, c); return tRSHFT; } pushback(p, c); return '>'; case '"': p->lex_strterm = new_strterm(p, str_dquote, '"', 0); return tSTRING_BEG; case '\'': p->lex_strterm = new_strterm(p, str_squote, '\'', 0); return parse_string(p); case '`': if (p->lstate == EXPR_FNAME) { p->lstate = EXPR_ENDFN; return '`'; } if (p->lstate == EXPR_DOT) { if (cmd_state) p->lstate = EXPR_CMDARG; else p->lstate = EXPR_ARG; return '`'; } p->lex_strterm = new_strterm(p, str_xquote, '`', 0); return tXSTRING_BEG; case '?': if (IS_END()) { p->lstate = EXPR_VALUE; return '?'; } c = nextc(p); if (c < 0) { yyerror(NULL, p, "incomplete character syntax"); return 0; } if (ISSPACE(c)) { if (!IS_ARG()) { int c2; switch (c) { case ' ': c2 = 's'; break; case '\n': c2 = 'n'; break; case '\t': c2 = 't'; break; case '\v': c2 = 'v'; break; case '\r': c2 = 'r'; break; case '\f': c2 = 'f'; break; default: c2 = 0; break; } if (c2) { char buf[256]; char cc[] = { (char)c2, '\0' }; strcpy(buf, "invalid character syntax; use ?\\"); strncat(buf, cc, 2); yyerror(NULL, p, buf); } } ternary: pushback(p, c); p->lstate = EXPR_VALUE; return '?'; } newtok(p); /* need support UTF-8 if configured */ if ((ISALNUM(c) || c == '_')) { int c2 = nextc(p); pushback(p, c2); if ((ISALNUM(c2) || c2 == '_')) { goto ternary; } } if (c == '\\') { c = read_escape(p); tokadd(p, c); } else { tokadd(p, c); } tokfix(p); pylval.nd = new_str_tok(p); p->lstate = EXPR_END; return tCHAR; case '&': if ((c = nextc(p)) == '&') { p->lstate = EXPR_BEG; if ((c = nextc(p)) == '=') { pylval.id = intern_op(andand); p->lstate = EXPR_BEG; return tOP_ASGN; } pushback(p, c); return tANDOP; } else if (c == '.') { p->lstate = EXPR_DOT; return tANDDOT; } else if (c == '=') { pylval.id = intern_op(and); p->lstate = EXPR_BEG; return tOP_ASGN; } pushback(p, c); if (IS_SPCARG(c)) { yywarning(p, "'&' interpreted as argument prefix"); c = tAMPER; } else if (IS_BEG()) { c = tAMPER; } else { c = '&'; } if (p->lstate == EXPR_FNAME || p->lstate == EXPR_DOT) { p->lstate = EXPR_ARG; } else { p->lstate = EXPR_BEG; } return c; case '|': if ((c = nextc(p)) == '|') { p->lstate = EXPR_BEG; if ((c = nextc(p)) == '=') { pylval.id = intern_op(oror); p->lstate = EXPR_BEG; return tOP_ASGN; } pushback(p, c); return tOROP; } if (c == '=') { pylval.id = intern_op(or); p->lstate = EXPR_BEG; return tOP_ASGN; } if (p->lstate == EXPR_FNAME || p->lstate == EXPR_DOT) { p->lstate = EXPR_ARG; } else { p->lstate = EXPR_BEG; } pushback(p, c); return '|'; case '+': c = nextc(p); if (p->lstate == EXPR_FNAME || p->lstate == EXPR_DOT) { p->lstate = EXPR_ARG; if (c == '@') { return tUPLUS; } pushback(p, c); return '+'; } if (c == '=') { pylval.id = intern_op(add); p->lstate = EXPR_BEG; return tOP_ASGN; } if (IS_BEG() || (IS_SPCARG(c) && arg_ambiguous(p))) { p->lstate = EXPR_BEG; pushback(p, c); if (c >= 0 && ISDIGIT(c)) { c = '+'; goto start_num; } return tUPLUS; } p->lstate = EXPR_BEG; pushback(p, c); return '+'; case '-': c = nextc(p); if (p->lstate == EXPR_FNAME || p->lstate == EXPR_DOT) { p->lstate = EXPR_ARG; if (c == '@') { return tUMINUS; } pushback(p, c); return '-'; } if (c == '=') { pylval.id = intern_op(sub); p->lstate = EXPR_BEG; return tOP_ASGN; } if (c == '>') { p->lstate = EXPR_ENDFN; return tLAMBDA; } if (IS_BEG() || (IS_SPCARG(c) && arg_ambiguous(p))) { p->lstate = EXPR_BEG; pushback(p, c); if (c >= 0 && ISDIGIT(c)) { return tUMINUS_NUM; } return tUMINUS; } p->lstate = EXPR_BEG; pushback(p, c); return '-'; case '.': { int is_beg = IS_BEG(); p->lstate = EXPR_MID; if ((c = nextc(p)) == '.') { if ((c = nextc(p)) == '.') { return is_beg ? tBDOT3 : tDOT3; } pushback(p, c); return is_beg ? tBDOT2 : tDOT2; } pushback(p, c); p->lstate = EXPR_BEG; if (c >= 0 && ISDIGIT(c)) { yyerror(NULL, p, "no . floating literal anymore; put 0 before dot"); } p->lstate = EXPR_DOT; return '.'; } 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, nondigit; int suffix = 0; is_float = seen_point = seen_e = nondigit = 0; p->lstate = EXPR_END; newtok(p); if (c == '-') { tokadd(p, c); c = nextc(p); } else if (c == '+') { c = nextc(p); } if (c == '0') { #define no_digits() do {yyerror(NULL, p,"numeric literal without digits"); return 0;} while (0) int start = toklen(p); c = nextc(p); if (c == 'x' || c == 'X') { /* hexadecimal */ c = nextc(p); if (c >= 0 && ISXDIGIT(c)) { do { if (c == '_') { if (nondigit) break; nondigit = c; continue; } if (!ISXDIGIT(c)) break; nondigit = 0; tokadd(p, tolower(c)); } while ((c = nextc(p)) >= 0); } pushback(p, c); tokfix(p); if (toklen(p) == start) { no_digits(); } else if (nondigit) goto trailing_uc; suffix = number_literal_suffix(p); pylval.nd = new_int(p, tok(p), 16, suffix); return tINTEGER; } if (c == 'b' || c == 'B') { /* binary */ c = nextc(p); if (c == '0' || c == '1') { do { if (c == '_') { if (nondigit) break; nondigit = c; continue; } if (c != '0' && c != '1') break; nondigit = 0; tokadd(p, c); } while ((c = nextc(p)) >= 0); } pushback(p, c); tokfix(p); if (toklen(p) == start) { no_digits(); } else if (nondigit) goto trailing_uc; suffix = number_literal_suffix(p); pylval.nd = new_int(p, tok(p), 2, suffix); return tINTEGER; } if (c == 'd' || c == 'D') { /* decimal */ c = nextc(p); if (c >= 0 && ISDIGIT(c)) { do { if (c == '_') { if (nondigit) break; nondigit = c; continue; } if (!ISDIGIT(c)) break; nondigit = 0; tokadd(p, c); } while ((c = nextc(p)) >= 0); } pushback(p, c); tokfix(p); if (toklen(p) == start) { no_digits(); } else if (nondigit) goto trailing_uc; suffix = number_literal_suffix(p); pylval.nd = new_int(p, tok(p), 10, suffix); return tINTEGER; } if (c == '_') { /* 0_0 */ goto octal_number; } if (c == 'o' || c == 'O') { /* prefixed octal */ c = nextc(p); if (c < 0 || c == '_' || !ISDIGIT(c)) { no_digits(); } } if (c >= '0' && c <= '7') { /* octal */ octal_number: do { if (c == '_') { if (nondigit) break; nondigit = c; continue; } if (c < '0' || c > '9') break; if (c > '7') goto invalid_octal; nondigit = 0; tokadd(p, c); } while ((c = nextc(p)) >= 0); if (toklen(p) > start) { pushback(p, c); tokfix(p); if (nondigit) goto trailing_uc; suffix = number_literal_suffix(p); pylval.nd = new_int(p, tok(p), 8, suffix); return tINTEGER; } if (nondigit) { pushback(p, c); goto trailing_uc; } } if (c > '7' && c <= '9') { invalid_octal: yyerror(NULL, p, "Invalid octal digit"); } else if (c == '.' || c == 'e' || c == 'E') { tokadd(p, '0'); } else { pushback(p, c); suffix = number_literal_suffix(p); pylval.nd = new_int(p, "0", 10, suffix); return tINTEGER; } } for (;;) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': nondigit = 0; tokadd(p, c); break; case '.': if (nondigit) goto trailing_uc; if (seen_point || seen_e) { goto decode_num; } else { int c0 = nextc(p); if (c0 < 0 || !ISDIGIT(c0)) { pushback(p, c0); goto decode_num; } c = c0; } tokadd(p, '.'); tokadd(p, c); is_float++; seen_point++; nondigit = 0; break; case 'e': case 'E': if (nondigit) { pushback(p, c); c = nondigit; goto decode_num; } if (seen_e) { goto decode_num; } tokadd(p, c); seen_e++; is_float++; nondigit = c; c = nextc(p); if (c != '-' && c != '+') continue; tokadd(p, c); nondigit = c; break; case '_': /* '_' in number just ignored */ if (nondigit) goto decode_num; nondigit = c; break; default: goto decode_num; } c = nextc(p); } decode_num: pushback(p, c); if (nondigit) { trailing_uc: yyerror_c(p, "trailing non digit in number: ", (char)nondigit); } tokfix(p); if (is_float) { #ifdef MRB_NO_FLOAT yywarning_s(p, "floating-point numbers are not supported", tok(p)); pylval.nd = new_int(p, "0", 10, 0); return tINTEGER; #else double d; if (!mrb_read_float(tok(p), NULL, &d)) { yywarning_s(p, "corrupted float value", tok(p)); } suffix = number_literal_suffix(p); if (seen_e && (suffix & NUM_SUFFIX_R)) { pushback(p, 'r'); suffix &= ~NUM_SUFFIX_R; } pylval.nd = new_float(p, tok(p), suffix); return tFLOAT; #endif } suffix = number_literal_suffix(p); pylval.nd = new_int(p, tok(p), 10, suffix); return tINTEGER; } case ')': case ']': p->paren_nest--; /* fall through */ case '}': COND_LEXPOP(); CMDARG_LEXPOP(); if (c == ')') p->lstate = EXPR_ENDFN; else p->lstate = EXPR_END; return c; case ':': c = nextc(p); if (c == ':') { if (IS_BEG() || p->lstate == EXPR_CLASS || IS_SPCARG(-1)) { p->lstate = EXPR_BEG; return tCOLON3; } p->lstate = EXPR_DOT; return tCOLON2; } if (!space_seen && IS_END()) { pushback(p, c); /* In pattern matching context, use EXPR_ARG so newlines are significant */ p->lstate = p->in_kwarg ? EXPR_ARG : EXPR_BEG; return tLABEL_TAG; } if (IS_END() || ISSPACE(c) || c == '#') { pushback(p, c); p->lstate = EXPR_BEG; return ':'; } pushback(p, c); p->lstate = EXPR_FNAME; return tSYMBEG; case '/': if (IS_BEG()) { p->lex_strterm = new_strterm(p, str_regexp, '/', 0); return tREGEXP_BEG; } if ((c = nextc(p)) == '=') { pylval.id = intern_op(div); p->lstate = EXPR_BEG; return tOP_ASGN; } pushback(p, c); if (IS_SPCARG(c)) { p->lex_strterm = new_strterm(p, str_regexp, '/', 0); return tREGEXP_BEG; } if (p->lstate == EXPR_FNAME || p->lstate == EXPR_DOT) { p->lstate = EXPR_ARG; } else { p->lstate = EXPR_BEG; } return '/'; case '^': if ((c = nextc(p)) == '=') { pylval.id = intern_op(xor); p->lstate = EXPR_BEG; return tOP_ASGN; } if (p->lstate == EXPR_FNAME || p->lstate == EXPR_DOT) { p->lstate = EXPR_ARG; } else { p->lstate = EXPR_BEG; } pushback(p, c); return '^'; case ';': p->lstate = EXPR_BEG; return ';'; case ',': p->lstate = EXPR_BEG; return ','; case '~': if (p->lstate == EXPR_FNAME || p->lstate == EXPR_DOT) { if ((c = nextc(p)) != '@') { pushback(p, c); } p->lstate = EXPR_ARG; } else { p->lstate = EXPR_BEG; } return '~'; case '(': if (IS_BEG()) { c = tLPAREN; } else if (IS_SPCARG(-1)) { c = tLPAREN_ARG; } else if (p->lstate == EXPR_END && space_seen) { c = tLPAREN_ARG; } p->paren_nest++; COND_PUSH(0); CMDARG_PUSH(0); p->lstate = EXPR_BEG; return c; case '[': p->paren_nest++; if (p->lstate == EXPR_FNAME || p->lstate == EXPR_DOT) { p->lstate = EXPR_ARG; p->paren_nest--; if ((c = nextc(p)) == ']') { if ((c = nextc(p)) == '=') { return tASET; } pushback(p, c); return tAREF; } pushback(p, c); return '['; } else if (IS_BEG()) { c = tLBRACK; } else if (IS_ARG() && space_seen) { c = tLBRACK; } p->lstate = EXPR_BEG; COND_PUSH(0); CMDARG_PUSH(0); return c; case '{': if (p->lpar_beg && p->lpar_beg == p->paren_nest) { p->lstate = EXPR_BEG; p->lpar_beg = 0; p->paren_nest--; COND_PUSH(0); CMDARG_PUSH(0); return tLAMBEG; } if (IS_ARG() || p->lstate == EXPR_END || p->lstate == EXPR_ENDFN) c = '{'; /* block (primary) */ else if (p->lstate == EXPR_ENDARG) c = tLBRACE_ARG; /* block (expr) */ else c = tLBRACE; /* hash */ COND_PUSH(0); CMDARG_PUSH(0); p->lstate = EXPR_BEG; return c; case '\\': c = nextc(p); if (c == '\n') { p->lineno+=nlines; nlines=1; p->column = 0; space_seen = 1; goto retry; /* skip \\n */ } pushback(p, c); return '\\'; case '%': if (IS_BEG()) { int term; int paren; c = nextc(p); quotation: if (c < 0 || !ISALNUM(c)) { term = c; c = 'Q'; } else { term = nextc(p); if (ISALNUM(term)) { yyerror(NULL, p, "unknown type of %string"); return 0; } } if (c < 0 || term < 0) { yyerror(NULL, p, "unterminated quoted string meets end of file"); return 0; } paren = term; if (term == '(') term = ')'; else if (term == '[') term = ']'; else if (term == '{') term = '}'; else if (term == '<') term = '>'; else paren = 0; switch (c) { case 'Q': p->lex_strterm = new_strterm(p, str_dquote, term, paren); return tSTRING_BEG; case 'q': p->lex_strterm = new_strterm(p, str_squote, term, paren); return parse_string(p); case 'W': p->lex_strterm = new_strterm(p, str_dword, term, paren); return tWORDS_BEG; case 'w': p->lex_strterm = new_strterm(p, str_sword, term, paren); return tWORDS_BEG; case 'x': p->lex_strterm = new_strterm(p, str_xquote, term, paren); return tXSTRING_BEG; case 'r': p->lex_strterm = new_strterm(p, str_regexp, term, paren); return tREGEXP_BEG; case 's': p->lex_strterm = new_strterm(p, str_ssym, term, paren); return tSYMBEG; case 'I': p->lex_strterm = new_strterm(p, str_dsymbols, term, paren); return tSYMBOLS_BEG; case 'i': p->lex_strterm = new_strterm(p, str_ssymbols, term, paren); return tSYMBOLS_BEG; default: yyerror(NULL, p, "unknown type of %string"); return 0; } } if ((c = nextc(p)) == '=') { pylval.id = intern_op(mod); p->lstate = EXPR_BEG; return tOP_ASGN; } if (IS_SPCARG(c)) { goto quotation; } if (p->lstate == EXPR_FNAME || p->lstate == EXPR_DOT) { p->lstate = EXPR_ARG; } else { p->lstate = EXPR_BEG; } pushback(p, c); return '%'; case '$': p->lstate = EXPR_END; token_column = newtok(p); c = nextc(p); if (c < 0) { yyerror(NULL, p, "incomplete global variable syntax"); return 0; } switch (c) { case '_': /* $_: last read line string */ c = nextc(p); if (c >= 0 && identchar(c)) { /* if there is more after _ it is a variable */ tokadd(p, '$'); tokadd(p, c); break; } pushback(p, c); c = '_'; /* fall through */ case '~': /* $~: match-data */ case '*': /* $*: argv */ case '$': /* $$: pid */ case '?': /* $?: last status */ case '!': /* $!: error string */ case '@': /* $@: error position */ case '/': /* $/: input record separator */ case '\\': /* $\: output record separator */ case ';': /* $;: field separator */ case ',': /* $,: output field separator */ case '.': /* $.: last read line number */ case '=': /* $=: ignorecase */ case ':': /* $:: load path */ case '<': /* $<: reading filename */ case '>': /* $>: default output handle */ case '\"': /* $": already loaded files */ tokadd(p, '$'); tokadd(p, c); tokfix(p); pylval.id = intern(tok(p), toklen(p)); return tGVAR; case '-': tokadd(p, '$'); tokadd(p, c); c = nextc(p); pushback(p, c); gvar: tokfix(p); pylval.id = intern(tok(p), toklen(p)); return tGVAR; case '&': /* $&: last match */ case '`': /* $`: string before last match */ case '\'': /* $': string after last match */ case '+': /* $+: string matches last pattern */ if (last_state == EXPR_FNAME) { tokadd(p, '$'); tokadd(p, c); goto gvar; } pylval.nd = new_back_ref(p, c); return tBACK_REF; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': do { tokadd(p, c); c = nextc(p); } while (c >= 0 && ISDIGIT(c)); pushback(p, c); if (last_state == EXPR_FNAME) goto gvar; tokfix(p); { mrb_int n; if (!mrb_read_int(tok(p), NULL, NULL, &n)) { yywarning(p, "capture group index too big; always nil"); return keyword_nil; } pylval.nd = new_nth_ref(p, (int)n); } return tNTH_REF; default: if (!identchar(c)) { pushback(p, c); return '$'; } /* fall through */ case '0': tokadd(p, '$'); } break; case '@': c = nextc(p); token_column = newtok(p); tokadd(p, '@'); if (c == '@') { tokadd(p, '@'); c = nextc(p); } if (c < 0) { if (p->tidx == 1) { yyerror(NULL, p, "incomplete instance variable syntax"); } else { yyerror(NULL, p, "incomplete class variable syntax"); } return 0; } else if (ISDIGIT(c)) { if (p->tidx == 1) { yyerror_c(p, "wrong instance variable name: @", c); } else { yyerror_c(p, "wrong class variable name: @@", c); } return 0; } if (!identchar(c)) { pushback(p, c); return '@'; } break; case '_': token_column = newtok(p); break; default: if (!identchar(c)) { char buf[36]; const char s[] = "Invalid char in expression: 0x"; const char hexdigits[] = "0123456789ABCDEF"; strcpy(buf, s); buf[sizeof(s)-1] = hexdigits[(c & 0xf0) >> 4]; buf[sizeof(s)] = hexdigits[(c & 0x0f)]; buf[sizeof(s)+1] = 0; yyerror(NULL, p, buf); goto retry; } token_column = newtok(p); break; } do { tokadd(p, c); c = nextc(p); if (c < 0) break; } while (identchar(c)); if (token_column == 0 && toklen(p) == 7 && (c < 0 || c == '\n') && strncmp(tok(p), "__END__", toklen(p)) == 0) return -1; switch (tok(p)[0]) { case '@': case '$': pushback(p, c); break; default: if ((c == '!' || c == '?') && !peek(p, '=')) { tokadd(p, c); } else { pushback(p, c); } } tokfix(p); { int result = 0; switch (tok(p)[0]) { case '$': p->lstate = EXPR_END; result = tGVAR; break; case '@': p->lstate = EXPR_END; if (tok(p)[1] == '@') result = tCVAR; else result = tIVAR; break; case '_': if (toklen(p) == 2 && ISDIGIT(tok(p)[1]) && p->nvars) { int n = tok(p)[1] - '0'; int nvar; if (n > 0) { nvar = node_to_int(p->nvars->car); if (nvar != -2) { /* numbered parameters never appear on toplevel */ pylval.num = n; p->lstate = EXPR_END; return tNUMPARAM; } } } /* fall through */ default: if (toklast(p) == '!' || toklast(p) == '?') { result = tFID; } else { if (p->lstate == EXPR_FNAME) { if ((c = nextc(p)) == '=' && !peek(p, '~') && !peek(p, '>') && (!peek(p, '=') || (peek_n(p, '>', 1)))) { result = tIDENTIFIER; tokadd(p, c); tokfix(p); } else { pushback(p, c); } if ((c = nextc(p)) == '=' && !peek(p, '~') && !peek(p, '>') && (!peek(p, '=') || (peek_n(p, '>', 1)))) { result = tIDENTIFIER; tokadd(p, c); tokfix(p); } else { pushback(p, c); } } if (result == 0 && ISUPPER(tok(p)[0])) { result = tCONSTANT; } else { result = tIDENTIFIER; } } if (IS_LABEL_POSSIBLE()) { if (IS_LABEL_SUFFIX(0)) { p->lstate = EXPR_END; tokfix(p); pylval.id = intern(tok(p), toklen(p)); return tIDENTIFIER; } } if (p->lstate != EXPR_DOT) { const struct kwtable *kw; /* See if it is a reserved word. */ kw = mrb_reserved_word(tok(p), toklen(p)); if (kw) { enum mrb_lex_state_enum state = p->lstate; pylval.num = p->lineno; p->lstate = kw->state; if (state == EXPR_FNAME) { pylval.id = intern_cstr(kw->name); return kw->id[0]; } if (p->lstate == EXPR_BEG) { p->cmd_start = TRUE; } if (kw->id[0] == keyword_do) { if (p->lpar_beg && p->lpar_beg == p->paren_nest) { p->lpar_beg = 0; p->paren_nest--; return keyword_do_LAMBDA; } if (COND_P()) return keyword_do_cond; if (CMDARG_P() && state != EXPR_CMDARG) return keyword_do_block; if (state == EXPR_ENDARG || state == EXPR_BEG) return keyword_do_block; return keyword_do; } if (kw->id[0] == keyword_in) { /* Set in_kwarg for pattern matching context */ p->in_kwarg++; } if (state == EXPR_BEG || state == EXPR_VALUE || state == EXPR_CLASS) return kw->id[0]; else { if (kw->id[0] != kw->id[1]) p->lstate = EXPR_BEG; return kw->id[1]; } } } if (IS_BEG() || p->lstate == EXPR_DOT || IS_ARG()) { if (cmd_state) { p->lstate = EXPR_CMDARG; } else { p->lstate = EXPR_ARG; } } else if (p->lstate == EXPR_FNAME) { p->lstate = EXPR_ENDFN; } else { p->lstate = EXPR_END; } } { mrb_sym ident = intern(tok(p), toklen(p)); pylval.id = ident; if (last_state != EXPR_DOT && ISLOWER(tok(p)[0]) && local_var_p(p, ident)) { p->lstate = EXPR_END; } } return result; } } static int yylex(void *lval, void *lp, parser_state *p) { p->ylval = lval; return parser_yylex(p); } static void parser_init_cxt(parser_state *p, mrb_ccontext *cxt) { if (!cxt) return; if (cxt->filename) mrb_parser_set_filename(p, cxt->filename); if (cxt->lineno) p->lineno = cxt->lineno; if (cxt->syms) { int i; p->locals = cons(0,0); for (i=0; islen; i++) { local_add_f(p, cxt->syms[i]); } } p->capture_errors = cxt->capture_errors; p->no_optimize = cxt->no_optimize; p->no_ext_ops = cxt->no_ext_ops; p->no_return_value = cxt->no_return_value; p->upper = cxt->upper; if (cxt->partial_hook) { p->cxt = cxt; } } static void parser_update_cxt(parser_state *p, mrb_ccontext *cxt) { node *n, *n0; int i = 0; if (!cxt) return; if (!p->tree) return; if (!node_type_p(p->tree, NODE_SCOPE)) return; /* Extract locals from variable-sized NODE_SCOPE */ struct mrb_ast_scope_node *scope = scope_node(p->tree); n0 = n = scope->locals; while (n) { i++; n = n->cdr; } cxt->syms = (mrb_sym*)mrbc_realloc(cxt->syms, i*sizeof(mrb_sym)); cxt->slen = i; for (i=0, n=n0; n; i++,n=n->cdr) { cxt->syms[i] = node_to_sym(n->car); } } static void dump_node(mrb_state *mrb, node *tree, int offset); MRB_API void mrb_parser_parse(parser_state *p, mrb_ccontext *c) { struct mrb_jmpbuf buf1; struct mrb_jmpbuf *prev = p->mrb->jmp; p->mrb->jmp = &buf1; MRB_TRY(p->mrb->jmp) { int n = 1; p->cmd_start = TRUE; p->in_def = p->in_single = 0; p->nerr = p->nwarn = 0; p->lex_strterm = NULL; parser_init_cxt(p, c); n = yyparse(p); if (n != 0 || p->nerr > 0) { p->tree = 0; p->mrb->jmp = prev; return; } parser_update_cxt(p, c); if (c && c->dump_result) { dump_node(p->mrb, p->tree, 0); } } MRB_CATCH(p->mrb->jmp) { p->nerr++; if (p->mrb->exc == NULL) { yyerror(NULL, p, "memory allocation error"); p->nerr++; p->tree = 0; } } MRB_END_EXC(p->jmp); p->mrb->jmp = prev; } MRB_API parser_state* mrb_parser_new(mrb_state *mrb) { mempool *pool; parser_state *p; static const parser_state parser_state_zero = { 0 }; pool = mempool_open(); if (!pool) return NULL; p = (parser_state*)mempool_alloc(pool, sizeof(parser_state)); if (!p) return NULL; *p = parser_state_zero; p->mrb = mrb; p->pool = pool; p->s = p->send = NULL; #ifndef MRB_NO_STDIO p->f = NULL; #endif p->cmd_start = TRUE; p->in_def = p->in_single = 0; p->capture_errors = FALSE; p->lineno = 1; p->column = 0; #if defined(PARSER_TEST) || defined(PARSER_DEBUG) yydebug = 1; #endif p->tsiz = MRB_PARSER_TOKBUF_SIZE; p->tokbuf = p->buf; p->lex_strterm = NULL; p->current_filename_index = -1; p->filename_table = NULL; p->filename_table_length = 0; return p; } MRB_API void mrb_parser_free(parser_state *p) { if (p->tokbuf != p->buf) { mrbc_free(p->tokbuf); } mempool_close(p->pool); } MRB_API mrb_ccontext* mrb_ccontext_new(mrb_state *mrb) { static const mrb_ccontext cc_zero = { 0 }; mrb_ccontext *cc = (mrb_ccontext*)mrbc_malloc(sizeof(mrb_ccontext)); *cc = cc_zero; return cc; } MRB_API void mrb_ccontext_free(mrb_state *mrb, mrb_ccontext *cxt) { mrbc_free(cxt->filename); mrbc_free(cxt->syms); mrbc_free(cxt); } MRB_API const char* mrb_ccontext_filename(mrb_state *mrb, mrb_ccontext *c, const char *s) { if (s) { size_t len = strlen(s); char *p = (char*)mrbc_malloc(len + 1); if (p == NULL) return NULL; memcpy(p, s, len + 1); if (c->filename) { mrbc_free(c->filename); } c->filename = p; } return c->filename; } MRB_API void mrb_ccontext_partial_hook(mrb_ccontext *c, int (*func)(struct mrb_parser_state*), void *data) { c->partial_hook = func; c->partial_data = data; } MRB_API void mrb_ccontext_cleanup_local_variables(mrb_ccontext *c) { if (c->syms) { mrbc_free(c->syms); c->syms = NULL; c->slen = 0; } c->keep_lv = FALSE; } MRB_API void mrb_parser_set_filename(struct mrb_parser_state *p, const char *f) { mrb_sym sym; uint16_t i; mrb_sym* new_table; sym = mrb_intern_cstr(p->mrb, f); p->filename_sym = sym; p->lineno = (p->filename_table_length > 0)? 0 : 1; for (i = 0; i < p->filename_table_length; i++) { if (p->filename_table[i] == sym) { p->current_filename_index = i; return; } } if (p->filename_table_length == UINT16_MAX) { yyerror(NULL, p, "too many files to compile"); return; } p->current_filename_index = p->filename_table_length++; new_table = (mrb_sym*)parser_palloc(p, sizeof(mrb_sym) * p->filename_table_length); if (p->filename_table) { memmove(new_table, p->filename_table, sizeof(mrb_sym) * p->current_filename_index); } p->filename_table = new_table; p->filename_table[p->filename_table_length - 1] = sym; } MRB_API mrb_sym mrb_parser_get_filename(struct mrb_parser_state* p, uint16_t idx) { if (idx >= p->filename_table_length) return 0; else { return p->filename_table[idx]; } } #ifndef MRB_NO_STDIO static struct mrb_parser_state * mrb_parse_file_continue(mrb_state *mrb, FILE *f, const void *prebuf, size_t prebufsize, mrb_ccontext *c) { parser_state *p; p = mrb_parser_new(mrb); if (!p) return NULL; if (prebuf) { p->s = (const char*)prebuf; p->send = (const char*)prebuf + prebufsize; } else { p->s = p->send = NULL; } p->f = f; mrb_parser_parse(p, c); return p; } MRB_API parser_state* mrb_parse_file(mrb_state *mrb, FILE *f, mrb_ccontext *c) { return mrb_parse_file_continue(mrb, f, NULL, 0, c); } #endif MRB_API parser_state* mrb_parse_nstring(mrb_state *mrb, const char *s, size_t len, mrb_ccontext *c) { parser_state *p; p = mrb_parser_new(mrb); if (!p) return NULL; p->s = s; p->send = s + len; mrb_parser_parse(p, c); return p; } MRB_API parser_state* mrb_parse_string(mrb_state *mrb, const char *s, mrb_ccontext *c) { return mrb_parse_nstring(mrb, s, strlen(s), c); } MRB_API mrb_value mrb_load_exec(mrb_state *mrb, struct mrb_parser_state *p, mrb_ccontext *c) { struct RClass *target = mrb->object_class; struct RProc *proc; mrb_value v; mrb_int keep = 0; if (!p) { return mrb_undef_value(); } if (!p->tree || p->nerr) { if (c) c->parser_nerr = p->nerr; if (p->capture_errors) { char buf[256]; strcpy(buf, "line "); dump_int(p->error_buffer[0].lineno, buf+5); strcat(buf, ": "); strncat(buf, p->error_buffer[0].message, sizeof(buf) - strlen(buf) - 1); mrb->exc = mrb_obj_ptr(mrb_exc_new(mrb, E_SYNTAX_ERROR, buf, strlen(buf))); mrb_parser_free(p); return mrb_undef_value(); } else { if (mrb->exc == NULL) { mrb->exc = mrb_obj_ptr(mrb_exc_new_lit(mrb, E_SYNTAX_ERROR, "syntax error")); } mrb_parser_free(p); return mrb_undef_value(); } } proc = mrb_generate_code(mrb, p); mrb_parser_free(p); if (proc == NULL) { if (mrb->exc == NULL) { mrb->exc = mrb_obj_ptr(mrb_exc_new_lit(mrb, E_SCRIPT_ERROR, "codegen error")); } return mrb_undef_value(); } if (c) { if (c->dump_result) mrb_codedump_all(mrb, proc); if (c->no_exec) return mrb_obj_value(proc); if (c->target_class) { target = c->target_class; } if (c->keep_lv) { keep = c->slen + 1; } else { c->keep_lv = TRUE; } } MRB_PROC_SET_TARGET_CLASS(proc, target); if (mrb->c->ci) { mrb_vm_ci_target_class_set(mrb->c->ci, target); } v = mrb_top_run(mrb, proc, mrb_top_self(mrb), keep); if (mrb->exc) return mrb_nil_value(); return v; } #ifndef MRB_NO_STDIO MRB_API mrb_value mrb_load_file_cxt(mrb_state *mrb, FILE *f, mrb_ccontext *c) { return mrb_load_exec(mrb, mrb_parse_file(mrb, f, c), c); } MRB_API mrb_value mrb_load_file(mrb_state *mrb, FILE *f) { return mrb_load_file_cxt(mrb, f, NULL); } #define DETECT_SIZE 64 /* * In order to be recognized as a `.mrb` file, the following three points must be satisfied: * - File starts with "RITE" * - At least `sizeof(struct rite_binary_header)` bytes can be read * - `NUL` is included in the first 64 bytes of the file */ MRB_API mrb_value mrb_load_detect_file_cxt(mrb_state *mrb, FILE *fp, mrb_ccontext *c) { union { char b[DETECT_SIZE]; struct rite_binary_header h; } leading; size_t bufsize; if (mrb == NULL || fp == NULL) { return mrb_nil_value(); } bufsize = fread(leading.b, sizeof(char), sizeof(leading), fp); if (bufsize < sizeof(leading.h) || memcmp(leading.h.binary_ident, RITE_BINARY_IDENT, sizeof(leading.h.binary_ident)) != 0 || memchr(leading.b, '\0', bufsize) == NULL) { return mrb_load_exec(mrb, mrb_parse_file_continue(mrb, fp, leading.b, bufsize, c), c); } else { mrb_int binsize = bin_to_uint32(leading.h.binary_size); mrb_value bin_obj = mrb_str_new(mrb, NULL, binsize); uint8_t *bin = (uint8_t*)RSTRING_PTR(bin_obj); if ((size_t)binsize > bufsize) { memcpy(bin, leading.b, bufsize); if (fread(bin + bufsize, binsize - bufsize, 1, fp) == 0) { binsize = bufsize; /* The error is reported by mrb_load_irep_buf_cxt() */ } } mrb_value result = mrb_load_irep_buf_cxt(mrb, bin, binsize, c); if (mrb_string_p(bin_obj)) mrb_str_resize(mrb, bin_obj, 0); return result; } } #endif MRB_API mrb_value mrb_load_nstring_cxt(mrb_state *mrb, const char *s, size_t len, mrb_ccontext *c) { return mrb_load_exec(mrb, mrb_parse_nstring(mrb, s, len, c), c); } MRB_API mrb_value mrb_load_nstring(mrb_state *mrb, const char *s, size_t len) { return mrb_load_nstring_cxt(mrb, s, len, NULL); } MRB_API mrb_value mrb_load_string_cxt(mrb_state *mrb, const char *s, mrb_ccontext *c) { return mrb_load_nstring_cxt(mrb, s, strlen(s), c); } MRB_API mrb_value mrb_load_string(mrb_state *mrb, const char *s) { return mrb_load_string_cxt(mrb, s, NULL); } #ifndef MRB_NO_STDIO static void dump_prefix(int offset, uint16_t lineno) { printf("%05d ", lineno); while (offset--) { putc(' ', stdout); putc(' ', stdout); } } static void dump_recur(mrb_state *mrb, node *tree, int offset) { while (tree) { dump_node(mrb, tree->car, offset); tree = tree->cdr; } } static void dump_locals(mrb_state *mrb, node *tree, int offset, uint16_t lineno) { if (!tree || (!tree->car && !tree->cdr)) return; dump_prefix(offset, lineno); printf("locals:\n"); dump_prefix(offset+1, lineno); while (tree) { if (tree->car) { mrb_sym sym = node_to_sym(tree->car); if (sym != 0) { const char *name = mrb_sym_name(mrb, sym); if (name && strlen(name) > 0 && name[0] != '!' && name[0] != '@' && name[0] != '$') { printf(" %s", mrb_sym_dump(mrb, sym)); } else { printf(" (invalid symbol: %s)", name ? name : "(null)"); } } else { printf(" (anonymous)"); } } tree = tree->cdr; } printf("\n"); } static void dump_cpath(mrb_state *mrb, node *tree, int offset, uint16_t lineno) { dump_prefix(offset, lineno); printf("cpath: "); if (!tree) { printf("(null)\n"); } else if (node_to_int(tree->car) == 0) { printf("(null)\n"); } else if (node_to_int(tree->car) == 1) { printf("Object\n"); } else { printf("\n"); dump_node(mrb, tree->car, offset+1); } dump_prefix(offset, lineno); printf("name: %s\n", mrb_sym_dump(mrb, node_to_sym(tree->cdr))); } /* * This function restores the GC arena on return. * For this reason, if a process that further generates an object is * performed at the caller, the string pointer returned as the return * value may become invalid. */ static const char* str_dump(mrb_state *mrb, const char *str, int len) { int ai = mrb_gc_arena_save(mrb); mrb_value s = mrb_str_new(mrb, str, (mrb_int)len); s = mrb_str_dump(mrb, s); mrb_gc_arena_restore(mrb, ai); return RSTRING_PTR(s); } static void dump_str(mrb_state *mrb, node *n, int offset, uint16_t lineno) { while (n) { dump_prefix(offset, lineno); int len = node_to_int(n->car->car); if (len >= 0) { printf("str: %s\n", str_dump(mrb, (char*)n->car->cdr, len)); } else { printf("interpolation:\n"); dump_node(mrb, n->car->cdr, offset+1); } n = n->cdr; } } static void dump_args(mrb_state *mrb, struct mrb_ast_args *args, int offset, uint16_t lineno) { if (args->mandatory_args) { dump_prefix(offset, lineno); printf("mandatory args:\n"); dump_recur(mrb, args->mandatory_args, offset+1); } if (args->optional_args) { dump_prefix(offset, lineno); printf("optional args:\n"); { node *n = args->optional_args; while (n) { dump_prefix(offset+1, lineno); printf("%s=\n", mrb_sym_name(mrb, node_to_sym(n->car->car))); dump_node(mrb, n->car->cdr, offset+2); n = n->cdr; } } } if (args->rest_arg) { mrb_sym rest = args->rest_arg; dump_prefix(offset, lineno); if (rest == MRB_OPSYM(mul)) printf("rest=*\n"); else printf("rest=*%s\n", mrb_sym_name(mrb, rest)); } if (args->post_mandatory_args) { dump_prefix(offset, lineno); printf("post mandatory args:\n"); dump_recur(mrb, args->post_mandatory_args, offset+1); } if (args->keyword_args) { dump_prefix(offset, lineno); printf("keyword args:\n"); { node *n = args->keyword_args; while (n) { dump_prefix(offset+1, lineno); printf("%s:\n", mrb_sym_name(mrb, node_to_sym(n->car->car))); dump_node(mrb, n->car->cdr, offset+2); n = n->cdr; } } } if (args->kwrest_arg) { mrb_sym rest = args->kwrest_arg; dump_prefix(offset, lineno); if (rest == MRB_OPSYM(pow)) printf("kwrest=**\n"); else printf("kwrest=**%s\n", mrb_sym_name(mrb, rest)); } if (args->block_arg) { mrb_sym blk = args->block_arg; dump_prefix(offset, lineno); if (blk == MRB_OPSYM(and)) printf("blk=&\n"); else if (blk == MRB_SYM(nil)) printf("blk=&nil\n"); else printf("blk=&%s\n", mrb_sym_name(mrb, blk)); } } static void dump_callargs(mrb_state *mrb, node *n, int offset, uint16_t lineno) { if (!n) return; struct mrb_ast_callargs *args = (struct mrb_ast_callargs*)n; if (args->regular_args) { dump_prefix(offset+1, lineno); printf("args:\n"); dump_recur(mrb, args->regular_args, offset+2); } if (args->keyword_args) { dump_prefix(offset+1, lineno); printf("kw_args:\n"); node *kw = args->keyword_args; while (kw) { dump_prefix(offset+2, lineno); printf("key:\n"); if (node_to_sym(kw->car->car) == MRB_OPSYM(pow)) { dump_prefix(offset+3, lineno); printf("**:\n"); } else { dump_node(mrb, kw->car->car, offset+3); } dump_prefix(offset+2, lineno); printf("value:\n"); dump_node(mrb, kw->car->cdr, offset+3); kw = kw->cdr; } } if (args->block_arg) { dump_prefix(offset+1, lineno); printf("block:\n"); dump_node(mrb, args->block_arg, offset+2); } } #endif void dump_node(mrb_state *mrb, node *tree, int offset) { #ifndef MRB_NO_STDIO enum node_type nodetype; uint16_t lineno = 0; if (!tree) return; /* Extract line number from variable-sized node header */ if (node_type(tree) != NODE_LAST) { lineno = ((struct mrb_ast_var_header*)tree)->lineno; } dump_prefix(offset, lineno); /* All nodes are now variable-sized nodes with headers */ nodetype = node_type(tree); switch (nodetype) { /* Variable-sized node cases */ case NODE_SCOPE: printf("NODE_SCOPE:\n"); if (scope_node(tree)->locals) { dump_locals(mrb, scope_node(tree)->locals, offset+1, lineno); } if (scope_node(tree)->body) { dump_prefix(offset+1, lineno); printf("body:\n"); dump_node(mrb, scope_node(tree)->body, offset+2); } break; case NODE_INT: printf("NODE_INT: %d\n", int_node(tree)->value); break; case NODE_BIGINT: printf("NODE_BIGINT: %s (base %d)\n", bigint_node(tree)->string, bigint_node(tree)->base); break; case NODE_FLOAT: printf("NODE_FLOAT: %s\n", float_node(tree)->value); break; case NODE_STR: printf("NODE_STR:\n"); dump_str(mrb, str_node(tree)->list, offset+1, lineno); break; case NODE_XSTR: printf("NODE_XSTR:\n"); dump_str(mrb, xstr_node(tree)->list, offset+1, lineno); break; case NODE_SYM: printf("NODE_SYM: %s\n", mrb_sym_dump(mrb, sym_node(tree)->symbol)); break; case NODE_DSYM: printf("NODE_DSYM:\n"); dump_str(mrb, str_node(tree)->list, offset+1, lineno); break; case NODE_LVAR: printf("NODE_LVAR: %s\n", mrb_sym_dump(mrb, var_node(tree)->symbol)); break; case NODE_GVAR: printf("NODE_GVAR: %s\n", mrb_sym_dump(mrb, var_node(tree)->symbol)); break; case NODE_IVAR: printf("NODE_IVAR: %s\n", mrb_sym_dump(mrb, var_node(tree)->symbol)); break; case NODE_CVAR: printf("NODE_CVAR: %s\n", mrb_sym_dump(mrb, var_node(tree)->symbol)); break; case NODE_NVAR: printf("NODE_NVAR: %d\n", nvar_node(tree)->num); break; case NODE_CONST: printf("NODE_CONST: %s\n", mrb_sym_dump(mrb, var_node(tree)->symbol)); break; case NODE_CALL: printf("NODE_CALL: %s\n", mrb_sym_dump(mrb, call_node(tree)->method_name)); if (call_node(tree)->receiver) { dump_prefix(offset+1, lineno); printf("receiver:\n"); dump_node(mrb, call_node(tree)->receiver, offset+2); } if (call_node(tree)->args) { dump_callargs(mrb, call_node(tree)->args, offset, lineno); } break; case NODE_ARRAY: printf("NODE_ARRAY:\n"); if (array_node(tree)->elements) { dump_recur(mrb, array_node(tree)->elements, offset+1); } break; case NODE_TRUE: printf("NODE_TRUE\n"); break; case NODE_FALSE: printf("NODE_FALSE\n"); break; case NODE_NIL: printf("NODE_NIL\n"); break; case NODE_SELF: printf("NODE_SELF\n"); break; case NODE_IF: printf("NODE_IF:\n"); if (if_node(tree)->condition) { dump_prefix(offset+1, lineno); printf("cond:\n"); dump_node(mrb, if_node(tree)->condition, offset+2); } if (if_node(tree)->then_body) { dump_prefix(offset+1, lineno); printf("then:\n"); dump_node(mrb, if_node(tree)->then_body, offset+2); } if (if_node(tree)->else_body) { dump_prefix(offset+1, lineno); printf("else:\n"); dump_node(mrb, if_node(tree)->else_body, offset+2); } break; case NODE_DEF: printf("NODE_DEF: %s\n", mrb_sym_dump(mrb, def_node(tree)->name)); if (def_node(tree)->args) { dump_args(mrb, sdef_node(tree)->args, offset+1, lineno); } if (def_node(tree)->locals) { dump_locals(mrb, def_node(tree)->locals, offset+1, lineno); } if (def_node(tree)->body) { dump_prefix(offset+1, lineno); printf("body:\n"); dump_node(mrb, def_node(tree)->body, offset+2); } break; case NODE_ASGN: printf("NODE_ASGN:\n"); if (asgn_node(tree)->lhs) { dump_prefix(offset+1, lineno); printf("lhs:\n"); dump_node(mrb, asgn_node(tree)->lhs, offset+2); } if (asgn_node(tree)->rhs) { dump_prefix(offset+1, lineno); printf("rhs:\n"); dump_node(mrb, asgn_node(tree)->rhs, offset+2); } break; case NODE_MASGN: case NODE_MARG: printf("%s:\n", node_type(tree) == NODE_MASGN ? "NODE_MASGN" : "NODE_MARG"); /* Handle pre-splat variables */ if (masgn_node(tree)->pre) { dump_prefix(offset+1, lineno); printf("pre:\n"); dump_recur(mrb, masgn_node(tree)->pre, offset+2); } /* Handle splat variable (can be -1 sentinel for anonymous splat) */ if (masgn_node(tree)->rest) { if ((intptr_t)masgn_node(tree)->rest == -1) { dump_prefix(offset+1, lineno); printf("rest: *\n"); } else { dump_prefix(offset+1, lineno); printf("rest:\n"); dump_node(mrb, masgn_node(tree)->rest, offset+2); } } /* Handle post-splat variables */ if (masgn_node(tree)->post) { dump_prefix(offset+1, lineno); printf("post:\n"); dump_recur(mrb, masgn_node(tree)->post, offset+2); } if (masgn_node(tree)->rhs) { dump_prefix(offset+1, lineno); printf("rhs:\n"); dump_node(mrb, masgn_node(tree)->rhs, offset+2); } break; case NODE_RETURN: printf("NODE_RETURN:\n"); if (return_node(tree)->args) { dump_node(mrb, return_node(tree)->args, offset); } break; case NODE_BREAK: printf("NODE_BREAK:\n"); if (break_node(tree)->value) { dump_prefix(offset+1, lineno); printf("value:\n"); dump_node(mrb, break_node(tree)->value, offset+2); } break; case NODE_NEXT: printf("NODE_NEXT:\n"); if (next_node(tree)->value) { dump_prefix(offset+1, lineno); printf("value:\n"); dump_node(mrb, next_node(tree)->value, offset+2); } break; case NODE_NEGATE: printf("NODE_NEGATE:\n"); if (negate_node(tree)->operand) { dump_prefix(offset+1, lineno); printf("operand:\n"); dump_node(mrb, negate_node(tree)->operand, offset+2); } break; case NODE_STMTS: printf("NODE_STMTS:\n"); if (stmts_node(tree)->stmts) { dump_recur(mrb, stmts_node(tree)->stmts, offset+1); } break; case NODE_BEGIN: printf("NODE_BEGIN:\n"); if (begin_node(tree)->body) { dump_node(mrb, begin_node(tree)->body, offset+1); } break; case NODE_RESCUE: printf("NODE_RESCUE:\n"); if (rescue_node(tree)->body) { dump_prefix(offset+1, lineno); printf("body:\n"); dump_node(mrb, rescue_node(tree)->body, offset+2); } if (rescue_node(tree)->rescue_clauses) { node *n2 = rescue_node(tree)->rescue_clauses; dump_prefix(offset+1, lineno); printf("rescue:\n"); while (n2) { node *n3 = n2->car; if (n3->car) { dump_prefix(offset+2, lineno); printf("handle classes:\n"); dump_recur(mrb, n3->car, offset+3); } if (n3->cdr->car) { dump_prefix(offset+2, lineno); printf("exc_var:\n"); dump_node(mrb, n3->cdr->car, offset+3); } if (n3->cdr->cdr->car) { dump_prefix(offset+2, lineno); printf("rescue body:\n"); dump_node(mrb, n3->cdr->cdr->car, offset+3); } n2 = n2->cdr; } } if (rescue_node(tree)->else_clause) { dump_prefix(offset+1, lineno); printf("else:\n"); dump_node(mrb, rescue_node(tree)->else_clause, offset+2); } break; case NODE_ENSURE: printf("NODE_ENSURE:\n"); if (ensure_node(tree)->body) { dump_prefix(offset+1, lineno); printf("body:\n"); dump_node(mrb, ensure_node(tree)->body, offset+2); } if (ensure_node(tree)->ensure_clause) { dump_prefix(offset+1, lineno); printf("ensure:\n"); dump_node(mrb, ensure_node(tree)->ensure_clause, offset+2); } break; case NODE_LAMBDA: printf("NODE_LAMBDA:\n"); goto block; case NODE_BLOCK: printf("NODE_BLOCK:\n"); block: if (block_node(tree)->locals) { dump_locals(mrb, block_node(tree)->locals, offset+1, lineno); } if (block_node(tree)->args) { dump_args(mrb, block_node(tree)->args, offset+1, lineno); } dump_prefix(offset+1, lineno); printf("body:\n"); dump_node(mrb, block_node(tree)->body, offset+2); break; case NODE_AND: printf("NODE_AND:\n"); dump_node(mrb, and_node(tree)->left, offset+1); dump_node(mrb, and_node(tree)->right, offset+1); break; case NODE_OR: printf("NODE_OR:\n"); dump_node(mrb, or_node(tree)->left, offset+1); dump_node(mrb, or_node(tree)->right, offset+1); break; case NODE_CASE: printf("NODE_CASE:\n"); if (case_node(tree)->value) { dump_prefix(offset+1, lineno); printf("value:\n"); dump_node(mrb, case_node(tree)->value, offset+2); } if (case_node(tree)->body) { node *when_node = case_node(tree)->body; while (when_node) { dump_prefix(offset+1, lineno); printf("when:\n"); node *when_clause = when_node->car; if (when_clause && when_clause->car) { dump_prefix(offset+2, lineno); printf("cond:\n"); dump_recur(mrb, when_clause->car, offset+3); } if (when_clause && when_clause->cdr) { dump_prefix(offset+2, lineno); printf("body:\n"); dump_node(mrb, when_clause->cdr, offset+3); } when_node = when_node->cdr; } } break; case NODE_WHILE: printf("NODE_WHILE:\n"); goto dump_loop_node; case NODE_UNTIL: printf("NODE_UNTIL:\n"); goto dump_loop_node; case NODE_WHILE_MOD: printf("NODE_WHILE_MOD:\n"); goto dump_loop_node; case NODE_UNTIL_MOD: printf("NODE_UNTIL_MOD:\n"); dump_loop_node: dump_prefix(offset+1, lineno); printf("cond:\n"); dump_node(mrb, while_node(tree)->condition, offset+2); dump_prefix(offset+1, lineno); printf("body:\n"); dump_node(mrb, while_node(tree)->body, offset+2); break; case NODE_FOR: printf("NODE_FOR:\n"); if (for_node(tree)->var) { dump_prefix(offset+1, lineno); printf("var:\n"); /* FOR_NODE_VAR structure: * var_list->car: cons-list of pre-splat variables * var_list->cdr->car: splat varnode (not a cons-list) * var_list->cdr->cdr->car: cons-list of post-splat variables */ node *var_list = for_node(tree)->var; if (var_list) { dump_recur(mrb, var_list->car, offset+2); if (var_list && var_list->cdr) { /* Second element is a varnode, not a cons-list */ dump_prefix(offset+1, lineno); printf("splat var:\n"); dump_node(mrb, var_list->cdr->car, offset+2); if (var_list->cdr->cdr) { /* Third element is a cons-list of post-splat variables */ dump_prefix(offset+1, lineno); printf("post var:\n"); dump_recur(mrb, var_list->cdr->cdr->car, offset+2); } } } } if (for_node(tree)->iterable) { dump_prefix(offset+1, lineno); printf("iterable:\n"); dump_node(mrb, for_node(tree)->iterable, offset+2); } if (for_node(tree)->body) { dump_prefix(offset+1, lineno); printf("body:\n"); dump_node(mrb, for_node(tree)->body, offset+2); } break; case NODE_DOT2: printf("NODE_DOT2:\n"); { if (dot2_node(tree)->left) { dump_prefix(offset+1, lineno); printf("left:\n"); dump_node(mrb, dot2_node(tree)->left, offset+2); } if (dot2_node(tree)->right) { dump_prefix(offset+1, lineno); printf("right:\n"); dump_node(mrb, dot2_node(tree)->right, offset+2); } } break; case NODE_DOT3: printf("NODE_DOT3:\n"); { if (dot3_node(tree)->left) { dump_prefix(offset+1, lineno); printf("left:\n"); dump_node(mrb, dot3_node(tree)->left, offset+2); } if (dot3_node(tree)->right) { dump_prefix(offset+1, lineno); printf("right:\n"); dump_node(mrb, dot3_node(tree)->right, offset+2); } } break; case NODE_COLON2: printf("NODE_COLON2:\n"); if (colon2_node(tree)->base) { dump_prefix(offset+1, lineno); printf("base:\n"); dump_node(mrb, colon2_node(tree)->base, offset+2); } dump_prefix(offset+1, lineno); printf("name: %s\n", mrb_sym_name(mrb, colon2_node(tree)->name)); break; case NODE_COLON3: printf("NODE_COLON3: ::%s\n", mrb_sym_name(mrb, colon3_node(tree)->name)); break; case NODE_HASH: printf("NODE_HASH:\n"); { node *pairs = hash_node(tree)->pairs; while (pairs) { dump_prefix(offset+1, lineno); printf("key:\n"); if (node_to_sym(pairs->car->car) == MRB_OPSYM(pow)) { dump_prefix(offset+2, lineno); printf("**\n"); } else { dump_node(mrb, pairs->car->car, offset+2); } dump_prefix(offset+1, lineno); printf("value:\n"); dump_node(mrb, pairs->car->cdr, offset+2); pairs = pairs->cdr; } } break; case NODE_SPLAT: printf("NODE_SPLAT:\n"); dump_node(mrb, splat_node(tree)->value, offset+1); break; case NODE_OP_ASGN: printf("NODE_OP_ASGN:\n"); dump_prefix(offset+1, lineno); printf("lhs:\n"); dump_node(mrb, op_asgn_node(tree)->lhs, offset+2); dump_prefix(offset+1, lineno); printf("op='%s' (%d)\n", mrb_sym_name(mrb, op_asgn_node(tree)->op), (int)op_asgn_node(tree)->op); dump_node(mrb, op_asgn_node(tree)->rhs, offset+1); break; case NODE_SUPER: printf("NODE_SUPER:\n"); if (super_node(tree)->args) { dump_callargs(mrb, super_node(tree)->args, offset, lineno); } break; case NODE_ZSUPER: printf("NODE_ZSUPER:\n"); if (super_node(tree)->args) { dump_callargs(mrb, super_node(tree)->args, offset, lineno); } break; case NODE_YIELD: printf("NODE_YIELD:\n"); if (yield_node(tree)->args) { dump_callargs(mrb, yield_node(tree)->args, offset, lineno); } break; case NODE_REDO: printf("NODE_REDO\n"); break; case NODE_RETRY: printf("NODE_RETRY\n"); break; case NODE_BACK_REF: printf("NODE_BACK_REF: $%c\n", node_to_int(tree)); break; case NODE_NTH_REF: printf("NODE_NTH_REF: $%d\n", node_to_int(tree)); break; case NODE_BLOCK_ARG: printf("NODE_BLOCK_ARG:\n"); dump_node(mrb, block_arg_node(tree)->value, offset+1); break; case NODE_REGX: printf("NODE_REGX:\n"); if (regx_node(tree)->list) { dump_str(mrb, regx_node(tree)->list, offset+1, lineno); } if (regx_node(tree)->flags) { dump_prefix(offset+1, lineno); printf("flags: %s\n", regx_node(tree)->flags); } if (regx_node(tree)->encoding) { dump_prefix(offset+1, lineno); printf("encoding: %s\n", regx_node(tree)->encoding); } break; case NODE_WORDS: printf("NODE_WORDS:\n"); if (words_node(tree)->args) { node *list = words_node(tree)->args; while (list && list->car) { node *item = list->car; if (item->car == 0 && item->cdr == 0) { /* Skip separator (0 . 0) */ } else if (item->car && item->cdr) { /* String item: (len . str) */ dump_prefix(offset+1, lineno); int len = node_to_int(item->car); if (len >= 0 && len < 1000 && item->cdr) { printf("word: \"%.*s\"\n", len, (char*)item->cdr); } } list = list->cdr; } } break; case NODE_SYMBOLS: printf("NODE_SYMBOLS:\n"); if (symbols_node(tree)->args) { node *list = symbols_node(tree)->args; while (list && list->car) { node *item = list->car; if (item->car == 0 && item->cdr == 0) { /* Skip separator (0 . 0) */ } else if (item->car && item->cdr) { /* String item: (len . str) */ dump_prefix(offset+1, lineno); int len = node_to_int(item->car); if (len >= 0 && len < 1000 && item->cdr) { printf("symbol: \"%.*s\"\n", len, (char*)item->cdr); } } list = list->cdr; } } break; case NODE_ALIAS: printf("NODE_ALIAS %s %s:\n", mrb_sym_dump(mrb, node_to_sym(tree->car)), mrb_sym_dump(mrb, node_to_sym(tree->cdr))); break; case NODE_UNDEF: printf("NODE_UNDEF"); { node *t = tree; while (t) { printf(" %s", mrb_sym_dump(mrb, node_to_sym(t->car))); t = t->cdr; } } printf(":\n"); break; case NODE_CLASS: printf("NODE_CLASS:\n"); if (class_node(tree)->name) { dump_cpath(mrb, module_node(tree)->name, offset+1, lineno); } if (class_node(tree)->superclass) { dump_prefix(offset+1, lineno); printf("super:\n"); dump_node(mrb, class_node(tree)->superclass, offset+2); } if (class_node(tree)->body) { dump_prefix(offset+1, lineno); printf("body:\n"); dump_node(mrb, class_node(tree)->body->cdr, offset+2); } break; case NODE_MODULE: printf("NODE_MODULE:\n"); if (module_node(tree)->name) { dump_cpath(mrb, module_node(tree)->name, offset+1, lineno); } if (module_node(tree)->body) { dump_prefix(offset+1, lineno); printf("body:\n"); dump_node(mrb, module_node(tree)->body->cdr, offset+2); } break; case NODE_SCLASS: printf("NODE_SCLASS:\n"); if (sclass_node(tree)->obj) { dump_prefix(offset+1, lineno); printf("obj:\n"); dump_node(mrb, sclass_node(tree)->obj, offset+2); } if (sclass_node(tree)->body) { dump_prefix(offset+1, lineno); printf("body:\n"); dump_node(mrb, sclass_node(tree)->body->cdr, offset+2); } break; case NODE_SDEF: printf("NODE_SDEF: %s\n", mrb_sym_dump(mrb, def_node(tree)->name)); if (sdef_node(tree)->obj) { dump_prefix(offset+1, lineno); printf("recv:\n"); dump_node(mrb, sdef_node(tree)->obj, offset+2); } if (sdef_node(tree)->args) { dump_args(mrb, sdef_node(tree)->args, offset+1, lineno); } if (sdef_node(tree)->locals) { dump_locals(mrb, sdef_node(tree)->locals, offset+1, lineno); } if (sdef_node(tree)->body) { dump_prefix(offset+1, lineno); printf("body:\n"); dump_node(mrb, sdef_node(tree)->body, offset+2); } break; case NODE_POSTEXE: printf("NODE_POSTEXE:\n"); dump_node(mrb, tree, offset+1); break; case NODE_HEREDOC: printf("NODE_HEREDOC:\n"); if (heredoc_node(tree)->info.term) { dump_prefix(offset+1, lineno); printf("terminator: \"%s\"\n", heredoc_node(tree)->info.term); } if (heredoc_node(tree)->info.doc) { dump_prefix(offset+1, lineno); printf("body:\n"); dump_str(mrb, heredoc_node(tree)->info.doc, offset+2, lineno); } if (heredoc_node(tree)->info.allow_indent) { dump_prefix(offset+1, lineno); printf("allow_indent: true\n"); } if (heredoc_node(tree)->info.remove_indent) { dump_prefix(offset+1, lineno); printf("remove_indent: true\n"); } break; case NODE_CASE_MATCH: printf("NODE_CASE_MATCH:\n"); if (case_match_node(tree)->value) { dump_prefix(offset+1, lineno); printf("value:\n"); dump_node(mrb, case_match_node(tree)->value, offset+2); } if (case_match_node(tree)->in_clauses) { node *in_clause = case_match_node(tree)->in_clauses; while (in_clause) { dump_node(mrb, in_clause->car, offset+1); in_clause = in_clause->cdr; } } break; case NODE_IN: printf("NODE_IN:\n"); if (in_node(tree)->pattern) { dump_prefix(offset+1, lineno); printf("pattern:\n"); dump_node(mrb, in_node(tree)->pattern, offset+2); } if (in_node(tree)->guard) { dump_prefix(offset+1, lineno); printf("guard (%s):\n", in_node(tree)->guard_is_unless ? "unless" : "if"); dump_node(mrb, in_node(tree)->guard, offset+2); } if (in_node(tree)->body) { dump_prefix(offset+1, lineno); printf("body:\n"); dump_node(mrb, in_node(tree)->body, offset+2); } break; case NODE_PAT_VALUE: printf("NODE_PAT_VALUE:\n"); if (pat_value_node(tree)->value) { dump_node(mrb, pat_value_node(tree)->value, offset+1); } break; case NODE_PAT_VAR: if (pat_var_node(tree)->name) { printf("NODE_PAT_VAR: %s\n", mrb_sym_dump(mrb, pat_var_node(tree)->name)); } else { printf("NODE_PAT_VAR: _ (wildcard)\n"); } break; case NODE_PAT_PIN: printf("NODE_PAT_PIN: ^%s\n", mrb_sym_dump(mrb, pat_pin_node(tree)->name)); break; case NODE_PAT_AS: printf("NODE_PAT_AS: => %s\n", mrb_sym_dump(mrb, pat_as_node(tree)->name)); if (pat_as_node(tree)->pattern) { dump_prefix(offset+1, lineno); printf("pattern:\n"); dump_node(mrb, pat_as_node(tree)->pattern, offset+2); } break; case NODE_PAT_ALT: printf("NODE_PAT_ALT:\n"); if (pat_alt_node(tree)->left) { dump_prefix(offset+1, lineno); printf("left:\n"); dump_node(mrb, pat_alt_node(tree)->left, offset+2); } if (pat_alt_node(tree)->right) { dump_prefix(offset+1, lineno); printf("right:\n"); dump_node(mrb, pat_alt_node(tree)->right, offset+2); } break; case NODE_PAT_ARRAY: printf("NODE_PAT_ARRAY:\n"); if (pat_array_node(tree)->pre) { dump_prefix(offset+1, lineno); printf("pre:\n"); dump_recur(mrb, pat_array_node(tree)->pre, offset+2); } if (pat_array_node(tree)->rest) { dump_prefix(offset+1, lineno); if (pat_array_node(tree)->rest == (node*)-1) { printf("rest: * (anonymous)\n"); } else { printf("rest:\n"); dump_node(mrb, pat_array_node(tree)->rest, offset+2); } } if (pat_array_node(tree)->post) { dump_prefix(offset+1, lineno); printf("post:\n"); dump_recur(mrb, pat_array_node(tree)->post, offset+2); } break; case NODE_PAT_HASH: printf("NODE_PAT_HASH:\n"); if (pat_hash_node(tree)->pairs) { dump_prefix(offset+1, lineno); printf("pairs:\n"); dump_recur(mrb, pat_hash_node(tree)->pairs, offset+2); } if (pat_hash_node(tree)->rest) { dump_prefix(offset+1, lineno); if (pat_hash_node(tree)->rest == (node*)-1) { printf("rest: **nil\n"); } else { printf("rest:\n"); dump_node(mrb, pat_hash_node(tree)->rest, offset+2); } } break; case NODE_MATCH_PAT: printf("NODE_MATCH_PAT%s:\n", match_pat_node(tree)->raise_on_fail ? " (=>)" : " (in)"); dump_prefix(offset+1, lineno); printf("value:\n"); dump_node(mrb, match_pat_node(tree)->value, offset+2); dump_prefix(offset+1, lineno); printf("pattern:\n"); dump_node(mrb, match_pat_node(tree)->pattern, offset+2); break; default: /* Fallback: unknown node type - skip like codegen.c does */ printf("unknown node type %d (0x%x)\n", nodetype, (unsigned)nodetype); break; } #endif } void mrb_parser_dump(mrb_state *mrb, node *tree, int offset) { dump_node(mrb, tree, offset); } typedef mrb_bool mrb_parser_foreach_top_variable_func(mrb_state *mrb, mrb_sym sym, void *user); void mrb_parser_foreach_top_variable(mrb_state *mrb, struct mrb_parser_state *p, mrb_parser_foreach_top_variable_func *func, void *user); void mrb_parser_foreach_top_variable(mrb_state *mrb, struct mrb_parser_state *p, mrb_parser_foreach_top_variable_func *func, void *user) { const mrb_ast_node *n = p->tree; if (node_type_p((node*)n, NODE_SCOPE)) { /* Extract locals from variable-sized NODE_SCOPE */ struct mrb_ast_scope_node *scope = scope_node(n); n = scope->locals; for (; n; n = n->cdr) { mrb_sym sym = node_to_sym(n->car); if (sym != 0) { if (!func(mrb, sym, user)) break; } } } }