mirror of
https://github.com/ruby/ruby.git
synced 2025-09-15 08:33:58 +02:00

Now it is used only for wheter `opt_paren_args` is `none`. Introduce a new special node to distinguish an empty parentheses from it .
668 lines
16 KiB
C
668 lines
16 KiB
C
%# -*- c -*-
|
|
#include "ruby/ruby.h"
|
|
#include "ruby/encoding.h"
|
|
#include "internal.h"
|
|
#include "rubyparser.h"
|
|
#define YYSTYPE_IS_DECLARED
|
|
#include "parse.h"
|
|
#include "internal/parse.h"
|
|
#include "internal/ruby_parser.h"
|
|
#include "node.h"
|
|
#include "eventids1.h"
|
|
#include "eventids2.h"
|
|
#include "ripper_init.h"
|
|
|
|
#define STR_NEW2(ptr) rb_enc_str_new((ptr),strlen(ptr),rb_ruby_parser_enc(p))
|
|
#define RIPPER_VERSION "0.1.0"
|
|
|
|
ID id_warn, id_warning, id_gets, id_assoc;
|
|
|
|
enum lex_type {
|
|
lex_type_str,
|
|
lex_type_io,
|
|
lex_type_generic,
|
|
};
|
|
|
|
struct ripper {
|
|
rb_parser_t *p;
|
|
enum lex_type type;
|
|
union {
|
|
struct lex_pointer_string ptr_str;
|
|
VALUE val;
|
|
} data;
|
|
};
|
|
|
|
static void
|
|
ripper_parser_mark2(void *ptr)
|
|
{
|
|
struct ripper *r = (struct ripper*)ptr;
|
|
if (r->p) {
|
|
ripper_parser_mark(r->p);
|
|
|
|
switch (r->type) {
|
|
case lex_type_str:
|
|
rb_gc_mark(r->data.ptr_str.str);
|
|
break;
|
|
case lex_type_io:
|
|
rb_gc_mark(r->data.val);
|
|
break;
|
|
case lex_type_generic:
|
|
rb_gc_mark(r->data.val);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
ripper_parser_free2(void *ptr)
|
|
{
|
|
struct ripper *r = (struct ripper*)ptr;
|
|
if (r->p) ripper_parser_free(r->p);
|
|
xfree(r);
|
|
}
|
|
|
|
static size_t
|
|
ripper_parser_memsize2(const void *ptr)
|
|
{
|
|
struct ripper *r = (struct ripper*)ptr;
|
|
return (r->p) ? ripper_parser_memsize(r->p) : 0;
|
|
}
|
|
|
|
static const rb_data_type_t parser_data_type = {
|
|
"ripper",
|
|
{
|
|
ripper_parser_mark2,
|
|
ripper_parser_free2,
|
|
ripper_parser_memsize2,
|
|
},
|
|
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
};
|
|
|
|
static rb_parser_string_t *
|
|
ripper_lex_get_generic(struct parser_params *p, rb_parser_input_data input, int line_count)
|
|
{
|
|
VALUE src = (VALUE)input;
|
|
VALUE line = rb_funcallv_public(src, id_gets, 0, 0);
|
|
if (NIL_P(line)) return 0;
|
|
if (!RB_TYPE_P(line, T_STRING)) {
|
|
rb_raise(rb_eTypeError,
|
|
"gets returned %"PRIsVALUE" (expected String or nil)",
|
|
rb_obj_class(line));
|
|
}
|
|
return rb_str_to_parser_string(p, line);
|
|
}
|
|
|
|
void
|
|
ripper_compile_error(struct parser_params *p, const char *fmt, ...)
|
|
{
|
|
VALUE str;
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
|
str = rb_vsprintf(fmt, args);
|
|
va_end(args);
|
|
rb_funcall(ripper_value(p), rb_intern("compile_error"), 1, str);
|
|
ripper_error(p);
|
|
}
|
|
|
|
static rb_parser_string_t *
|
|
ripper_lex_io_get(struct parser_params *p, rb_parser_input_data input, int line_count)
|
|
{
|
|
VALUE src = (VALUE)input;
|
|
VALUE line = rb_io_gets(src);
|
|
if (NIL_P(line)) return 0;
|
|
return rb_str_to_parser_string(p, line);
|
|
}
|
|
|
|
static rb_parser_string_t *
|
|
ripper_lex_get_str(struct parser_params *p, rb_parser_input_data input, int line_count)
|
|
{
|
|
return rb_parser_lex_get_str(p, (struct lex_pointer_string *)input);
|
|
}
|
|
|
|
static VALUE
|
|
ripper_s_allocate(VALUE klass)
|
|
{
|
|
struct ripper *r;
|
|
|
|
VALUE self = TypedData_Make_Struct(klass, struct ripper,
|
|
&parser_data_type, r);
|
|
|
|
#ifdef UNIVERSAL_PARSER
|
|
const rb_parser_config_t *config = rb_ruby_parser_config();
|
|
r->p = rb_ripper_parser_params_allocate(config);
|
|
#else
|
|
r->p = rb_ruby_ripper_parser_allocate();
|
|
#endif
|
|
rb_ruby_parser_set_value(r->p, self);
|
|
return self;
|
|
}
|
|
|
|
static struct parser_params *
|
|
ripper_parser_params(VALUE self, bool initialized)
|
|
{
|
|
struct ripper *r;
|
|
struct parser_params *p;
|
|
|
|
TypedData_Get_Struct(self, struct ripper, &parser_data_type, r);
|
|
p = r->p;
|
|
if (initialized && !rb_ruby_ripper_initialized_p(p)) {
|
|
rb_raise(rb_eArgError, "method called for uninitialized object");
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* ripper.error? -> Boolean
|
|
*
|
|
* Return true if parsed source has errors.
|
|
*/
|
|
static VALUE
|
|
ripper_error_p(VALUE vparser)
|
|
{
|
|
struct parser_params *p = ripper_parser_params(vparser, false);
|
|
|
|
return RBOOL(rb_ruby_parser_error_p(p));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* ripper.end_seen? -> Boolean
|
|
*
|
|
* Return true if parsed source ended by +\_\_END\_\_+.
|
|
*/
|
|
static VALUE
|
|
ripper_parser_end_seen_p(VALUE vparser)
|
|
{
|
|
struct parser_params *p = ripper_parser_params(vparser, false);
|
|
|
|
return RBOOL(rb_ruby_parser_end_seen_p(p));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* ripper.encoding -> encoding
|
|
*
|
|
* Return encoding of the source.
|
|
*/
|
|
static VALUE
|
|
ripper_parser_encoding(VALUE vparser)
|
|
{
|
|
struct parser_params *p = ripper_parser_params(vparser, false);
|
|
|
|
return rb_enc_from_encoding(rb_ruby_parser_encoding(p));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* ripper.yydebug -> true or false
|
|
*
|
|
* Get yydebug.
|
|
*/
|
|
static VALUE
|
|
ripper_parser_get_yydebug(VALUE self)
|
|
{
|
|
struct parser_params *p = ripper_parser_params(self, false);
|
|
|
|
return RBOOL(rb_ruby_parser_get_yydebug(p));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* ripper.yydebug = flag
|
|
*
|
|
* Set yydebug.
|
|
*/
|
|
static VALUE
|
|
ripper_parser_set_yydebug(VALUE self, VALUE flag)
|
|
{
|
|
struct parser_params *p = ripper_parser_params(self, false);
|
|
|
|
rb_ruby_parser_set_yydebug(p, RTEST(flag));
|
|
return flag;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* ripper.debug_output -> obj
|
|
*
|
|
* Get debug output.
|
|
*/
|
|
static VALUE
|
|
ripper_parser_get_debug_output(VALUE self)
|
|
{
|
|
struct parser_params *p = ripper_parser_params(self, false);
|
|
|
|
return rb_ruby_parser_debug_output(p);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* ripper.debug_output = obj
|
|
*
|
|
* Set debug output.
|
|
*/
|
|
static VALUE
|
|
ripper_parser_set_debug_output(VALUE self, VALUE output)
|
|
{
|
|
struct parser_params *p = ripper_parser_params(self, false);
|
|
|
|
rb_ruby_parser_set_debug_output(p, output);
|
|
return output;
|
|
}
|
|
|
|
#ifdef UNIVERSAL_PARSER
|
|
struct dedent_string_arg {
|
|
struct parser_params *p;
|
|
VALUE input;
|
|
VALUE width;
|
|
};
|
|
|
|
static VALUE
|
|
parser_dedent_string0(VALUE a)
|
|
{
|
|
struct dedent_string_arg *arg = (void *)a;
|
|
int wid, col;
|
|
|
|
StringValue(arg->input);
|
|
wid = NUM2UINT(arg->width);
|
|
col = rb_ruby_ripper_dedent_string(arg->p, arg->input, wid);
|
|
return INT2NUM(col);
|
|
}
|
|
|
|
static VALUE
|
|
parser_free(VALUE a)
|
|
{
|
|
struct parser_params *p = (void *)a;
|
|
|
|
rb_ruby_parser_free(p);
|
|
return Qnil;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* call-seq:
|
|
* Ripper.dedent_string(input, width) -> Integer
|
|
*
|
|
* USE OF RIPPER LIBRARY ONLY.
|
|
*
|
|
* Strips up to +width+ leading whitespaces from +input+,
|
|
* and returns the stripped column width.
|
|
*/
|
|
#ifdef UNIVERSAL_PARSER
|
|
static VALUE
|
|
parser_dedent_string(VALUE self, VALUE input, VALUE width)
|
|
{
|
|
struct parser_params *p;
|
|
struct dedent_string_arg args;
|
|
|
|
p = rb_parser_params_new();
|
|
|
|
args.p = p;
|
|
args.input = input;
|
|
args.width = width;
|
|
return rb_ensure(parser_dedent_string0, (VALUE)&args, parser_free, (VALUE)p);
|
|
}
|
|
#else
|
|
static VALUE
|
|
parser_dedent_string(VALUE self, VALUE input, VALUE width)
|
|
{
|
|
int wid, col;
|
|
|
|
StringValue(input);
|
|
wid = NUM2UINT(width);
|
|
col = rb_ruby_ripper_dedent_string(0, input, wid);
|
|
return INT2NUM(col);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* call-seq:
|
|
* Ripper.new(src, filename="(ripper)", lineno=1) -> ripper
|
|
*
|
|
* Create a new Ripper object.
|
|
* _src_ must be a String, an IO, or an Object which has #gets method.
|
|
*
|
|
* This method does not starts parsing.
|
|
* See also Ripper#parse and Ripper.parse.
|
|
*/
|
|
static VALUE
|
|
ripper_initialize(int argc, VALUE *argv, VALUE self)
|
|
{
|
|
struct ripper *r;
|
|
struct parser_params *p;
|
|
VALUE src, fname, lineno;
|
|
rb_parser_lex_gets_func *gets;
|
|
VALUE sourcefile_string;
|
|
const char *sourcefile;
|
|
int sourceline;
|
|
rb_parser_input_data input;
|
|
|
|
p = ripper_parser_params(self, false);
|
|
TypedData_Get_Struct(self, struct ripper, &parser_data_type, r);
|
|
rb_scan_args(argc, argv, "12", &src, &fname, &lineno);
|
|
if (RB_TYPE_P(src, T_FILE)) {
|
|
gets = ripper_lex_io_get;
|
|
r->type = lex_type_io;
|
|
r->data.val = src;
|
|
input = (rb_parser_input_data)src;
|
|
}
|
|
else if (rb_respond_to(src, id_gets)) {
|
|
gets = ripper_lex_get_generic;
|
|
r->type = lex_type_generic;
|
|
r->data.val = src;
|
|
input = (rb_parser_input_data)src;
|
|
}
|
|
else {
|
|
StringValue(src);
|
|
gets = ripper_lex_get_str;
|
|
r->type = lex_type_str;
|
|
r->data.ptr_str.str = src;
|
|
r->data.ptr_str.ptr = 0;
|
|
input = (rb_parser_input_data)&r->data.ptr_str;
|
|
}
|
|
if (NIL_P(fname)) {
|
|
fname = STR_NEW2("(ripper)");
|
|
OBJ_FREEZE(fname);
|
|
}
|
|
else {
|
|
StringValueCStr(fname);
|
|
fname = rb_str_new_frozen(fname);
|
|
}
|
|
rb_ruby_ripper_parser_initialize(p);
|
|
|
|
sourcefile_string = fname;
|
|
sourcefile = RSTRING_PTR(fname);
|
|
sourceline = NIL_P(lineno) ? 0 : NUM2INT(lineno) - 1;
|
|
|
|
rb_ruby_parser_ripper_initialize(p, gets, input, sourcefile_string, sourcefile, sourceline);
|
|
|
|
return Qnil;
|
|
}
|
|
|
|
static VALUE
|
|
ripper_parse0(VALUE vparser)
|
|
{
|
|
struct parser_params *p = ripper_parser_params(vparser, false);
|
|
|
|
rb_ruby_ripper_parse0(p);
|
|
return rb_ruby_parser_result(p);
|
|
}
|
|
|
|
static VALUE
|
|
ripper_ensure(VALUE vparser)
|
|
{
|
|
struct parser_params *p = ripper_parser_params(vparser, false);
|
|
|
|
rb_ruby_parser_set_parsing_thread(p, Qnil);
|
|
return Qnil;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* ripper.parse
|
|
*
|
|
* Start parsing and returns the value of the root action.
|
|
*/
|
|
static VALUE
|
|
ripper_parse(VALUE self)
|
|
{
|
|
struct parser_params *p = ripper_parser_params(self, true);
|
|
VALUE result;
|
|
|
|
if (!NIL_P(rb_ruby_parser_parsing_thread(p))) {
|
|
if (rb_ruby_parser_parsing_thread(p) == rb_thread_current())
|
|
rb_raise(rb_eArgError, "Ripper#parse is not reentrant");
|
|
else
|
|
rb_raise(rb_eArgError, "Ripper#parse is not multithread-safe");
|
|
}
|
|
rb_ruby_parser_set_parsing_thread(p, rb_thread_current());
|
|
result = rb_ensure(ripper_parse0, self, ripper_ensure, self);
|
|
RB_GC_GUARD(self);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* ripper.column -> Integer
|
|
*
|
|
* Return column number of current parsing line.
|
|
* This number starts from 0.
|
|
*/
|
|
static VALUE
|
|
ripper_column(VALUE self)
|
|
{
|
|
struct parser_params *p = ripper_parser_params(self, true);
|
|
long col;
|
|
|
|
if (NIL_P(rb_ruby_parser_parsing_thread(p))) return Qnil;
|
|
col = rb_ruby_ripper_column(p);
|
|
return LONG2NUM(col);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* ripper.filename -> String
|
|
*
|
|
* Return current parsing filename.
|
|
*/
|
|
static VALUE
|
|
ripper_filename(VALUE self)
|
|
{
|
|
struct parser_params *p = ripper_parser_params(self, true);
|
|
|
|
return rb_ruby_parser_ruby_sourcefile_string(p);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* ripper.lineno -> Integer
|
|
*
|
|
* Return line number of current parsing line.
|
|
* This number starts from 1.
|
|
*/
|
|
static VALUE
|
|
ripper_lineno(VALUE self)
|
|
{
|
|
struct parser_params *p = ripper_parser_params(self, true);
|
|
|
|
if (NIL_P(rb_ruby_parser_parsing_thread(p))) return Qnil;
|
|
return INT2NUM(rb_ruby_parser_ruby_sourceline(p));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* ripper.state -> Integer
|
|
*
|
|
* Return scanner state of current token.
|
|
*/
|
|
static VALUE
|
|
ripper_state(VALUE self)
|
|
{
|
|
struct parser_params *p = ripper_parser_params(self, true);
|
|
|
|
if (NIL_P(rb_ruby_parser_parsing_thread(p))) return Qnil;
|
|
return INT2NUM(rb_ruby_parser_lex_state(p));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* ripper.token -> String
|
|
*
|
|
* Return the current token string.
|
|
*/
|
|
static VALUE
|
|
ripper_token(VALUE self)
|
|
{
|
|
struct parser_params *p = ripper_parser_params(self, true);
|
|
long pos, len;
|
|
VALUE str;
|
|
|
|
if (NIL_P(rb_ruby_parser_parsing_thread(p))) return Qnil;
|
|
pos = rb_ruby_ripper_column(p);
|
|
len = rb_ruby_ripper_token_len(p);
|
|
str = rb_str_new_parser_string(rb_ruby_ripper_lex_lastline(p));
|
|
return rb_str_subseq(str, pos, len);
|
|
}
|
|
|
|
#ifdef RIPPER_DEBUG
|
|
/* :nodoc: */
|
|
static VALUE
|
|
ripper_assert_Qundef(VALUE self, VALUE obj, VALUE msg)
|
|
{
|
|
StringValue(msg);
|
|
if (UNDEF_P(obj)) {
|
|
rb_raise(rb_eArgError, "%"PRIsVALUE, msg);
|
|
}
|
|
return Qnil;
|
|
}
|
|
|
|
/* :nodoc: */
|
|
static VALUE
|
|
ripper_raw_value(VALUE self, VALUE obj)
|
|
{
|
|
return ULONG2NUM(obj);
|
|
}
|
|
|
|
/* :nodoc: */
|
|
static VALUE
|
|
ripper_validate_object(VALUE self, VALUE x)
|
|
{
|
|
if (x == Qfalse) return x;
|
|
if (x == Qtrue) return x;
|
|
if (NIL_P(x)) return x;
|
|
if (UNDEF_P(x))
|
|
rb_raise(rb_eArgError, "Qundef given");
|
|
if (FIXNUM_P(x)) return x;
|
|
if (SYMBOL_P(x)) return x;
|
|
switch (BUILTIN_TYPE(x)) {
|
|
case T_STRING:
|
|
case T_OBJECT:
|
|
case T_ARRAY:
|
|
case T_BIGNUM:
|
|
case T_FLOAT:
|
|
case T_COMPLEX:
|
|
case T_RATIONAL:
|
|
break;
|
|
default:
|
|
rb_raise(rb_eArgError, "wrong type of ruby object: %p (%s)",
|
|
(void *)x, rb_obj_classname(x));
|
|
}
|
|
if (!RBASIC_CLASS(x)) {
|
|
rb_raise(rb_eArgError, "hidden ruby object: %p (%s)",
|
|
(void *)x, rb_builtin_type_name(TYPE(x)));
|
|
}
|
|
return x;
|
|
}
|
|
#endif
|
|
|
|
#ifdef UNIVERSAL_PARSER
|
|
struct lex_state_name_arg {
|
|
struct parser_params *p;
|
|
VALUE state;
|
|
};
|
|
|
|
static VALUE
|
|
lex_state_name0(VALUE a)
|
|
{
|
|
struct lex_state_name_arg *arg = (void *)a;
|
|
|
|
return rb_ruby_ripper_lex_state_name(arg->p, NUM2INT(arg->state));
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* call-seq:
|
|
* Ripper.lex_state_name(integer) -> string
|
|
*
|
|
* Returns a string representation of lex_state.
|
|
*/
|
|
#ifdef UNIVERSAL_PARSER
|
|
static VALUE
|
|
ripper_lex_state_name(VALUE self, VALUE state)
|
|
{
|
|
struct parser_params *p;
|
|
struct lex_state_name_arg args;
|
|
|
|
p = rb_parser_params_new();
|
|
|
|
args.p = p;
|
|
args.state = state;
|
|
|
|
return rb_ensure(lex_state_name0, (VALUE)&args, parser_free, (VALUE)p);
|
|
}
|
|
#else
|
|
static VALUE
|
|
ripper_lex_state_name(VALUE self, VALUE state)
|
|
{
|
|
return rb_ruby_ripper_lex_state_name(0, NUM2INT(state));
|
|
}
|
|
#endif
|
|
|
|
void
|
|
Init_ripper(void)
|
|
{
|
|
ripper_init_eventids1();
|
|
ripper_init_eventids2();
|
|
id_warn = rb_intern_const("warn");
|
|
id_warning = rb_intern_const("warning");
|
|
id_gets = rb_intern_const("gets");
|
|
id_assoc = rb_intern_const("=>");
|
|
|
|
InitVM(ripper);
|
|
}
|
|
|
|
void
|
|
InitVM_ripper(void)
|
|
{
|
|
VALUE Ripper;
|
|
|
|
Ripper = rb_define_class("Ripper", rb_cObject);
|
|
/* version of Ripper */
|
|
rb_define_const(Ripper, "Version", rb_usascii_str_new2(RIPPER_VERSION));
|
|
rb_define_alloc_func(Ripper, ripper_s_allocate);
|
|
rb_define_method(Ripper, "initialize", ripper_initialize, -1);
|
|
rb_define_method(Ripper, "parse", ripper_parse, 0);
|
|
rb_define_method(Ripper, "column", ripper_column, 0);
|
|
rb_define_method(Ripper, "filename", ripper_filename, 0);
|
|
rb_define_method(Ripper, "lineno", ripper_lineno, 0);
|
|
rb_define_method(Ripper, "state", ripper_state, 0);
|
|
rb_define_method(Ripper, "token", ripper_token, 0);
|
|
rb_define_method(Ripper, "end_seen?", ripper_parser_end_seen_p, 0);
|
|
rb_define_method(Ripper, "encoding", ripper_parser_encoding, 0);
|
|
rb_define_method(Ripper, "yydebug", ripper_parser_get_yydebug, 0);
|
|
rb_define_method(Ripper, "yydebug=", ripper_parser_set_yydebug, 1);
|
|
rb_define_method(Ripper, "debug_output", ripper_parser_get_debug_output, 0);
|
|
rb_define_method(Ripper, "debug_output=", ripper_parser_set_debug_output, 1);
|
|
rb_define_method(Ripper, "error?", ripper_error_p, 0);
|
|
#ifdef RIPPER_DEBUG
|
|
rb_define_method(Ripper, "assert_Qundef", ripper_assert_Qundef, 2);
|
|
rb_define_method(Ripper, "rawVALUE", ripper_raw_value, 1);
|
|
rb_define_method(Ripper, "validate_object", ripper_validate_object, 1);
|
|
#endif
|
|
|
|
rb_define_singleton_method(Ripper, "dedent_string", parser_dedent_string, 2);
|
|
rb_define_private_method(Ripper, "dedent_string", parser_dedent_string, 2);
|
|
|
|
rb_define_singleton_method(Ripper, "lex_state_name", ripper_lex_state_name, 1);
|
|
|
|
<% @exprs.each do |expr, desc| -%>
|
|
/* <%=desc%> */
|
|
rb_define_const(Ripper, "<%=expr%>", INT2NUM(<%=expr%>));
|
|
<% end %>
|
|
ripper_init_eventids1_table(Ripper);
|
|
ripper_init_eventids2_table(Ripper);
|
|
|
|
# if 0
|
|
/* Hack to let RDoc document SCRIPT_LINES__ */
|
|
|
|
/*
|
|
* When a Hash is assigned to +SCRIPT_LINES__+ the contents of files loaded
|
|
* after the assignment will be added as an Array of lines with the file
|
|
* name as the key.
|
|
*/
|
|
rb_define_global_const("SCRIPT_LINES__", Qnil);
|
|
#endif
|
|
}
|