プログラミング言語pyoko

※この記事はピョッコリンアドベントカレンダーのために書かれたものです

はじめに

みんなのアイドルピョッコリンさんのために、 プログラミング言語pyoko(以後、単にpyokoと呼びます)を開発しました!!

pyokoとは

pyokoは、言語のシンプルさとピョコ感を出すことを念頭に置いて設計された言語です。 字面の似ているpythonと同等の表現力を持ちながらも、圧倒的可読性・記述性を達成しました。 また、ピョコ感を演出することうけあいでしょう。

サンプルコード

お決まりのHello, Worldをみてみましょう。 ね、圧倒的可読性・記述性とピョコ感が伝わるでしょう?

ピョコピョコピョコピョコピョコピョコピョコピョコピョコピョリコピョッコ
ピョコピョコピョコピョコピョコピョコピョコピョコピョッコピョコピョコ
ピョコピョコピョコピョコピョコピョコピョコピョコピョコピョッコピョコ
ピョコピョコピョコピョコピョ?!ピョ?!ピョ?!ピョコリピョコ-ピョッコ
ピョッコリンピョッコピョコピョコピョッコリンピョコピョコピョコピョコピョコ
ピョコピョコピョッコリンピョッコリンピョコピョコピョコピョッコリンピョッコ
ピョコリピョッコリンピョコリピョコリピョコリピョコリピョコリピョコリピョコリ
ピョコリピョコリピョコリピョコリピョコリピョッコリンピョ?!ピョコピョコピョコ
ピョコピョコピョコピョコピョコピョッコリンピョコリピョコリピョコリピョコリ
ピョコリピョコリピョコリピョコリピョッコリンピョコピョコピョコピョッコリンピョコリ
ピョコリピョコリピョコリピョコリピョコリピョッコリンピョコリピョコリピョコリピョコリ
ピョコリピョコリピョコリピョコリピョッコリンピョッコピョコピョッコリン

上記テキストをhello.pyokoとして保存し、インタプリタを実行します。

$ pyoko hello.pyoko
Hello, world!

ね、Hello, worldでしょう?

ソースコード

pyokoのインタプリタpythonで実装されています。

#!/usr/bin/env python
#fileencoding: utf-8

from __future__ import print_function
import sys
import codecs

class pyokolang():
    plus   = "+"
    minus  = "-"
    gt     = ">"
    lt     = "<"
    lb     = "["
    rb     = "]"
    comma  = ","
    period = "."

    def __init__(self,filename):
        with codecs.open(filename, "r", "utf-8") as f:
            self.raw_code = ("".join(f.readlines())).strip()
        self.iptr = 0
        self.dptr = 0
        self.data = [0]*100
        self.loopmap = {} 
        self.insts = []
        self.__parser()

    def __parser(self):
        self.__tokenizer()

    def __get_token(self, pos):
        is_same = lambda raw_code, pos, x: raw_code[pos:pos+len(x)] == x
        quasi_plus   = u"ピョコ"
        quasi_minus  = u"ピョコリ"
        quasi_gt     = u"ピョッコ"
        quasi_lt     = u"ピョ?!"
        quasi_lb     = u"ピョリコ"
        quasi_rb     = u"ピョコ-"
        quasi_comma  = u"ピョコ?!"
        quasi_period = u"ピョッコリン"

        if is_same(self.raw_code, pos, quasi_minus):
            return quasi_minus, pyokolang.minus
        elif is_same(self.raw_code, pos, quasi_period):
            return quasi_period, pyokolang.period
        elif is_same(self.raw_code, pos, quasi_gt):
            return quasi_gt, pyokolang.gt
        elif is_same(self.raw_code, pos, quasi_lt):
            return quasi_lt, pyokolang.lt
        elif is_same(self.raw_code, pos, quasi_lb):
            return quasi_lb, pyokolang.lb
        elif is_same(self.raw_code, pos, quasi_rb):
            return quasi_rb, pyokolang.rb
        elif is_same(self.raw_code, pos, quasi_plus):
            return quasi_plus, pyokolang.plus
        elif self.raw_code[pos:pos+len(quasi_comma)] == quasi_comma:
            return quasi_comma, pyokolang.comma
        else:
            return False, False

    def __tokenizer(self):
        pos = 0
        while pos < len(self.raw_code):
            token, symbol = self.__get_token(pos)
            if token is False:
                pos += 1
                continue
            self.insts.append(symbol)
            pos += len(token)

    def __gen_loopmap(self):
        stack = []
        for i, val in enumerate(self.insts):
            if val == pyokolang.lb:
                stack.append(i)
            elif val == pyokolang.rb:
                if len(stack) == 0:
                    print("parse error: right brackets exists more than left brackets")
                    sys.exit()
                lb_index = stack.pop()
                self.loopmap[i] = lb_index
                self.loopmap[lb_index] = i

        if len(stack) != 0:
            print("parse error: left brackets exists more than right brackets")
            sys.exit()


    def executor(self):
        self.__gen_loopmap()
        while self.iptr < len(self.insts):
            if self.insts[self.iptr] == pyokolang.gt: # >
                self.dptr += 1
                if self.dptr >= len(self.data):
                    for i in range(len(self.data)):
                        self.data.append(0)
            elif self.insts[self.iptr] == pyokolang.lt: # <
                self.dptr -= 1
                if self.dptr < 0:
                    print("data pointer underflow", self.iptr, self.insts, self.dptr, self.data)
            elif self.insts[self.iptr] == pyokolang.plus: # +
                self.data[self.dptr] += 1
            elif self.insts[self.iptr] == pyokolang.minus: # -
                self.data[self.dptr] -= 1
            elif self.insts[self.iptr] == pyokolang.period: # .
                print(chr(self.data[self.dptr]), end="")
            elif self.insts[self.iptr] == pyokolang.comma:  # ,
                self.data[self.dptr] = sys.stdin.read(1)
            elif self.insts[self.iptr] == pyokolang.lb: # [
                if self.data[self.dptr] == 0:
                    self.iptr = self.loopmap[self.iptr]
            elif self.insts[self.iptr] == pyokolang.rb: # ]
                if self.data[self.dptr] != 0:
                    self.iptr = self.loopmap[self.iptr]
            self.iptr += 1

    def run(self):
        self.executor()

def usage():
    print("usage: python pyokolang.py <filename>")
    sys.exit()

def main():
    if len(sys.argv) != 2:
        usage()
    bp = pyokolang(sys.argv[1])
    bp.run()
    print()

if __name__ == "__main__":
    main()

実装に関して

ここまで来ればお気づきの方もいるかもしれませんね。 そう、pyokoは難読で有名なプログラミング言語brainf*ckを ベースに作られています。 実装方針は単純で、brainf*ckを構成する8個の実行命令(+/-/>/</[/]/,/.)を ピョコ感ある単語に置き換えるだけです。

処理の流れも単純で、以下の2ステップです。

  1. ピョコ感ある単語をbrainf*ckの8個の実行命令に置き換え(__tokenizer)

  2. brainf*ckインタプリタで実行(executor)

感想や反省など

  • 感想:意外に簡単にできた。
  • 反省:もっとhello.pyokoを複雑な文章にすべきだった。少々面白みに欠ける。

おまけ

インタプリタソースコードはここにあります。 github.com