D言語であそぼう シェルとかつくってみる編。

なんか C で簡単なシェルつくってて、「あーハッシュテーブルでシンボルテーブルつくった方がいいよなあ、でもめんどくせー」と思ったので D で書いてみた。
とりあえず import std.c.linux.linux とかしたり色々すればできんだろー、と思ったら std.c.* のアレコレがまあ conflict しまくる。なので最低限使うのだけ宣言。

extern (C){
  int fork();
  int wait(int*);
  char* getenv(char*);
  void exit(int);
  void perror(char *);
}

D 側の関数で対応するものあればいらなくなんだけど。ちゃんと調べてないのであるかもしれない。

D はとにかく C との相性がいいっすね。C言語向けにインターフェースを提供してるライブラリのバインドが簡単ってのは、すごく有利なんだなと。あとはまあ C言語に比べりゃ文字列操作系の処理が超楽ちんですよねー、みたいなー。

以下うんこシェルのソース。「ハッシュテーブルハッシュテーブル!」言ってたのはどうしたかっつーと、まったくもって活用していなかったりする。しょぼいというか習作以外の何物でもない。今後改良したりとかも、特にしないと思う。

import std.process;
import std.cstream;
import std.stream;
import std.string;
import std.file;
import std.path;

extern (C){
  int fork();
  int wait(int*);
  char* getenv(char*);
  void exit(int);
  void perror(char *);
}

const char[] PS1 = "$ ";

struct Command {
  char[][] argv;
};

int delegate(Command*)[char[]] builtinFunc;

void initFunction(){
  int builtin_cd(Command *com){
    static char[] prevpath = ".";
    if(com.argv.length == 1){
      prevpath = getcwd();
      chdir(toString(getenv("HOME")));
    }
    else if(com.argv.length == 2){
      char[] newpath;
      if(com.argv[1] == "-"){
        newpath = prevpath;
      }
      else{
        newpath = expandTilde(com.argv[1]);
      }
      prevpath = getcwd();
      chdir(newpath);
    }
    else{
      derr.writeLine("too may arguments");
      return 1;
    }
    return 0;
  }
  int builtin_exit(Command *com){
    exit(0);
    return 1;
  }
  builtinFunc["cd"] = &builtin_cd;
  builtinFunc["exit"] = &builtin_exit;
}

int exec_com(Command *com){
  int pid;
  char[] cmdname = com.argv[0];
  if((pid = fork()) == 0){
    execvp(cmdname, com.argv);
    perror(toStringz(cmdname));
    exit(1);
  }
  if(pid < 0){
    perror(toStringz("fork"));
    exit(1);
  }
  if(wait(null) < 0){
    perror(toStringz("wait"));
    exit(1);
  }
  return 0;
}
int run(Command *com){
  char[] cmdname = com.argv[0];
  if(cmdname in builtinFunc){
    builtinFunc[cmdname](com);
    return 0;
  }
  else{
    exec_com(com);
    return 0;
  }
}
Command *parse(Command *com, char[] line){
  if(line.length == 0){
    return com;
  }
  switch(line[0]){
    case ' ':
    case '\t':
      return parse(com, line[1 .. line.length]);
    case '\'':
      int rightQuot = line[1 .. line.length].find('\'')+1;

      if(rightQuot != -1){
        com.argv ~= line[1 .. rightQuot];
      }
      else{
        derr.writeLine("error: close quot(\')");
      }

      if(rightQuot >= 0 && rightQuot+1 <= line.length){
        return parse(com, line[rightQuot+1 .. line.length]);
      }
      else{
        return com;
      }
    default:
      int rightSp;
      if((rightSp = line.find(' ')) != -1
          || (rightSp = line.find('\t')) != -1){
        com.argv ~= line[0 .. rightSp];
        return parse(com, line[rightSp+1 .. line.length]);
      }
      else{
        com.argv ~= line[0 .. line.length];
        return com;
      }
  }
}
int main(char[][] args){
  initFunction();
  while(!din.eof()){
    dout.write(PS1);
    char[] line = din.readLine();
    if(line.length == 0) continue;

    Command com;
    parse(&com, line);
    run(&com);
  }
  return 0;
}

クラスってなんですか!!!
あきらかに Command あたりをクラスにした方がすっきりしそう。parse, run とかメソッドにしてほげほげ。

あと実行画面。

% ./shell
$ ls
shell  shell.d  shell.o
$ ls -aF
./  ../  shell*  shell.d  shell.o
$ cd ~/tmp
$ ls
coins.tar.bz2  evilwm-1.0.0         home  sevilwm-0.6.1-1.i386.rpm  swig-d-1-Aug-2004.zip  verilog-0.8.4-0.src.rpm
echo.php       evilwm-1.0.0.tar.gz  m     sun.tar.bz2               verilog-0.8.4          verilog-0.8.4.tar.gz
$ pwd
/home/sun/tmp
$ cd -
$ ls
shell  shell.d  shell.o
$ pwd
/home/sun/docs/d/shell
$ perl -e 'print "hoge\n"'
hoge
$ exit
%

test