LoginSignup
89

More than 3 years have passed since last update.

posted at

updated at

ゴリラ言語の読み方

先日、「ウ」と「ホ」と改行文字だけでプログラミングできる「ゴリラ言語」を作りました。

コード短いのでそのまま張っておきます。

package main

import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "io/ioutil"
    "os"
    "strconv"
    "strings"
)

type Op int

const (
    Push Op = iota
    Signed
    Dup
    Copy
    Swap
    Discard
    Slide
    Add
    Sub
    Mul
    Div
    Mod
    Store
    Retrieve
    Mark
    Unsigned
    Call
    Jump
    Jz
    Jn
    Ret
    Exit
    OutC
    OutN
    InC
    InN
    Nop
)

type opcode struct {
    code Op
    arg  int
}

type opcodes []opcode

func (p *opcodes) jmp(arg int, pc *int) {
    for i, t := range *p {
        if t.code == Mark && t.arg == arg {
            *pc = i
        }
    }
}

var optable = map[string]Op{
    "  ":       Push,
    " \n ":     Dup,
    " \n\t":    Swap,
    " \n\n":    Discard,
    "\t   ":    Add,
    "\t  \t":   Sub,
    "\t  \n":   Mul,
    "\t \t ":   Div,
    "\t \t\t":  Mod,
    "\t\t ":    Store,
    "\t\t\t":   Retrieve,
    "\n  ":     Mark,
    "\n \t":    Call,
    "\n \n":    Jump,
    "\n\t ":    Jz,
    "\n\t\t":   Jn,
    "\n\t\n":   Ret,
    "\n\n\n":   Exit,
    "\t\n  ":   OutC,
    "\t\n \t":  OutN,
    "\t\n\t ":  InC,
    "\t\n\t\t": InN,
    //" \t ":     Copy,
    //" \t\n":    Slide,
}

type stack []int

func (s *stack) push(v int) {
    *s = append(*s, v)
}

func (s *stack) pop() int {
    l := len(*s)
    if l == 0 {
        return -1
    }
    v := (*s)[l-1]
    *s = (*s)[:l-1]
    return v
}

func (s *stack) dup() {
    l := len(*s)
    if l == 0 {
        return
    }
    v := (*s)[l-1]
    *s = append(*s, v)
}

func uho(src []rune) {
    for i, r := range src {
        switch r {
        case 'ウ':
            src[i] = ' '
        case 'ホ':
            src[i] = '\t'
        }
    }
    // remove needless comments
    for {
        pos := strings.IndexFunc(string(src), func(r rune) bool {
            return r != ' ' && r != '\t' && r != '\n'
        })
        if pos < 0 {
            break
        }
        if pos == 0 {
            src = src[1:]
        } else {
            src = append(src[:pos], src[pos+1:]...)
        }
    }

    // parse uho into tokens
    tokens := opcodes{}
    for len(src) > 0 {
        op := ""
        code := Nop
        for k, v := range optable {
            if strings.HasPrefix(string(src), k) {
                op = k
                code = v
                break
            }
        }
        if op == "" {
            src = src[1:]
            continue
        }
        src = src[len(op):]
        var arg int
        switch code {
        case Push:
            // handle argument
        handle_signed_arg:
            for i := 1; i < len(src); i++ {
                switch src[i] {
                case ' ':
                    arg = (arg << 1) | 0
                case '\t':
                    arg = (arg << 1) | 1
                case '\n':
                    // Push take singed argument
                    if src[0] == '\t' {
                        arg = -arg
                    }
                    src = src[i+1:]
                    break handle_signed_arg
                }
            }
        case Mark, Call, Jump, Jz, Jn:
            // handle argument
        handle_unsigned_arg:
            for i := 0; i < len(src); i++ {
                switch src[i] {
                case ' ':
                    arg = (arg << 1) | 0
                case '\t':
                    arg = (arg << 1) | 1
                case '\n':
                    src = src[i+1:]
                    break handle_unsigned_arg
                }
            }
        }
        tokens = append(tokens, opcode{code, arg})
    }

    pc := 0
    ps := stack{}
    cs := stack{}
    heap := map[int]int{}
    for {
        token := tokens[pc]

        code, arg := token.code, token.arg
        //fmt.Println(pc, code, arg)
        pc++
        switch code {
        case Push:
            ps.push(arg)
        case Mark:
        case Dup:
            ps.dup()
        case OutN:
            fmt.Print(ps.pop())
        case OutC:
            fmt.Print(string(rune(ps.pop())))
        case Add:
            rhs := ps.pop()
            lhs := ps.pop()
            ps.push(lhs + rhs)
        case Sub:
            rhs := ps.pop()
            lhs := ps.pop()
            ps.push(lhs - rhs)
        case Mul:
            rhs := ps.pop()
            lhs := ps.pop()
            ps.push(lhs * rhs)
        case Div:
            rhs := ps.pop()
            lhs := ps.pop()
            ps.push(lhs / rhs)
        case Mod:
            rhs := ps.pop()
            lhs := ps.pop()
            ps.push(lhs % rhs)
        case Jz:
            if ps.pop() == 0 {
                tokens.jmp(arg, &pc)
            }
        case Jn:
            if ps.pop() < 0 {
                tokens.jmp(arg, &pc)
            }
        case Jump:
            tokens.jmp(arg, &pc)
        case Discard:
            ps.pop()
        case Exit:
            os.Exit(0)
        case Store:
            v := ps.pop()
            address := ps.pop()
            heap[address] = v
        case Call:
            cs.push(pc)
            tokens.jmp(arg, &pc)
        case Retrieve:
            ps.push(heap[ps.pop()])
        case Ret:
            pc = cs.pop()
        case InC:
            var b [1]byte
            os.Stdin.Read(b[:])
            heap[ps.pop()] = int(b[0])
        case InN:
            scanner := bufio.NewScanner(os.Stdin)
            if scanner.Scan() {
                i, _ := strconv.Atoi(scanner.Text())
                heap[ps.pop()] = i
            }
        case Swap:
            ps[len(ps)-1], ps[len(ps)-2] = ps[len(ps)-2], ps[len(ps)-1]
        default:
            panic(fmt.Sprintf("Unknown opcode: %v", code))
        }
    }
}

