起動用プログラム

TurtleEdit内でプログラムを実行すると,出力がバッファーされてしまうため,コンソールで動かす場合と挙動が異なってしまいます。 そこで,プログラムの標準入出力を疑似端末につなぎ替えて,コンソール上で動いているように錯覚させるためのコードを書いてみました。

使い方

下に掲載されているCのコードをファイルに保存し,MacやLinux上でコンパイルします(Windowsでは動きません)。ファイル名を run.c として

cc -o run run.c

を実行すると,runという実行ファイルが出来ます。この run を,プログラミング練習用のフォルダに コピー(移動)しておきます。

./runのところは,runの保存先の絶対パスを書いておいたほうが良いかもしれません。 例えば、ホームディレクトリの下の bin/runにrunコマンドを置いた場合は、 %h/bin/runのように書くことができます。

次に,TurtleEditの「実行」メニューから「設定」を開いて,「実行コマンド」の欄を

./run ./a.out

と書き換えます(C言語等の場合)。他の言語,例えばRubyの場合は,

./run ruby %f

等と書き換えます。

これで,インタラクティブな処理であっても,教科書などに載っているコードがそのまま走るはずです。

コード

run.c をダウンロード

/* a simple program launcher for TurtleEdit                */
/* Yoshinori Hayakawa, Tohoku University                   */
/* 07-MAY-2014                                             */
/* most of this code is based on the exmaples in           */
/* http://rachid.koucha.free.fr/tech_corner/pty_pdip.html  */
/*                                                         */

#define _XOPEN_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/wait.h>

#define BUFFLEN 1024

char **cmdargv = NULL ;
char readbuf[BUFFLEN] ;

int terminating ;
void sigchld_handler(int);

int main(int argc, char *argv[]) {

  int i,master,slave ;
  pid_t pid ;

  if (argc<=1) return 0 ;
  else {
    cmdargv = (char **) malloc(sizeof(char *)*argc) ;
    for (i=1 ; i<argc ; i++) {
      cmdargv[i-1] = argv[i] ;
    }
    cmdargv[argc-1]='\0' ;
  }

  master =  open("/dev/ptmx", O_RDWR|O_NOCTTY);
  grantpt(master);
  unlockpt(master);
  slave = open(ptsname(master), O_RDWR);

  terminating = 0 ;

  if (pid=fork()) {  /* parent */
    fd_set fd_input ;
    int rc,k ;

    signal(SIGCHLD, sigchld_handler);
    close(slave) ;

    while (1)
      {
	FD_ZERO(&fd_input);
	FD_SET(0, &fd_input);
	FD_SET(master, &fd_input);
	rc = select(master + 1, &fd_input, NULL, NULL, NULL);
	switch(rc)
	  {
	  case -1 : 
	  	exit(0);
	  default : 
	    if (FD_ISSET(0, &fd_input)) { /* check stdin */
	      rc = read(0, readbuf, sizeof(readbuf));
	      if (rc > 0)  {
		write(master, readbuf, rc);
	      } else {
		if (rc < 0) {
		  fprintf(stderr, "Error on read standard input\n");
		  exit(1);
		}
	      }
	    }

	    if (FD_ISSET(master, &fd_input)) { /* check PTY */
	      rc = read(master, readbuf, sizeof(readbuf));
	      if (rc > 0) {
		  	write(1, readbuf, rc);
	      }
	      if (terminating) exit(0) ;
	    }
	  } 
      } 
  }
  else { /* child process */
    struct termios term_setting ;

    close(master) ;
    dup2(slave,0) ;
    dup2(slave,1) ;
    dup2(slave,2) ;

    /* disable echo */
    tcgetattr (0, &term_setting) ;
    term_setting.c_lflag &= ~ECHO;
    term_setting.c_cc[VEOF] = 0x04 ;   // CTRL-D
    term_setting.c_cc[VERASE] = 0x08 ; // BACKSPACE
    term_setting.c_lflag |= ICANON ;
    tcsetattr (0, TCSAFLUSH, &term_setting) ;

    setsid() ;
    ioctl(0, TIOCSCTTY, 1);
    if (execvp(argv[1],cmdargv)==-1) {
      fprintf(stderr,"cannot execute %s\n",argv[1]) ;
    } 
    _exit(0) ;
  }
  return 0 ;
}

void sigchld_handler(int x)
{
  terminating = 1 ;
}