mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
cc622f718c
allocate ** keyword dictionary register when methods have keyword
arguments (parse.y new_args_tail), broken in commit 26ea71260 during
cons-list to struct migration. reconstruct keyword hash after KEYEND
from extracted keyword local variables so super can access keyword
values. encode block parameter flag in ainfo bit 13 and generate
LOADNIL for block register in codegen_zsuper when parent has keywords
but no block parameter.
Co-authored-by: Claude <noreply@anthropic.com>
8389 lines
218 KiB
Plaintext
8389 lines
218 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 <mruby/presym.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_2(p->mrb, 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
|
|
get_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;
|
|
}
|
|
|
|
/* 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 (get_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) 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_2(p->mrb, 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_2(p->mrb, 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 (get_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 (get_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 = get_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
|
|
|
|
%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
|
|
%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));
|
|
}
|
|
| arg tASSOC tIDENTIFIER
|
|
{
|
|
node *lhs = new_lvar(p, $3);
|
|
assignable(p, lhs);
|
|
$$ = new_asgn(p, lhs, $1);
|
|
}
|
|
| 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
|
|
;
|
|
|
|
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;
|
|
}
|
|
stmt {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_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_2(p->mrb, call), $3, $2);
|
|
}
|
|
| primary_value tCOLON2 paren_args
|
|
{
|
|
$$ = new_call(p, $1, MRB_SYM_2(p->mrb, 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
|
|
;
|
|
|
|
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_2(p->mrb, __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
|
|
{
|
|
local_nest(p);
|
|
}
|
|
| tNUMPARAM tLABEL_TAG
|
|
{
|
|
local_nest(p);
|
|
}
|
|
;
|
|
|
|
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;
|
|
}
|
|
| /* 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
|
|
{
|
|
$$ = intern_op(and);
|
|
}
|
|
;
|
|
|
|
opt_f_block_arg : ',' f_block_arg
|
|
{
|
|
$$ = $2;
|
|
}
|
|
| 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 */
|
|
c = -c;
|
|
if (c < 0x80) {
|
|
utf8[0] = (char)c;
|
|
len = 1;
|
|
}
|
|
else if (c < 0x800) {
|
|
utf8[0] = (char)(0xC0 | (c >> 6));
|
|
utf8[1] = (char)(0x80 | (c & 0x3F));
|
|
len = 2;
|
|
}
|
|
else if (c < 0x10000) {
|
|
utf8[0] = (char)(0xE0 | (c >> 12) );
|
|
utf8[1] = (char)(0x80 | ((c >> 6) & 0x3F));
|
|
utf8[2] = (char)(0x80 | ( c & 0x3F));
|
|
len = 3;
|
|
}
|
|
else {
|
|
utf8[0] = (char)(0xF0 | (c >> 18) );
|
|
utf8[1] = (char)(0x80 | ((c >> 12) & 0x3F));
|
|
utf8[2] = (char)(0x80 | ((c >> 6) & 0x3F));
|
|
utf8[3] = (char)(0x80 | ( c & 0x3F));
|
|
len = 4;
|
|
}
|
|
}
|
|
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())
|
|
#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;
|
|
|
|
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);
|
|
p->lstate = 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 (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->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
|
|
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 (get_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 = get_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", get_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;
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|