func main() {
    var content bytes.Buffer

    for _, f := range os.Args[1:] {
        b, err := ioutil.ReadFile(f)
        if err != nil {
            fmt.Fprintln(os.Stderr, "read file:", err)
            os.Exit(1)
        }
        content.Write(b)
    }

    if content.Len() == 0 {
        if _, err := io.Copy(&content, os.Stdin); err != nil {
            fmt.Fprintln(os.Stderr, "read stdin:", err)
            os.Exit(1)
        }
    }

    uho([]rune(content.String()))
}

以下のコードをコマンドラインから標準入力に食わせると、FizzBuzz が実行されます。

ウウウホッ

ウウウウ
ウ
ウウ
ウウウウホホ
ホウホッホ
ホウウホ
ウウウホッウホ
ホウホッホ
ホウウホウ
ウ
ウホ
ウホッ
ウ
ウホウウ

ウウウホッ
ウウウホッウウウホホウ
ホ
ウウウウウホッホウホッウウホ
ホ
ウウ
ウホッウホホ
ウウウホッウホ
ホウホッホ
ホウウホウ

ウ
ウホウウ

ウウウホッウ
ウウウホウウウウホッウ
ホ
ウウウウウホッホホウホウホッ
ホ
ウウ
ウホウホッホ

ウ
ウホウウ

ウウウホホ
ウウウホッホホホウホッウ
ウ
ウホッ
ウウホ
ウウ
ホ

ウウウホッウウ
ウウウホウホッウ
ホ
ウウウウウホ
ホウウウウ
ウウウウホッホウウホッウホ
ホウウホ
ホホウウ
ウ


便利ですね。また以下のコードを食わせると「ウホッ!」と表示されます。

ウッウウ
ウウウホウホウ
ウウウッホッウウウウホ
ウウウッホホウウウウッホホウッウウウッホホ
ウウウホホウウウッウホホウホッホウホホ
ウウウッホッホウウウウッホウホウウホホウ

ウウホウウウッウーウウウウウホウッウウホウウーホウウ
ウ
ウ
ホウッホホホウホウウウッホウホウウ
ホ
ウウ
ウ
ホウウウッウウウウウウッホウウーウホウウホウウ

ウウホホホウホウウウホッウホウウ
ウ



勘のいい方はコード見なくてもこの説明だけで気付いたかもしれません。そう「whitespace」です。

Whitespace(ホワイトスペース)は、プログラミング言語のひとつであり、またそれを動作させるインタプリタを指している。WhitespaceはGPLにより配布されている。実用言語ではない難解プログラミング言語のひとつ。
本来 "whitespace" とは「空白」や「余白」を意味する英単語である。多くの一般的なプログラミング言語では空白に相当する文字(スペース、タブ、言語によっては改行も)は他の言語要素間の区切りとして使われている。しかし、言語 Whitespace においてはプログラムは空白文字だけで構成される(それ以外の文字列はコメント扱いで無視される)。そのため、一見するとプログラムであることすらわからないという珍しい言語である。

