mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
919cbd8fea
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>
8977 lines
236 KiB
Plaintext
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;
|
|
}
|
|
}
|
|
}
|
|
}
|