Files
Yukihiro "Matz" Matsumoto 919cbd8fea mruby-compiler: allow compound statement in tLPAREN_ARG
Change the grammar rule for tLPAREN_ARG from accepting only a
single stmt to accepting compstmt. This allows compound
statements with semicolons inside parenthesized arguments when
the parenthesis is preceded by a space, e.g., `p (f1; f2)`.

This matches the behavior of CRuby 3.3+.

Fixes #6766.

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-30 07:06:30 +09:00

8977 lines
236 KiB
Plaintext

/*
** 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 <ctype.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <mruby.h>
#include <mruby/compile.h>
#include <mruby/proc.h>
#include <mruby/error.h>
#include <mruby/throw.h>
#include <mruby/string.h>
#include <mruby/dump.h>
#include <mruby/internal.h>
#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; i<len-1; i++) {
if (!identchar(name[i])) return;
}
yyerror(NULL, p, "setter method cannot be defined by endless method definition");
}
}
static void
call_with_block(parser_state *p, node *a, node *b)
{
if (!a) return;
/* Handle direct variable-sized nodes */
struct mrb_ast_var_header *header = (struct mrb_ast_var_header*)a;
enum node_type var_type = (enum node_type)header->node_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 <num>
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 <id> tIDENTIFIER "local variable or method"
%token <id> tFID "method"
%token <id> tGVAR "global variable"
%token <id> tIVAR "instance variable"
%token <id> tCONSTANT "constant"
%token <id> tCVAR "class variable"
%token <id> tLABEL_TAG "label"
%token <nd> tINTEGER "integer literal"
%token <nd> tFLOAT "float literal"
%token <nd> tCHAR "character literal"
%token <nd> tXSTRING tREGEXP
%token <nd> tSTRING tSTRING_PART tSTRING_MID
%token <nd> tNTH_REF tBACK_REF
%token <num> tREGEXP_END
%token <num> tNUMPARAM "numbered parameter"
%type <nd> singleton string string_fragment string_rep string_interp xstring regexp
%type <nd> literal numeric cpath symbol defn_head defs_head
%type <nd> top_compstmt top_stmts top_stmt
%type <nd> bodystmt compstmt stmts stmt expr arg primary command command_call method_call
%type <nd> expr_value arg_rhs primary_value
%type <nd> if_tail opt_else case_body cases opt_rescue exc_list exc_var opt_ensure
%type <nd> args call_args opt_call_args
%type <nd> paren_args opt_paren_args variable
%type <nd> command_args aref_args opt_block_arg block_arg var_ref var_lhs
%type <nd> command_asgn command_rhs mrhs superclass block_call block_command
%type <nd> f_block_optarg f_block_opt
%type <nd> f_opt_arglist_paren f_arglist_paren f_arglist
%type <nd> f_args f_arg f_arg_item f_optarg f_margs
%type <nd> assoc_list assocs assoc undef_list backref for_var
%type <nd> block_param opt_block_param block_param_def f_opt
%type <nd> bv_decls opt_bv_decl bvar f_larglist lambda_body
%type <nd> brace_block cmd_brace_block do_block lhs none f_bad_arg
%type <nd> mlhs mlhs_list mlhs_post mlhs_basic mlhs_item mlhs_node mlhs_inner
%type <id> fsym sym basic_symbol operation operation2 operation3
%type <id> cname fname op f_rest_arg f_block_arg opt_f_block_arg f_norm_arg f_opt_asgn
%type <nd> heredoc words symbols
%type <num> call_op call_op2 /* 0:'&.', 1:'.', 2:'::' */
%type <nd> args_tail opt_args_tail f_kwarg f_kw
%type <nd> f_block_kwarg f_block_kw block_args_tail opt_block_args_tail
%type <id> f_label f_kwrest
/* pattern matching */
%type <nd> 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 <id> 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 <nd> tHEREDOC_BEG "here document"
%token tHEREDOC_END tLITERAL_DELIM tHD_LITERAL_DELIM
%token <nd> 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
{
$<nd>$ = local_switch(p);
nvars_block(p);
}
'{' top_compstmt '}'
{
yyerror(&@1, p, "BEGIN not supported");
local_resume(p, $<nd>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;
$$ = $<id>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 : {
$<stack>$ = p->cmdarg_stack;
CMDARG_PUSH(1);
}
call_args
{
p->cmdarg_stack = $<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
{
$<stack>$ = p->cmdarg_stack;
p->cmdarg_stack = 0;
}
bodystmt
keyword_end
{
p->cmdarg_stack = $<stack>2;
$$ = new_begin(p, $3);
}
| tLPAREN_ARG
{
$<stack>$ = p->cmdarg_stack;
p->cmdarg_stack = 0;
}
compstmt {p->lstate = EXPR_ENDARG;} rparen
{
p->cmdarg_stack = $<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);
$<num>$ = p->lpar_beg;
p->lpar_beg = ++p->paren_nest;
}
f_larglist
{
$<stack>$ = p->cmdarg_stack;
p->cmdarg_stack = 0;
}
lambda_body
{
p->lpar_beg = $<num>2;
$$ = new_lambda(p, $3, $5);
local_unnest(p);
nvars_unnest(p);
p->cmdarg_stack = $<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");
$<nd>$ = local_switch(p);
nvars_block(p);
}
bodystmt
keyword_end
{
$$ = new_class(p, $2, $3, $5);
SET_LINENO($$, $1);
local_resume(p, $<nd>4);
nvars_unnest(p);
}
| keyword_class
tLSHFT expr
{
$<num>$ = p->in_def;
p->in_def = 0;
}
term
{
$<nd>$ = 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, $<nd>6->car);
nvars_unnest(p);
p->in_def = $<num>4;
p->in_single = node_to_int($<nd>6->cdr);
}
| keyword_module
cpath
{
if (p->in_def || p->in_single)
yyerror(&@1, p, "module definition in method body");
$<nd>$ = local_switch(p);
nvars_block(p);
}
bodystmt
keyword_end
{
$$ = new_module(p, $2, $4);
SET_LINENO($$, $1);
local_resume(p, $<nd>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);
$<num>$ = p->lineno;
}
opt_block_param
bodystmt
keyword_end
{
$$ = new_block(p,$3,$4);
SET_LINENO($$, $<num>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);
$<num>$ = p->lineno;
}
opt_block_param
compstmt '}'
{
$$ = new_block(p,$3,$4);
SET_LINENO($$, $<num>2);
local_unnest(p);
nvars_unnest(p);
}
| keyword_do
{
local_nest(p);
nvars_nest(p);
$<num>$ = p->lineno;
}
opt_block_param
bodystmt keyword_end
{
$$ = new_block(p,$3,$4);
SET_LINENO($$, $<num>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
{
$<nd>$ = push_strterm(p);
}
compstmt
'}'
{
pop_strterm(p,$<nd>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
{
$<nd>$ = push_strterm(p);
}
compstmt
'}'
{
pop_strterm(p, $<nd>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
{
$<nd>$ = local_switch(p);
}
f_margs rparen
{
$$ = new_marg(p, $3);
local_resume(p, $<nd>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 += $<num>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<limit; i++) {
buf[i] = nextc(p);
if (buf[i] < 0) goto eof;
if (!ISXDIGIT(buf[i])) {
pushback(p, buf[i]);
break;
}
}
}
else {
pushback(p, buf[0]);
}
hex = scan_hex(p, buf, i, &i);
if (i == 0 || hex > 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 .<digit> 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; i<cxt->slen; 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: *<anonymous>\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;
}
}
}
}