はじめてのGo言語プログラミング

GDDで鵜飼さんのセッションを聞いてムラムラしたのでGo言語入門してみました。意外と公式サイトのドキュメントが充実していて素晴らしい。

HelloWorldの次に何試そうかなと考えて、そういえばBrainf*ckを今まで作ったことがなかったことを思い出したので、とりあえずGo言語の練習がてら作ってみます。正確にはBrainf*ckではなくてBrainCrashだけど。

getcharをどうやったらいいか分からなかったので「,」の実装が多分変ですが、(゚ε゚)キニシナイ!!

package main

import (
  "flag"
  "fmt"
  "bufio"
  "os"
)

const (
  PROMPT = "\n%% "
)

var is_brainf_ck = false

func read_line(file *os.File) string {
  line, _ := bufio.NewReader(file).ReadString('\n')
  return line
}

func braincrash(str string) {
  program := []byte(str)
  var memory [30000]byte
  p := 0
  var curr byte

  if !is_brainf_ck {
    ini := "Hello, world!"
    for i := 0; i < len(ini); i++ {
      memory[i] = ini[i]
    }
  }

  for pc := 0; pc < len(program); pc++ {
    switch program[pc] {
    case '>': p++
    case '<': p--
    case '+': memory[p]++
    case '-': memory[p]--
    case '.': fmt.Printf("%c", memory[p])
    case ',': memory[p] = read_line(os.Stdin)[0]
    case '[': 
      if memory[p] == 0 { 
        depth := 0
        for {
          pc++
          if program[pc] == '[' {depth += 1}
          if program[pc] == ']' { if depth == 0 { break } else { depth -= 1} }
        }
      }
    case ']': 
      if memory[p] != 0 { 
        depth := 0
        for {
          pc--
          if program[pc] == ']' {depth += 1}
          if program[pc] == '[' { if depth == 0 { break } else { depth -= 1} }
        }
      }
    }

    if !is_brainf_ck {
      switch program[pc] {
      case '|': curr = memory[p]; p++; memory[p] |= curr
      case '&': curr = memory[p]; p++; memory[p] &= curr
      case '~': memory[p] = ^memory[p]
      case '^': curr = memory[p]; p++; memory[p] ^= curr
      }
    }
  }

  if !is_brainf_ck {
    for i := p; memory[i] != 0; i++ {
      fmt.Printf("%c", memory[i])
    }
  }
}

func read_and_braincrash(file *os.File) bool {
  program := read_line(file)
  if program == "exit\n" { return false }
  braincrash(program)
  return true
}

func main() {
  flag.BoolVar(&is_brainf_ck, "f", false, "brainf*ck mode")
  flag.Parse()
  if flag.NArg() == 0 {
    if is_brainf_ck { fmt.Printf("[BRAINF*CK MODE]\n") }
    fmt.Printf("Type 'exit' to exit")
    for {
      fmt.Printf(PROMPT)
      if !read_and_braincrash(os.Stdin) { break }
    }
  } else {
    filename := flag.Arg(0)
    file, err := os.Open(filename, os.O_RDONLY, 0666)
    if err == nil {
      read_and_braincrash(file)
    }
  }
}

コンパイル&リンクはうちのマシンだと

$ 6g braincrash.go
$ 6l -o braincrash braincrash.6

実行は

$ ./braincrash 
Type 'exit' to exit
% |<
Hmllo, world!
% exit
$

ちなみに「-f」をつけるとBrainf*ckモードになります。

$ ./braincrash -f
[BRAINF*CK MODE]
Type 'exit' to exit
% +++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.------------.<++++++++.--------.+++.------.--------.>+.
Hello, world!
% exit
$

このレベルなら特にクセもなくて使いやすそうな言語ですね。いや、もちろんBrainf*ckがではなくて、Go言語が。

次はやっぱりWhitespaceを作るべきなのかな。