ゴリラ言語は、空白を「ウ」、タブ文字を「ホ」に見立てた Esolang です。この言語で目的の文字を表示するコードをどうやって書いているか想像もつかないかもしれません。実は whitespace のコンパイラを作っておられる方がいます。

ウェブ上の IDE になっています。例えばここの Files の中にある nerd.wsa を開くと以下のコードが表示されます。

; Push the zero-terminated string
; onto stack in reverse order.

    push "Hello Nerd!\n"

; Loop while top of stack is non-zero.

print:
    dup
    jz end
    printc
    jmp print

; Graceful end
end:
    drop
    end

独自のアセンブラ言語になっていて、コンパイルすると whitespace のソースが出力されます。この Hello Nerd!ウホッ! 書き換えると空白とタブ文字と改行文字で作られる whitespace のソースコードが出来上がります。























真っ白けですね。vim 等を使い、空白を「ウ」、タブ文字を「ホ」に置換してみます。

ウウウ
ウウウホウホウ
ウウウホホホホホホホホウウウウウウウホ
ウウウホホウウウウホホウウウウホホ
ウウウホホウウウウホホウホホウホホ
ウウウホホウウウウホウホウウホホウ

ウウホウウウウウウウウウホウウウホウウホウウ
ウ
ウ
ホウホホホウホウウウホウホウウ
ホ
ウウ
ウ
ホウウウウウウウウウホウウウホウウホウウ

ウウホホホウホウウウホウホウウ
ウ





読めそうな気がしてきましたね。読める気がしましたよね?

読んでみましょう。ゴリラ言語の仕様は以下の通り。

IMP
[ウ] スタック操作
[ホ][ウ] 演算
[ホ][ホ] ヒープアクセス
[LF] フロー制御
[ホ][LF] I/O

STACK OPERATION
[ウ] 数値:数値をスタックに積む
[LF][ウ]:スタックの一番上を複製する
[ホ][ウ] 数値:スタックのn番目をコピーして一番上に積む
[LF][ホ]:スタックの1番目と2番目を交換する
[LF][LF]:スタックの一番上の物を捨てる

CALCULATE
[ウ][ウ]:加算
[ウ][ホ]:引き算
[ウ][LF]:かけ算
[ホ][ウ]:割り算
[ホ][ホ]:剰余

始めはデータ部ですね。push ([ウ])に続く引数、0(なし), 10(1<<1, 2<<1, 5<<1), 65281(1<<1, 2<<1, 4<<1, 8<<1, 16<<1, 33<<1), 12483(略), 12507(略), 12454(略) を積んでいます。スタックですから取り出す時には逆から取り出されます。つまりこれは「12454, 12507, 12483, 65281, 10, 0」、文字に直すと「ウホッ!」です。続いて制御部


ウウホウウウウウウウウウホウウウホウウホウウ
ウ
ウ
ホウホホホウホウウウホウホウウ
ホ
ウウ
ウ
ホウウウウウウウウウホウウウホウウホウウ

ウウホホホウホウウウホウホウウ
ウ




マーク([LF][ウ][ウ])でラベル定義([ホ][ウ][ウ][ウ][ウ][ウ][ウ][ウ][ウ][ウ][ホ][ウ][ウ][ウ][ホ][ウ][ウ][ホ][ウ][ウ])、スタックから取り出し([ウ][LF][ウ])、ゼロ比較([LF][ホ][ウ])、0 であればラベル ホホホウホウウウホウホウウ にジャンプしています。0 でない場合は画面に取り出した値を文字として表示([ホ][LF][ウ])します。そしてループの最初に戻る為に ホウウウウウウウウウホウウウホウウホウウ へジャンプ([LF][ウ][LF])です。0 比較時のジャンプ先としてマーク([LF][ウ][ウ])でラベル定義([ホ][ホ][ホ][ウ][ホ][ウ][ウ][ウ][ホ][ウ][ホ][ウ][ウ])しています。

ループから出た後は、スタックの一番上を捨て([ウ][LF][LF])、プログラムを終了([LF][LF][LF])します。

簡単ですね!もうこれでいつ会社で「先輩、このゴリラ言語わからないので読んでもらえますか?」と聞かれても読めますよね。

ちなみにこれを実行すると以下の様に画面に表示されます。

ET48oE2UEAAPUn5.png

言ってしまえば whitespace のパクリ言語なのですが、2文字書き換えるだけでオレオレ言語が作れます。「ピ」と「ヨ」と改行でヒヨコ言語を作る事も出来ます。ぜひ楽しんで下さい。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
89