c-repl-0.0.20071223/0000755000175000017500000000000010740074477013310 5ustar edmondsedmondsc-repl-0.0.20071223/LICENSE0000644000175000017500000000272210740073720014306 0ustar edmondsedmondsCopyright (c) 2007 Evan Martin All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the author nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. c-repl-0.0.20071223/Makefile0000644000175000017500000000006310740073720014735 0ustar edmondsedmondschild: child.c gcc -Wall -g child.c -o child -ldl c-repl-0.0.20071223/c-repl0000755000175000017500000001151610740073720014412 0ustar edmondsedmonds#!/usr/bin/ruby # c-repl -- a C read-eval-print loop. # Copyright (C) 2006 Evan Martin require 'readline' require 'gdbmi' require 'codesnippet' # A wrapper around the child process. class Runner def initialize start end def start command_pipe = IO.pipe response_pipe = IO.pipe @pid = fork do command_pipe[1].close response_pipe[0].close STDIN.reopen(command_pipe[0]) command_pipe[0].close exec('./child', response_pipe[1].fileno.to_s) end command_pipe[0].close command_pipe[1].sync = true response_pipe[0].sync = true response_pipe[1].close @command_pipe = command_pipe[1] @response_pipe = response_pipe[0] end def run_command(id, attempt_recover=true) @command_pipe.puts id resp = @response_pipe.gets #p resp if resp != "#{id}\n" pid, status = Process.wait2(@pid, Process::WNOHANG) return true unless pid # the subprocess failed. try recovering once if asked. if status.signaled? and status.termsig == 11 puts "segfault detected." else puts "??? wait finished; #{pid.inspect} #{status.inspect}" end if attempt_recover puts "resuming." start run_command(id-1, false) if id > 1 end return false end def gdb(args) GDBMI.attach_run_detach(@pid, args) end end class CREPL def initialize @debug = false @cur_so_id = 1 @runner = Runner.new @externs = [] @headers = [] @libraries = [] @commands = { 'd' => ['toggle debug mode.', proc { @debug = !@debug; puts "debug is #{@debug?'on':'off'}" }], 'h' => ['"h foo.h": bring in a C header.', proc { |args| @headers << args }], 'l' => ['"l m": bring in a C library.', proc { |args| @libraries << args }], 't' => ['test if the repl is ok by running a printf through it.', proc { c_command('printf("repl is ok\n");') }], 'g' => ['"g foobar": run an arbitrary command through gdb.', proc { |args| @runner.gdb args }], 's' => ['cause a segfault to let crepl attempt to recover.', proc do puts 'attempting segfault' c_command '*((char*)0) = 0;' end], 'help' => ['show help on commands.', proc { show_help }] } end # Given a number and some code. # generate a shared object that declares the variables as globals # with a function containing the code (if any). def generate_so(name, snippet) reader, writer = IO.pipe pid = fork do writer.close STDIN.reopen(reader) reader.close cmd = "gcc -xc -g -shared -o #{name}.so" # add in all libraries cmd += ' ' + @libraries.map{|l| "-l#{l}"}.join(' ') # tell it to read input through stdin cmd += ' -' puts cmd if @debug exec(cmd) end reader.close generate_code(name, snippet, writer) writer.close generate_code(name, snippet, STDERR) if @debug pid, status = Process.wait2(pid) status.success? end # Generate code from snippet, writing it to out. def generate_code(name, snippet, out) out.puts "#include " @headers.each do |header| out.puts "#include \"#{header}\"" end @externs.each do |extern| out.puts "extern #{extern}" end out.puts snippet.decl if snippet.decl if snippet.func out.puts snippet.func end out.puts "void #{name}() {" if snippet.stmt out.puts " #line 1" out.puts " #{snippet.stmt}" end out.puts "}" end def user_command command args = command.gsub(/^(\S+)\s*/, '') cmd = $1 unless @commands.has_key? cmd puts "unknown command: #{cmd}" return end help, func = @commands[cmd] func.call args end def c_command code snippet = CodeSnippet.parse(code) return unless generate_so("dl#{@cur_so_id}", snippet) return unless @runner.run_command @cur_so_id @externs << snippet.decl if snippet.decl @cur_so_id += 1 end def show_help puts <<-EOT Type C statements and declarations as you would in a normal program. Type a variable name by itself to see its value. Commands start with a . and are as follows: EOT cmds = @commands.keys.sort len = cmds.map{|c|c.length}.max @commands.keys.sort.each do |cmd| printf(" %-#{len}s %s\n", cmd, @commands[cmd][0]) end end def input_loop loop do line = Readline.readline('> ') break unless line line.chomp! next if line.empty? if line[0] == ?. user_command line[1..-1] elsif line =~ /^(\w+)$/ # bare variable name -- dump it @runner.gdb "p #{line}" else line += ';' unless line =~ /;$/ c_command line end Readline::HISTORY.push(line) end end end CREPL.new.input_loop # vim: set ts=2 sw=2 et : c-repl-0.0.20071223/child.c0000644000175000017500000000320310740073720014523 0ustar edmondsedmonds/* c-repl -- a C read-eval-print loop. * Copyright (C) 2006 Evan Martin */ /* The child process is what actually runs the code. * It reads in a number from stdin, * then loads dl#.so and executes dl#(). */ #include #include #include #include #include #include static int debug = 0; /* Load dl.so and run dl(). */ static void load_and_run(int id) { char buf[1024]; sprintf(buf, "./dl%d.so", id); if (debug) fprintf(stderr, "CHILD> loading %s\n", buf); void *so = dlopen(buf, RTLD_LAZY | RTLD_GLOBAL); if (!so) { fprintf(stderr, "CHILD> error loading library: %s\n", dlerror()); assert(so); } sprintf(buf, "dl%d", id); void (*f)() = dlsym(so, buf); if (!f) { fprintf(stderr, "CHILD> error loading function: %s\n", dlerror()); assert(f); } //printf("child executing '%s':\n", dlname); // XXX fork here to do the segfault -> undo magic? f(); } int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "bad arguments\n"); return -1; } const int callback_pipe = atoi(argv[1]); char buf[1024]; int highest_id = 0; while (fgets(buf, sizeof(buf), stdin)) { const int id = atoi(buf); if (id > 0) for ( ; highest_id < id; highest_id++) load_and_run(highest_id+1); /* If we get here, we succeeded. * Let the parent know. */ const int len = strlen(buf); if (write(callback_pipe, buf, len) < len) { perror("CHILD> write"); break; } } if (debug) fprintf(stderr, "CHILD> exiting\n"); return 0; }; /* vim: set ts=2 sw=2 et cino=(0 : */ c-repl-0.0.20071223/codesnippet.rb0000644000175000017500000000304510740073720016142 0ustar edmondsedmonds# c-repl -- a C read-eval-print loop. # Copyright (C) 2006 Evan Martin # This class parses snippets of code and gives you back an object # that holds the: # - declaration (goes at the top level the of the file; can be used with extern) # - statement (the code that is run, if any) # - function (which also goes at the top level, but can't be used with extern) # # Currently, it doesn't actually do real parsing. class CodeSnippet attr_reader :decl, :stmt, :func def initialize(code=nil) parse code if code end def self.with_semi(text) return text if text =~ /;$/ text + ';' end def parse(code) # Some patterns we'd like to support: # int x # int x = 3 # void f() { } # while() { ... } # x = 4 # Heuristics: # 1. first two tokens are non-punct means decl # 1. anything after = or { is stmt # 2. = means variable declaration # 3. { means function declaration # 2. everything else is a stmt. if code =~ /^\w+\s+\w/ # rule 1 pos = code =~ /\s*([={])/ # find first of the rule 1.1 unless pos @decl = CodeSnippet.with_semi(code) else @decl = CodeSnippet.with_semi(code[0,pos]) case $1 when '=' # rule 1.2 code =~ /(\w+\s*=.*)/ @stmt = CodeSnippet.with_semi($1) when '{' # rule 1.3 @func = code end end else # rule 2 @stmt = CodeSnippet.with_semi(code) end end def self.parse(code) self.new(code) end end # vim: set ts=2 sw=2 et : c-repl-0.0.20071223/codesnippet_unittest.rb0000644000175000017500000000115410740073720020100 0ustar edmondsedmonds#!/usr/bin/ruby require 'test/unit' require 'codesnippet' class TC_CodeSnippet < Test::Unit::TestCase def test_parse tests = [ ['int x', 'int x;', nil], ['int x = 3', 'int x;', 'x = 3;'], ['void f() {}', 'void f();', nil, 'void f() {}'], ['while(foo) { bar; }', nil, 'while(foo) { bar; };'], ['x = 4', nil, 'x = 4;'], ['f(x)', nil, 'f(x);'], ] tests.each do |input, decl, stmt, func| c = CodeSnippet.parse(input) assert_equal decl, c.decl assert_equal stmt, c.stmt assert_equal func, c.func end end end # vim: set ts=2 sw=2 et : c-repl-0.0.20071223/gdbmi.rb0000644000175000017500000000354410740073720014713 0ustar edmondsedmonds # GDB/MI (the GDB interface for IDEs and such) wrapper. # Very simple for now. class GDBMI @@debug = false def initialize(pid) #return self puts "attaching gdb to #{pid}" if @@debug pw = IO::pipe pr = IO::pipe @pid = fork do pw[1].close; STDIN.reopen(pw[0]); pw[0].close pr[0].close; STDOUT.reopen(pr[1]); pr[1].close exec("gdb --interpreter mi2 -p #{pid}") end @write = pw[1]; pw[0].close @read = pr[0]; pr[1].close # read the GDB greeting. read_response(false) end # GDB responses are C strings. # This should do a lot more... def self.parse_c_string str str =~ /"(.*)"/ str = $1 str.gsub!('\n', "\n") str.gsub!('\"', '"') return str end # Read a response from GDB. # A response is multiple lines, and the last one is just "(gdb)". def read_response(echo=false) loop do puts "waiting for gdb line" if @@debug line = @read.gets.strip puts "GDB> " + line if @@debug break if line == '(gdb)' case line[0,1] when '^' puts "GDBMI done> #{line[1..-1]}" if @@debug when '&' # "log stream" puts "GDBMI status> #{line[1..-1]}" if @@debug print(GDBMI.parse_c_string(line[1..-1])) if echo when '~' # "console stream" print(GDBMI.parse_c_string(line[1..-1])) if echo else puts "GDBMI other> #{line}" if echo end end puts "done waiting for gdb line" if @@debug end # Run a command through GDB. def run(cmd, echo=true) puts "->GDB> " + cmd if @@debug @write.puts cmd read_response(echo) end # Detach the GDB. def detach # XXX is there a better way to do this? @write.puts "q" # "quit" @write.puts "y" # "yes, I really mean to quit" end def self.attach_run_detach(pid, cmd) gdb = GDBMI.new(pid) gdb.run cmd gdb.detach end end