[macruby-changes] [3287] MacRuby/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Sat Jan 16 20:37:53 PST 2010
Revision: 3287
http://trac.macosforge.org/projects/ruby/changeset/3287
Author: lsansonetti at apple.com
Date: 2010-01-16 20:37:52 -0800 (Sat, 16 Jan 2010)
Log Message:
-----------
experimental debugger
Modified Paths:
--------------
MacRuby/trunk/array.c
MacRuby/trunk/compiler.cpp
MacRuby/trunk/compiler.h
MacRuby/trunk/dispatcher.cpp
MacRuby/trunk/include/ruby/ruby.h
MacRuby/trunk/main.cpp
MacRuby/trunk/rakelib/builder.rb
MacRuby/trunk/ruby.c
MacRuby/trunk/vm.cpp
MacRuby/trunk/vm.h
MacRuby/trunk/vm_eval.c
Added Paths:
-----------
MacRuby/trunk/MacRubyDebuggerConnector.h
MacRuby/trunk/MacRubyDebuggerConnector.m
MacRuby/trunk/bin/rubyd
MacRuby/trunk/debugger.cpp
MacRuby/trunk/debugger.h
Added: MacRuby/trunk/MacRubyDebuggerConnector.h
===================================================================
--- MacRuby/trunk/MacRubyDebuggerConnector.h (rev 0)
+++ MacRuby/trunk/MacRubyDebuggerConnector.h 2010-01-17 04:37:52 UTC (rev 3287)
@@ -0,0 +1,59 @@
+/*
+ * MacRuby Debugger Connector.
+ *
+ * This is an interface to the MacRuby runtime when running in debug mode.
+ * It is used by MacRuby debugger clients, such as macrubyd or Xcode.
+ * This file when compiled separately must be compiled with garbage collection
+ * enabled.
+ *
+ * This file is covered by the Ruby license. See COPYING for more details.
+ *
+ * Copyright (C) 2010, Apple Inc. All rights reserved.
+ */
+
+#import <Foundation/Foundation.h>
+
+typedef unsigned int breakpoint_t;
+
+ at interface MacRubyDebuggerConnector : NSObject
+{
+ NSString *_socketPath;
+ NSMutableArray *_arguments;
+ NSTask *_task;
+ NSFileHandle *_socket;
+ NSString *_location;
+}
+
+- (id)initWithArguments:(NSArray *)arguments;
+
+// Execution control.
+
+- (void)startExecution;
+- (void)continueExecution;
+- (void)stepExecution;
+- (void)stopExecution;
+
+// Current state.
+
+- (NSString *)location;
+- (NSArray *)localVariables;
+- (NSArray *)backtrace;
+- (void)setFrame:(unsigned int)frame;
+
+// Breakpoints.
+
+- (breakpoint_t)addBreakPointAtPath:(NSString *)path line:(unsigned int)line
+ condition:(NSString *)condition;
+
+- (void)enableBreakPoint:(breakpoint_t)bp;
+- (void)disableBreakPoint:(breakpoint_t)bp;
+- (void)deleteBreakPoint:(breakpoint_t)bp;
+- (void)setCondition:(NSString *)condition forBreakPoint:(breakpoint_t)bp;
+
+- (NSArray *)allBreakPoints;
+
+// Context evaluation.
+
+- (NSString *)evaluateExpression:(NSString *)expression;
+
+ at end
Added: MacRuby/trunk/MacRubyDebuggerConnector.m
===================================================================
--- MacRuby/trunk/MacRubyDebuggerConnector.m (rev 0)
+++ MacRuby/trunk/MacRubyDebuggerConnector.m 2010-01-17 04:37:52 UTC (rev 3287)
@@ -0,0 +1,270 @@
+/*
+ * MacRuby Debugger Connector.
+ *
+ * This file is covered by the Ruby license. See COPYING for more details.
+ *
+ * Copyright (C) 2010, Apple Inc. All rights reserved.
+ */
+
+#import "MacRubyDebuggerConnector.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+ at implementation MacRubyDebuggerConnector
+
+- (NSData *)_recv
+{
+ assert(_socket != nil);
+ return [_socket availableData];
+}
+
+- (const char *)_recvCString
+{
+ return (const char *)[[self _recv] bytes];
+}
+
+- (NSString *)_recvString
+{
+ return [[NSString alloc] initWithData:[self _recv]
+ encoding:NSASCIIStringEncoding];
+}
+
+- (void)_send:(NSData *)data
+{
+ assert(_socket != nil);
+ [_socket writeData:data];
+}
+
+- (void)_sendCString:(const char *)str
+{
+ [self _send:[NSData dataWithBytes:str length:strlen(str)]];
+}
+
+-(void)_readLocation
+{
+ _location = [self _recvString];
+ if ([_location length] == 0) {
+ _location = nil;
+ }
+}
+
+- (id)initWithArguments:(NSArray *)arguments
+{
+ self = [super init];
+ if (self != nil) {
+ // Generate the socket path.
+ const char *tmpdir = getenv("TMPDIR");
+ assert(tmpdir != NULL);
+ char path[PATH_MAX];
+ snprintf(path, sizeof path, "%s/macrubyd-XXXXXX", tmpdir);
+ assert(mktemp(path) != NULL);
+ _socketPath = [[NSFileManager defaultManager]
+ stringWithFileSystemRepresentation:path length:strlen(path)];
+
+ // Setup arguments.
+ _arguments = [NSMutableArray new];
+ [_arguments addObject:@"--debug-mode"];
+ [_arguments addObject:_socketPath];
+ [_arguments addObjectsFromArray:arguments];
+
+ _task = nil;
+ _socket = nil;
+ _location = nil;
+ }
+ return self;
+}
+
+- (void)finalize
+{
+ [super finalize];
+ [self stopExecution];
+}
+
+- (void)startExecution
+{
+ if (_task == nil || ![_task isRunning]) {
+ // Create task.
+ _task = [NSTask new];
+ [_task setLaunchPath:@"./miniruby"]; // XXX
+ [_task setArguments:_arguments];
+
+ // Launch the remote process.
+ [[NSFileManager defaultManager] removeItemAtPath:_socketPath error:nil];
+ [_task launch];
+
+ // Wait until the socket file is available.
+ while (true) {
+ if ([[NSFileManager defaultManager] fileExistsAtPath:_socketPath]) {
+ break;
+ }
+ if (![_task isRunning]) {
+ // Something wrong happened at the very beginning, most likely
+ // a command-line argument error.
+ _task = nil;
+ return;
+ }
+ assert([_task isRunning]); // XXX raise exception
+ usleep(500000);
+ }
+
+ // Create the socket.
+ const int fd = socket(PF_LOCAL, SOCK_STREAM, 0);
+ if (fd == -1) {
+ // XXX should raise exception.
+ perror("socket()");
+ exit(1);
+ }
+
+ // Prepare the name.
+ struct sockaddr_un name;
+ name.sun_family = PF_LOCAL;
+ strncpy(name.sun_path, [_socketPath fileSystemRepresentation],
+ sizeof(name.sun_path));
+
+ // Connect.
+ if (connect(fd, (struct sockaddr *)&name, SUN_LEN(&name)) == -1) {
+ // XXX should raise exception.
+ perror("connect()");
+ exit(1);
+ }
+
+ // Good!
+ _socket = [[NSFileHandle alloc] initWithFileDescriptor:fd];
+ [self _readLocation];
+ }
+}
+
+- (void)continueExecution
+{
+ [self _sendCString:"continue"];
+ [self _readLocation];
+}
+
+- (void)stepExecution
+{
+ [self _sendCString:"next"];
+ [self _readLocation];
+}
+
+- (void)stopExecution
+{
+ if (_task != nil) {
+ [_task terminate];
+ _task = nil;
+ }
+ _location = nil;
+ [[NSFileManager defaultManager] removeItemAtPath:_socketPath error:nil];
+}
+
+- (NSString *)location
+{
+ return _location;
+}
+
+- (NSArray *)localVariables
+{
+ return nil;
+}
+
+- (NSArray *)backtrace
+{
+ [self _sendCString:"backtrace"];
+ return [[self _recvString] componentsSeparatedByString:@"\n"];
+}
+
+- (void)setFrame:(unsigned int)frame
+{
+ char buf[512];
+ snprintf(buf, sizeof buf, "frame %d", frame);
+ [self _sendCString:buf];
+}
+
+- (breakpoint_t)addBreakPointAtPath:(NSString *)path line:(unsigned int)line
+ condition:(NSString *)condition
+{
+ char buf[512];
+ snprintf(buf, sizeof buf, "break %s:%d", [path fileSystemRepresentation],
+ line);
+ [self _sendCString:buf];
+ return [[self _recvString] integerValue];
+}
+
+- (void)enableBreakPoint:(breakpoint_t)bp
+{
+ char buf[512];
+ snprintf(buf, sizeof buf, "enable %d", bp);
+ [self _sendCString:buf];
+}
+
+- (void)disableBreakPoint:(breakpoint_t)bp
+{
+ char buf[512];
+ snprintf(buf, sizeof buf, "disable %d", bp);
+ [self _sendCString:buf];
+}
+
+- (void)deleteBreakPoint:(breakpoint_t)bp
+{
+ char buf[512];
+ snprintf(buf, sizeof buf, "delete %d", bp);
+ [self _sendCString:buf];
+}
+
+- (void)setCondition:(NSString *)condition forBreakPoint:(breakpoint_t)bp
+{
+ char buf[512];
+ snprintf(buf, sizeof buf, "condition %d %s", bp, [condition UTF8String]);
+ [self _sendCString:buf];
+}
+
+- (NSDictionary *)_parseBreakPointDescription:(NSString *)desc
+{
+ NSArray *ary = [desc componentsSeparatedByString:@","];
+ if ([ary count] == 0) {
+ return nil;
+ }
+ NSMutableDictionary *dict = [NSMutableDictionary new];
+ unsigned i, count;
+ for (i = 0, count = [ary count]; i < count; i++) {
+ NSArray *fields = [[ary objectAtIndex:i]
+ componentsSeparatedByString:@"="];
+ if ([fields count] == 2) {
+ NSString *key = [fields objectAtIndex:0];
+ NSString *val = [fields objectAtIndex:1];
+ [dict setObject:val forKey:key];
+ }
+ }
+ if ([dict count] == 0) {
+ return nil;
+ }
+ return dict;
+}
+
+
+- (NSArray *)allBreakPoints
+{
+ [self _sendCString:"info breakpoints"];
+ NSArray *ary = [[self _recvString] componentsSeparatedByString:@"\n"];
+ NSMutableArray *ary2 = [NSMutableArray new];
+ unsigned i, count;
+ for (i = 0, count = [ary count]; i < count; i++) {
+ NSString *desc = [ary objectAtIndex:i];
+ NSDictionary *dict = [self _parseBreakPointDescription:desc];
+ if (dict != nil) {
+ [ary2 addObject:dict];
+ }
+ }
+ return ary2;
+}
+
+- (NSString *)evaluateExpression:(NSString *)expression
+{
+ char buf[512];
+ snprintf(buf, sizeof buf, "eval %s", [expression UTF8String]);
+ [self _sendCString:buf];
+ return [self _recvString];
+}
+
+ at end
Modified: MacRuby/trunk/array.c
===================================================================
--- MacRuby/trunk/array.c 2010-01-17 04:36:20 UTC (rev 3286)
+++ MacRuby/trunk/array.c 2010-01-17 04:37:52 UTC (rev 3287)
@@ -1498,10 +1498,8 @@
static VALUE
rb_ary_each_imp(VALUE ary, SEL sel)
{
- long i;
-
RETURN_ENUMERATOR(ary, 0, 0);
- for (i = 0; i < RARRAY_LEN(ary); i++) {
+ for (long i = 0; i < RARRAY_LEN(ary); i++) {
rb_yield(RARRAY_AT(ary, i));
RETURN_IF_BROKEN();
}
@@ -1532,10 +1530,9 @@
static VALUE
rb_ary_each_index(VALUE ary, SEL sel)
{
- long i, n;
RETURN_ENUMERATOR(ary, 0, 0);
- for (i = 0, n = RARRAY_LEN(ary); i < n; i++) {
+ for (long i = 0, n = RARRAY_LEN(ary); i < n; i++) {
rb_yield(LONG2NUM(i));
RETURN_IF_BROKEN();
}
@@ -1560,14 +1557,13 @@
static VALUE
rb_ary_reverse_each(VALUE ary, SEL sel)
{
- long n, len;
-
RETURN_ENUMERATOR(ary, 0, 0);
- len = RARRAY_LEN(ary);
+
+ long len = RARRAY_LEN(ary);
while (len--) {
rb_yield(RARRAY_AT(ary, len));
RETURN_IF_BROKEN();
- n = RARRAY_LEN(ary);
+ const long n = RARRAY_LEN(ary);
if (n < len) {
len = n;
}
@@ -1707,7 +1703,7 @@
taint = Qtrue;
}
if (OBJ_UNTRUSTED(tmp)) {
- untrust = Qtrue;
+ untrust = Qtrue;
}
}
Added: MacRuby/trunk/bin/rubyd
===================================================================
--- MacRuby/trunk/bin/rubyd (rev 0)
+++ MacRuby/trunk/bin/rubyd 2010-01-17 04:37:52 UTC (rev 3287)
@@ -0,0 +1,214 @@
+#!/usr/bin/ruby
+# MacRuby Debugger.
+#
+# This file is covered by the Ruby license.
+#
+# Copyright (C) 2010, Apple Inc
+
+require 'readline'
+
+class Debugger
+ def initialize(args)
+ @terran_mode = args.delete('--terran')
+ raise "need at least one argument" if args.empty?
+ @connector = ::MacRubyDebuggerConnector.alloc.initWithArguments(args)
+ end
+
+ def run
+ $stdout.puts "Starting program."
+ @connector.startExecution
+ running = true
+
+ while true do
+ if @connector.location == nil and running
+ # Looks like the program terminated.
+ $stdout.puts "Program exited."
+ running = false
+ end
+
+ # Grab a command from the prompt.
+ command = prompt
+ break unless command
+ command.strip!
+ next if command.empty?
+
+ case command
+ when 'c', 'continue'
+ if running
+ @connector.continueExecution
+ else
+ $stdout.puts "Program not running."
+ end
+ when 'n', 'next'
+ if running
+ @connector.stepExecution
+ else
+ $stdout.puts "Program not running."
+ end
+ when 'r', 'run'
+ if running
+ $stdout.print <<EOS.chomp
+The program being debugged has been started already.
+Start it from the beginning? (y or n)
+EOS
+ if $stdin.gets.chomp == 'y'
+ @connector.stopExecution
+ @connector.startExecution
+ running = true
+ else
+ $stdout.puts "Program not restarted."
+ end
+ else
+ $stdout.puts "Starting program."
+ @connector.startExecution
+ running = true
+ end
+ when 'stop'
+ if running
+ @connector.stopExecution
+ else
+ $stdout.puts "Program not running."
+ end
+ when /^b(?:reak)?\s+(.+):(\d+)(?:\s+if\s+(.+))?/
+ if running
+ file = $1.strip
+ line = $2.to_i
+ cond = $3
+ bp = @connector.addBreakPointAtPath(file, line: line,
+ condition: nil)
+ if cond
+ cond.strip!
+ unless cond.empty?
+ @connector.setCondition(cond, forBreakPoint: bp)
+ end
+ end
+ $stdout.puts "Added breakpoint #{bp}."
+ else
+ $stdout.puts "Program not running."
+ end
+ when /^enabled\s+(\d+)/
+ if running
+ bp = $1.to_i
+ @connector.enableBreakPoint(bp)
+ $stdout.puts "Enabled breakpoint #{bp}."
+ else
+ $stdout.puts "Program not running."
+ end
+ when /^disable\s+(\d+)/
+ if running
+ bp = $1.to_i
+ @connector.disableBreakPoint(bp)
+ $stdout.puts "Disabled breakpoint #{bp}."
+ else
+ $stdout.puts "Program not running."
+ end
+ when /^delete\s+(\d+)/
+ if running
+ bp = $1.to_i
+ @connector.deleteBreakPoint(bp)
+ $stdout.puts "Deleted breakpoint #{bp}."
+ else
+ $stdout.puts "Program not running."
+ end
+ when /^condition\s+(\d+)\s+(.+)/
+ if running
+ bp = $1.to_i
+ cond = $2.strip
+ @connector.setCondition(cond, forBreakPoint: bp)
+ else
+ $stdout.puts "Program not running."
+ end
+ when 'bt', 'backtrace'
+ if running
+ @connector.backtrace.each_with_index do |frame, n|
+ $stdout.puts "#%d\t%s" % [n, frame]
+ end
+ else
+ $stdout.puts "Program not running."
+ end
+ when /^fr(?:ame)?\s+(\d+)/
+ frid = $1.to_i
+ @connector.setFrame(frid)
+ when /^info\s+breakpoints/
+ if running
+ $stdout.puts "Program not running." and next unless running
+ bps = @connector.allBreakPoints
+ if bps.empty?
+ $stdout.puts "No breakpoint."
+ else
+ $stdout.puts "ID\t\tSTATE\t\tLOCATION"
+ bps.each do |bp|
+ $stdout.puts "%s\t\t%s\t\t%s" % [bp['id'],
+ bp['enabled'] == '1' ? "enabled" : "disabled",
+ bp['location']]
+ end
+ end
+ else
+ $stdout.puts "Program not running."
+ end
+ when /^info\s+threads/, /^thread\s+(\d+)/
+ $stdout.puts "Not yet implemented."
+ when /^p\s+(.+)/
+ if running
+ expr = $1.strip
+ $stdout.puts "=> #{@connector.evaluateExpression(expr)}"
+ else
+ $stdout.puts "Program not running."
+ end
+ when 'help'
+ help
+ when 'quit', 'exit'
+ @connector.stopExecution
+ exit
+ else
+ $stdout.puts "Unknown command `#{command}', type `help' for help."
+ end
+ end
+ end
+
+ private
+
+ def help
+ $stdout.puts <<EOS
+break <file:line> Set up and enable a breakpoint at given file/line.
+break <...> if <expr> Same as above but also set up a given condition.
+delete <bp-id> Delete the given breakpoint.
+disable <bp-id> Disable the given breakpoint.
+enable <bp-id> Enable the given breakpoint.
+condition <bp-id> <expr> Set up a condition for the given breakpoint.
+info breakpoints List all breakpoints.
+info threads List all threads.
+next Go to the next expression.
+continue Continue the program's execution.
+run Start the program's execution.
+stop Stop the program's execution.
+backtrace Show current backtrace.
+backtrace full Show a full backtrace (for every thread).
+thread <thread-id> Switch to the given thread.
+frame <frame-id> Switch to the given frame.
+p <expression> Evaluate the given expression and print the result.
+help Print this message.
+quit Terminate the debugged program and exit.
+EOS
+ end
+
+ PROMPTS = [
+ "Explorer reporting.",
+ "Ah, greetings command!",
+ "Transmit orders.",
+ "Receiving headquarters!",
+ "We have you on visual.",
+ "Let's roll!",
+ "Excellent!",
+ "Commencing!",
+ "Affirmative, sir."
+ ]
+ def prompt
+ if @terran_mode
+ $stdout.puts PROMPTS.sample
+ end
+ Readline.readline("#{@connector.location}> ", true)
+ end
+end
+
+Debugger.new(ARGV).run
Modified: MacRuby/trunk/compiler.cpp
===================================================================
--- MacRuby/trunk/compiler.cpp 2010-01-17 04:36:20 UTC (rev 3286)
+++ MacRuby/trunk/compiler.cpp 2010-01-17 04:37:52 UTC (rev 3287)
@@ -29,13 +29,15 @@
llvm::Module *RoxorCompiler::module = NULL;
RoxorCompiler *RoxorCompiler::shared = NULL;
-RoxorCompiler::RoxorCompiler(void)
+RoxorCompiler::RoxorCompiler(bool _debug_mode)
{
assert(RoxorCompiler::module != NULL);
debug_info = new DIFactory(*RoxorCompiler::module);
+ debug_mode = _debug_mode;
fname = "";
inside_eval = false;
+ current_line = 0;
bb = NULL;
entry_bb = NULL;
@@ -141,6 +143,7 @@
setScopeFunc = NULL;
setCurrentClassFunc = NULL;
getCacheFunc = NULL;
+ debugTrapFunc = NULL;
VoidTy = Type::getVoidTy(context);
Int1Ty = Type::getInt1Ty(context);
@@ -182,7 +185,7 @@
}
RoxorAOTCompiler::RoxorAOTCompiler(void)
-: RoxorCompiler()
+: RoxorCompiler(false)
{
cObject_gvar = NULL;
cStandardError_gvar = NULL;
@@ -3055,7 +3058,12 @@
}
printf("... %s\n", ruby_node_name(nd_type(node)));
#endif
- current_line = nd_line(node);
+ if (current_line != nd_line(node)) {
+ current_line = nd_line(node);
+ if (debug_mode) {
+ compile_debug_trap();
+ }
+ }
switch (nd_type(node)) {
case NODE_SCOPE:
@@ -4301,13 +4309,14 @@
// object, let's create it.
// (Note: this won't work if the method is aliased, but we can
// live with that for now)
- if (!super_call
+ if (debug_mode
+ || (!super_call
&& (sel == selEval
|| sel == selInstanceEval
|| sel == selClassEval
|| sel == selModuleEval
|| sel == selLocalVariables
- || sel == selBinding)) {
+ || sel == selBinding))) {
compile_binding();
}
Modified: MacRuby/trunk/compiler.h
===================================================================
--- MacRuby/trunk/compiler.h 2010-01-17 04:36:20 UTC (rev 3286)
+++ MacRuby/trunk/compiler.h 2010-01-17 04:37:52 UTC (rev 3287)
@@ -32,7 +32,7 @@
static llvm::Module *module;
static RoxorCompiler *shared;
- RoxorCompiler(void);
+ RoxorCompiler(bool debug_mode);
virtual ~RoxorCompiler(void) { }
void set_fname(const char *_fname);
@@ -66,6 +66,7 @@
DICompileUnit debug_compile_unit;
DISubprogram debug_subprogram;
+ bool debug_mode;
const char *fname;
bool inside_eval;
@@ -191,6 +192,7 @@
Function *setScopeFunc;
Function *setCurrentClassFunc;
Function *getCacheFunc;
+ Function *debugTrapFunc;
Constant *zeroVal;
Constant *oneVal;
@@ -337,6 +339,7 @@
Value *slot);
Value *compile_conversion_to_ruby(const char *type,
const Type *llvm_type, Value *val);
+ void compile_debug_trap(void);
Value *compile_slot_cache(ID id);
virtual Value *gen_slot_cache(ID id);
Added: MacRuby/trunk/debugger.cpp
===================================================================
--- MacRuby/trunk/debugger.cpp (rev 0)
+++ MacRuby/trunk/debugger.cpp 2010-01-17 04:37:52 UTC (rev 3287)
@@ -0,0 +1,422 @@
+/*
+ * MacRuby debugger.
+ *
+ * This file is covered by the Ruby license. See COPYING for more details.
+ *
+ * Copyright (C) 2010, Apple Inc. All rights reserved.
+ */
+
+#include <llvm/Module.h>
+#include <llvm/DerivedTypes.h>
+#include <llvm/Constants.h>
+#include <llvm/CallingConv.h>
+#include <llvm/Instructions.h>
+#include <llvm/ModuleProvider.h>
+#include <llvm/Intrinsics.h>
+#include <llvm/Analysis/DebugInfo.h>
+#include <llvm/ExecutionEngine/JIT.h>
+#include <llvm/PassManager.h>
+#include <llvm/Target/TargetData.h>
+using namespace llvm;
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "ruby/ruby.h"
+#include "ruby/node.h"
+#include "vm.h"
+#include "compiler.h"
+#include "debugger.h"
+
+void
+RoxorCompiler::compile_debug_trap(void)
+{
+ if (bb == NULL || inside_eval) {
+ return;
+ }
+
+ if (debugTrapFunc == NULL) {
+ // void rb_vm_debug_trap(const char *file, int line,
+ // VALUE self, rb_vm_block_t *current_block, int lvars_size, ...);
+ std::vector<const Type *> types;
+ types.push_back(PtrTy);
+ types.push_back(Int32Ty);
+ types.push_back(RubyObjTy);
+ types.push_back(PtrTy);
+ types.push_back(Int32Ty);
+ FunctionType *ft = FunctionType::get(VoidTy, types, true);
+ debugTrapFunc = cast<Function>
+ (module->getOrInsertFunction( "rb_vm_debug_trap", ft));
+ }
+
+ std::vector<Value *> params;
+ params.push_back(compile_const_pointer((void *)fname));
+ params.push_back(ConstantInt::get(Int32Ty, current_line));
+ params.push_back(current_self);
+ params.push_back(running_block == NULL
+ ? compile_const_pointer(NULL) : running_block);
+
+ // Lvars.
+ params.push_back(ConstantInt::get(Int32Ty, (int)lvars.size()));
+ for (std::map<ID, Value *>::iterator iter = lvars.begin();
+ iter != lvars.end(); ++iter) {
+
+ ID lvar = iter->first;
+ params.push_back(compile_id(lvar));
+ params.push_back(iter->second);
+ }
+
+ CallInst::Create(debugTrapFunc, params.begin(), params.end(), "", bb);
+}
+
+extern "C"
+void
+rb_vm_debug_trap(const char *file, const int line, VALUE self,
+ rb_vm_block_t *current_block, int lvars_size, ...)
+{
+ if (RoxorDebugger::shared == NULL) {
+ RoxorDebugger::shared = RoxorDebugger::unix_server();
+ }
+
+ va_list args;
+ va_start(args, lvars_size);
+ RoxorDebugger::shared->trap(file, line, self, current_block,
+ lvars_size, args);
+ va_end(args);
+}
+
+unsigned int RoxorBreakPoint::IDs = 0;
+RoxorDebugger *RoxorDebugger::shared = NULL;
+
+RoxorDebugger *
+RoxorDebugger::unix_server(void)
+{
+ // Create the socket.
+ const int fd = socket(PF_LOCAL, SOCK_STREAM, 0);
+ if (fd < 0) {
+ perror("socket()");
+ exit(1);
+ }
+
+ // Bind it to the given path.
+ assert(ruby_debug_socket_path != Qfalse);
+ const char *path = RSTRING_PTR(ruby_debug_socket_path);
+ struct sockaddr_un name;
+ name.sun_family = PF_LOCAL;
+ strncpy(name.sun_path, path, sizeof(name.sun_path));
+ if (bind(fd, (struct sockaddr *)&name, SUN_LEN(&name)) != 0) {
+ perror("bind()");
+ exit(1);
+ }
+
+ // Prepare for listening, backlog of 1 connection.
+ if (listen(fd, 1) != 0) {
+ perror("listen()");
+ exit(1);
+ }
+
+ // Accept first connection.
+ struct sockaddr_un remote;
+ socklen_t remotelen = sizeof(remote);
+ const int pipe = accept(fd, (struct sockaddr *)&remote, &remotelen);
+ if (socket < 0) {
+ perror("accept()");
+ exit(1);
+ }
+
+//printf("DBG: connected with fd %d\n", pipe);
+
+ return new RoxorDebugger(pipe);
+}
+
+RoxorDebugger::RoxorDebugger(int _pipe)
+{
+ pipe = _pipe;
+ binding = NULL;
+ breakpoint = NULL;
+ frame = 0;
+
+ // We want to break at the very first line.
+ break_at_next = true;
+}
+
+RoxorDebugger::~RoxorDebugger()
+{
+ close(pipe);
+}
+
+bool
+RoxorDebugger::send(std::string &data)
+{
+//printf("DBG: send %s\n", data.c_str());
+ const ssize_t len = ::send(pipe, data.c_str(), data.length(), 0);
+ if (len != (ssize_t)data.length()) {
+ if (len == -1) {
+ perror("send()");
+ }
+ else {
+ fprintf(stderr, "expected to send %ld bytes instead of %ld\n",
+ data.length(), len);
+ }
+ return false;
+ }
+ return true;
+}
+
+bool
+RoxorDebugger::recv(std::string &data)
+{
+ char buf[512];
+ const ssize_t len = ::recv(pipe, buf, sizeof buf, 0);
+ if (len == -1) {
+ perror("recv()");
+ return false;
+ }
+ data.clear();
+ data.append(buf, len);
+//printf("DBG: recv %s\n", data.c_str());
+ return true;
+}
+
+void
+RoxorDebugger::trap(const char *file, const int line, VALUE self,
+ rb_vm_block_t *block, int lvars_size, va_list lvars)
+{
+ // Compute location.
+ char buf[100];
+ snprintf(buf, sizeof buf, "%s:%d", file, line);
+ std::string loc(buf);
+
+ // Should we break here?
+ bool should_break = false;
+ RoxorBreakPoint *bp = NULL;
+ if (break_at_next) {
+ should_break = true;
+ break_at_next = false;
+ }
+ else {
+ std::map<std::string, RoxorBreakPoint *>::iterator iter =
+ breakpoints.find(loc);
+ if (iter != breakpoints.end()) {
+ bp = iter->second;
+ if (bp->enabled) {
+ if (bp->condition.length() > 0) {
+ VALUE obj = evaluate_expression(self, block, lvars_size,
+ lvars, bp->condition);
+ if (obj != Qnil && obj != Qfalse) {
+ should_break = true;
+ }
+ }
+ else {
+ should_break = true;
+ }
+ }
+ }
+ }
+
+ if (!should_break) {
+ return;
+ }
+
+ // Send the current location to the client.
+ if (!send(loc)) {
+ return;
+ }
+
+ // Invalidate latest binding.
+ if (binding != NULL) {
+ GC_RELEASE(binding);
+ binding = NULL;
+ }
+ frame = 0;
+
+ // Enter the main loop.
+ std::string data;
+ while (true) {
+ // Receive command.
+ if (!recv(data)) {
+ return;
+ }
+ const char *cmd = data.c_str();
+
+ // Handle command.
+ if (strcmp(cmd, "exit") == 0) {
+ exit(0);
+ }
+ if (strcmp(cmd, "continue") == 0) {
+ break;
+ }
+ if (strcmp(cmd, "next") == 0) {
+ break_at_next = true;
+ break;
+ }
+ if (strncmp(cmd, "break ", 6) == 0) {
+ std::string location = data.substr(6);
+ if (location.length() >= 3) {
+ unsigned int id = add_breakpoint(location);
+ char buf[10];
+ snprintf(buf, sizeof buf, "%d", id);
+ send(buf);
+ continue;
+ }
+ }
+ if (strncmp(cmd, "enable ", 7) == 0) {
+ const unsigned int bpid = (unsigned int)strtol(cmd + 7, NULL, 10);
+ RoxorBreakPoint *bp = find_breakpoint(bpid);
+ if (bp != NULL) {
+ bp->enabled = true;
+ }
+ continue;
+ }
+ if (strncmp(cmd, "disable ", 8) == 0) {
+ const unsigned int bpid = (unsigned int)strtol(cmd + 8, NULL, 10);
+ RoxorBreakPoint *bp = find_breakpoint(bpid);
+ if (bp != NULL) {
+ bp->enabled = false;
+ }
+ continue;
+ }
+ if (strncmp(cmd, "delete ", 7) == 0) {
+ const unsigned int bpid = (unsigned int)strtol(cmd + 7, NULL, 10);
+ delete_breakpoint(bpid);
+ continue;
+ }
+ if (strncmp(cmd, "condition ", 10) == 0) {
+ const char *p = strchr(cmd + 10, ' ');
+ if (p != NULL) {
+ std::string bpstr = data.substr(10, p - cmd - 10);
+ const unsigned int bpid = (unsigned int)strtol(bpstr.c_str(),
+ NULL, 10);
+ std::string condstr = data.substr(10 + bpstr.length());
+ RoxorBreakPoint *bp = find_breakpoint(bpid);
+ if (bp != NULL) {
+ bp->condition = condstr;
+ }
+ continue;
+ }
+ }
+ if (strcmp(cmd, "info breakpoints") == 0) {
+ std::string resp;
+ for (std::map<std::string, RoxorBreakPoint *>::iterator iter
+ = breakpoints.begin();
+ iter != breakpoints.end();
+ ++iter) {
+ char buf[100];
+ snprintf(buf, sizeof buf, "id=%d,location=%s,enabled=%d\n",
+ iter->second->id, iter->first.c_str(),
+ iter->second->enabled);
+ resp.append(buf);
+ }
+ if (resp.length() == 0) {
+ resp.append("nil");
+ }
+ send(resp);
+ continue;
+ }
+ if (strcmp(cmd, "backtrace") == 0) {
+ VALUE bt = rb_vm_backtrace(100);
+ VALUE str = rb_ary_join(bt, rb_str_new2("\n"));
+ send(RSTRING_PTR(str));
+ continue;
+ }
+ if (strncmp(cmd, "frame ", 6) == 0) {
+ unsigned int frid = (unsigned int)strtol(cmd + 6, NULL, 10);
+ if (frame != frid) {
+ frame = frid;
+ if (binding != NULL) {
+ GC_RELEASE(binding);
+ binding = NULL;
+ }
+ // TODO update location
+ }
+ continue;
+ }
+ if (strncmp(cmd, "eval ", 5) == 0) {
+ std::string expr = data.substr(5);
+ if (expr.length() > 0) {
+ VALUE obj = evaluate_expression(self, block, lvars_size, lvars,
+ expr);
+ send(RSTRING_PTR(rb_inspect(obj)));
+ continue;
+ }
+ }
+
+ // Unknown command, let's exit the program. This should never happen!
+ fprintf(stderr, "unknown command: %s\n", cmd);
+ exit(1);
+ }
+}
+
+unsigned int
+RoxorDebugger::add_breakpoint(std::string &location)
+{
+ std::map<std::string, RoxorBreakPoint *>::iterator iter
+ = breakpoints.find(location);
+
+ RoxorBreakPoint *bp;
+ if (iter == breakpoints.end()) {
+ bp = new RoxorBreakPoint();
+ breakpoints[location] = bp;
+ }
+ else {
+ bp = iter->second;
+ }
+
+ return bp->id;
+}
+
+VALUE
+RoxorDebugger::evaluate_expression(VALUE self, rb_vm_block_t *block,
+ int lvars_size, va_list lvars, std::string expr)
+{
+ if (binding == NULL) {
+ if (frame == 0) {
+ binding = rb_vm_create_binding(self, block, lvars_size, lvars,
+ false);
+ }
+ else {
+ binding = GET_VM()->get_binding(frame - 1);
+ assert(binding != NULL);
+ }
+ GC_RETAIN(binding);
+ }
+
+ try {
+ return rb_vm_eval_string(self, 0, rb_str_new2(expr.c_str()), binding,
+ "(eval)", 1);
+ }
+ catch (...) {
+ rb_vm_print_current_exception();
+ return Qnil;
+ }
+}
+
+RoxorBreakPoint *
+RoxorDebugger::find_breakpoint(unsigned int bpid)
+{
+ for (std::map<std::string, RoxorBreakPoint *>::iterator iter
+ = breakpoints.begin();
+ iter != breakpoints.end();
+ ++iter) {
+ if (iter->second->id == bpid) {
+ return iter->second;
+ }
+ }
+ return NULL;
+}
+
+bool
+RoxorDebugger::delete_breakpoint(unsigned int bpid)
+{
+ for (std::map<std::string, RoxorBreakPoint *>::iterator iter
+ = breakpoints.begin();
+ iter != breakpoints.end();
+ ++iter) {
+ if (iter->second->id == bpid) {
+ breakpoints.erase(iter);
+ return true;
+ }
+ }
+ return false;
+}
Added: MacRuby/trunk/debugger.h
===================================================================
--- MacRuby/trunk/debugger.h (rev 0)
+++ MacRuby/trunk/debugger.h 2010-01-17 04:37:52 UTC (rev 3287)
@@ -0,0 +1,65 @@
+/*
+ * MacRuby debugger.
+ *
+ * This file is covered by the Ruby license. See COPYING for more details.
+ *
+ * Copyright (C) 2010, Apple Inc. All rights reserved.
+ */
+
+#ifndef __DEBUGGER_H_
+#define __DEBUGGER_H_
+
+#if defined(__cplusplus)
+
+class RoxorBreakPoint {
+ public:
+ static unsigned int IDs;
+ unsigned int id;
+ bool enabled;
+ std::string condition;
+
+ RoxorBreakPoint(void) {
+ id = ++IDs;
+ enabled = true;
+ }
+};
+
+class RoxorDebugger {
+ private:
+ std::map<std::string, RoxorBreakPoint *> breakpoints;
+ bool break_at_next;
+ std::string location;
+ RoxorBreakPoint *breakpoint;
+ rb_vm_binding_t *binding;
+ unsigned int frame;
+ int pipe;
+
+ bool send(std::string &data);
+ bool send(const char *str) {
+ std::string s(str);
+ return send(s);
+ }
+ bool recv(std::string &data);
+
+ unsigned int add_breakpoint(std::string &location);
+ RoxorBreakPoint *find_breakpoint(unsigned int bpid);
+ bool delete_breakpoint(unsigned int bpid);
+
+ VALUE evaluate_expression(VALUE self, rb_vm_block_t *block,
+ int lvars_size, va_list lvars, std::string expr);
+
+ public:
+ static RoxorDebugger *shared;
+
+ static RoxorDebugger *unix_server(void);
+
+ RoxorDebugger(int pipe);
+ ~RoxorDebugger();
+
+ void trap(const char *file, const int line, VALUE self,
+ rb_vm_block_t *block, int lvars_size, va_list lvars);
+};
+
+#endif /* __cplusplus */
+
+#endif /* __DEBUGGER_H_ */
Modified: MacRuby/trunk/dispatcher.cpp
===================================================================
--- MacRuby/trunk/dispatcher.cpp 2010-01-17 04:36:20 UTC (rev 3286)
+++ MacRuby/trunk/dispatcher.cpp 2010-01-17 04:37:52 UTC (rev 3287)
@@ -1053,11 +1053,15 @@
RoxorVM *vm = GET_VM();
+ struct Finally {
+ RoxorVM *vm;
+ Finally(RoxorVM *_vm) { vm = _vm; }
+ ~Finally() { vm->pop_current_binding(); }
+ } finalizer(vm);
+
VALUE retval = __rb_vm_dispatch(vm, cache, top, self, NULL, sel, block,
opt, argc, argv);
- vm->pop_current_binding();
-
return retval;
}
Modified: MacRuby/trunk/include/ruby/ruby.h
===================================================================
--- MacRuby/trunk/include/ruby/ruby.h 2010-01-17 04:36:20 UTC (rev 3286)
+++ MacRuby/trunk/include/ruby/ruby.h 2010-01-17 04:37:52 UTC (rev 3287)
@@ -1000,9 +1000,15 @@
VALUE rb_equal(VALUE,VALUE);
+// Flags.
RUBY_EXTERN VALUE ruby_verbose, ruby_debug;
+
+// AOT compiler.
RUBY_EXTERN VALUE ruby_aot_compile, ruby_aot_init_func;
+// Debugger.
+RUBY_EXTERN VALUE ruby_debug_socket_path;
+
PRINTF_ARGS(NORETURN(void rb_raise(VALUE, const char*, ...)), 2, 3);
PRINTF_ARGS(NORETURN(void rb_fatal(const char*, ...)), 1, 2);
PRINTF_ARGS(NORETURN(void rb_bug(const char*, ...)), 1, 2);
Modified: MacRuby/trunk/main.cpp
===================================================================
--- MacRuby/trunk/main.cpp 2010-01-17 04:36:20 UTC (rev 3286)
+++ MacRuby/trunk/main.cpp 2010-01-17 04:37:52 UTC (rev 3287)
@@ -5,6 +5,8 @@
* Copyright (C) 1993-2007 Yukihiro Matsumoto
*/
+#include "llvm.h"
+
#undef RUBY_EXPORT
#include "ruby.h"
#include "ruby/node.h"
@@ -12,11 +14,7 @@
#include <locale.h>
#endif
-extern "C" {
- void rb_vm_print_current_exception(void);
- void rb_vm_aot_compile(NODE *);
- void rb_vm_init_compiler(void);
-}
+#include "vm.h"
extern bool ruby_is_miniruby;
Modified: MacRuby/trunk/rakelib/builder.rb
===================================================================
--- MacRuby/trunk/rakelib/builder.rb 2010-01-17 04:36:20 UTC (rev 3286)
+++ MacRuby/trunk/rakelib/builder.rb 2010-01-17 04:37:52 UTC (rev 3287)
@@ -115,7 +115,7 @@
ruby signal sprintf st string struct time transcode util variable version
thread id objc bs encoding main dln dmyext marshal gcd
vm_eval prelude miniprelude gc-stub bridgesupport compiler dispatcher vm
- MacRuby
+ debugger MacRuby MacRubyDebuggerConnector
}
OBJS_CFLAGS = {
Modified: MacRuby/trunk/ruby.c
===================================================================
--- MacRuby/trunk/ruby.c 2010-01-17 04:36:20 UTC (rev 3286)
+++ MacRuby/trunk/ruby.c 2010-01-17 04:37:52 UTC (rev 3287)
@@ -44,6 +44,7 @@
/* TODO: move to VM */
VALUE ruby_debug = Qfalse;
VALUE ruby_verbose = Qfalse;
+VALUE ruby_debug_socket_path = Qfalse;
VALUE ruby_aot_compile = Qfalse;
VALUE ruby_aot_init_func = Qfalse;
VALUE rb_parser_get_yydebug(VALUE);
@@ -769,6 +770,18 @@
argc--; argv++;
argc--; argv++;
}
+ else if (strcmp("debug-mode", s) == 0) {
+ // This option is not documented and only used by macrubyd.
+ // Users should use macrubyd and never call this option
+ // directly.
+ if (argc < 2) {
+ rb_raise(rb_eRuntimeError,
+ "expected 1 argument (unix socket path) for --debug-mode");
+ }
+ ruby_debug_socket_path = rb_str_new2(argv[1]);
+ GC_RETAIN(ruby_debug_socket_path);
+ argc--; argv++;
+ }
else {
rb_raise(rb_eRuntimeError,
"invalid option --%s (-h will show valid options)", s);
Modified: MacRuby/trunk/vm.cpp
===================================================================
--- MacRuby/trunk/vm.cpp 2010-01-17 04:36:20 UTC (rev 3286)
+++ MacRuby/trunk/vm.cpp 2010-01-17 04:37:52 UTC (rev 3287)
@@ -47,6 +47,7 @@
#include "id.h"
#include "vm.h"
#include "compiler.h"
+#include "debugger.h"
#include "objc.h"
#include "dtrace.h"
@@ -3000,10 +3001,9 @@
}
extern "C"
-void
-rb_vm_push_binding(VALUE self, rb_vm_block_t *current_block,
- rb_vm_var_uses **parent_var_uses,
- int lvars_size, ...)
+rb_vm_binding_t *
+rb_vm_create_binding(VALUE self, rb_vm_block_t *current_block, int lvars_size,
+ va_list lvars, bool vm_push)
{
rb_vm_binding_t *binding =
(rb_vm_binding_t *)xmalloc(sizeof(rb_vm_binding_t));
@@ -3017,24 +3017,36 @@
}
}
- va_list ar;
- va_start(ar, lvars_size);
for (int i = 0; i < lvars_size; ++i) {
- ID name = va_arg(ar, ID);
- VALUE *value = va_arg(ar, VALUE *);
+ ID name = va_arg(lvars, ID);
+ VALUE *value = va_arg(lvars, VALUE *);
l = push_local(l, name, value);
}
- va_end(ar);
- rb_vm_add_binding_lvar_use(binding, current_block, parent_var_uses);
-
RoxorVM *vm = GET_VM();
GC_WB(&binding->block, vm->current_block());
+ if (vm_push) {
+ vm->push_current_binding(binding);
+ }
- vm->push_current_binding(binding);
+ return binding;
}
extern "C"
+void
+rb_vm_push_binding(VALUE self, rb_vm_block_t *current_block,
+ rb_vm_var_uses **parent_var_uses, int lvars_size, ...)
+{
+ va_list lvars;
+ va_start(lvars, lvars_size);
+ rb_vm_binding_t *binding = rb_vm_create_binding(self, current_block,
+ lvars_size, lvars, true);
+ va_end(lvars);
+
+ rb_vm_add_binding_lvar_use(binding, current_block, parent_var_uses);
+}
+
+extern "C"
rb_vm_binding_t *
rb_vm_current_binding(void)
{
@@ -3725,9 +3737,13 @@
void
rb_vm_init_compiler(void)
{
+ if (ruby_aot_compile && ruby_debug_socket_path) {
+ fprintf(stderr, "cannot run in both AOT and debug mode\n");
+ exit(1);
+ }
RoxorCompiler::shared = ruby_aot_compile
? new RoxorAOTCompiler()
- : new RoxorCompiler();
+ : new RoxorCompiler(ruby_debug_socket_path);
}
extern "C" void rb_node_release(NODE *node);
@@ -4798,7 +4814,7 @@
GET_VM()->set_current_class(NULL);
VALUE top_self = rb_obj_alloc(rb_cTopLevel);
- rb_objc_retain((void *)top_self);
+ GC_RETAIN(top_self);
GET_VM()->set_current_top_object(top_self);
rb_vm_set_current_scope(rb_cNSObject, SCOPE_PRIVATE);
Modified: MacRuby/trunk/vm.h
===================================================================
--- MacRuby/trunk/vm.h 2010-01-17 04:36:20 UTC (rev 3286)
+++ MacRuby/trunk/vm.h 2010-01-17 04:37:52 UTC (rev 3287)
@@ -250,10 +250,12 @@
return NEW_FBODY(NEW_METHOD(node, klass, noex), 0);
}
+VALUE rb_vm_eval_string(VALUE self, VALUE klass, VALUE src,
+ rb_vm_binding_t *binding, const char *file, const int line);
VALUE rb_vm_run(const char *fname, NODE *node, rb_vm_binding_t *binding,
- bool inside_eval);
+ bool inside_eval);
VALUE rb_vm_run_under(VALUE klass, VALUE self, const char *fname, NODE *node,
- rb_vm_binding_t *binding, bool inside_eval);
+ rb_vm_binding_t *binding, bool inside_eval);
void rb_vm_aot_compile(NODE *node);
void rb_vm_init_compiler(void);
@@ -385,6 +387,8 @@
}
}
+rb_vm_binding_t *rb_vm_create_binding(VALUE self, rb_vm_block_t *current_block,
+ int lvars_size, va_list lvars, bool vm_push);
rb_vm_binding_t *rb_vm_current_binding(void);
void rb_vm_add_binding(rb_vm_binding_t *binding);
void rb_vm_pop_binding();
@@ -442,6 +446,7 @@
VALUE rb_vm_current_exception(void);
void rb_vm_set_current_exception(VALUE exception);
VALUE rb_vm_backtrace(int level);
+void rb_vm_print_current_exception(void);
#define TEST_THREAD_CANCEL() (pthread_testcancel())
@@ -957,6 +962,15 @@
? NULL : bindings.back();
}
+ rb_vm_binding_t *get_binding(unsigned int index) {
+ if (!bindings.empty()) {
+ if (index < bindings.size()) {
+ return bindings[index];
+ }
+ }
+ return NULL;
+ }
+
void push_current_binding(rb_vm_binding_t *binding, bool retain=true) {
if (retain) {
rb_objc_retain(binding);
Modified: MacRuby/trunk/vm_eval.c
===================================================================
--- MacRuby/trunk/vm_eval.c 2010-01-17 04:36:20 UTC (rev 3286)
+++ MacRuby/trunk/vm_eval.c 2010-01-17 04:37:52 UTC (rev 3287)
@@ -291,29 +291,20 @@
return rb_call(obj, idEach, 0, 0, CALL_FCALL, false);
}
-static VALUE
-eval_string(VALUE self, VALUE klass, VALUE src, VALUE scope, const char *file,
- const int line)
+VALUE
+rb_vm_eval_string(VALUE self, VALUE klass, VALUE src, rb_vm_binding_t *binding,
+ const char *file, const int line)
{
- rb_vm_binding_t *b = NULL;
- if (scope != Qnil) {
- if (!rb_obj_is_kind_of(scope, rb_cBinding)) {
- rb_raise(rb_eTypeError, "wrong argument type %s (expected Binding)",
- rb_obj_classname(scope));
- }
- b = (rb_vm_binding_t *)DATA_PTR(scope);
- }
-
bool old_parse_in_eval = rb_vm_parse_in_eval();
rb_vm_set_parse_in_eval(true);
- if (b != NULL) {
+ if (binding != NULL) {
// Binding must be added because the parser needs it.
- rb_vm_add_binding(b);
+ rb_vm_add_binding(binding);
}
NODE *node = rb_compile_string(file, src, line);
- if (b != NULL) {
+ if (binding != NULL) {
// We remove the binding now but we still pass it to the VM, which
// will use it for compilation.
rb_vm_pop_binding();
@@ -330,10 +321,25 @@
}
}
- return rb_vm_run_under(klass, self, file, node, b, true);
+ return rb_vm_run_under(klass, self, file, node, binding, true);
}
static VALUE
+eval_string(VALUE self, VALUE klass, VALUE src, VALUE scope, const char *file,
+ const int line)
+{
+ rb_vm_binding_t *binding = NULL;
+ if (scope != Qnil) {
+ if (!rb_obj_is_kind_of(scope, rb_cBinding)) {
+ rb_raise(rb_eTypeError, "wrong argument type %s (expected Binding)",
+ rb_obj_classname(scope));
+ }
+ binding = (rb_vm_binding_t *)DATA_PTR(scope);
+ }
+ return rb_vm_eval_string(self, klass, src, binding, file, line);
+}
+
+static VALUE
specific_eval(int argc, VALUE *argv, VALUE klass, VALUE self)
{
VALUE retval;
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/macruby-changes/attachments/20100116/01be3548/attachment-0001.html>
More information about the macruby-changes
mailing list