yoshiislandblog.net
元営業の駆け出しアラサーSEが、休日にMACと戯れた際の殴り書きメモ。日々勉強。日々進歩。

この記事は3年以上前に書かれた記事で内容が古い可能性があります

pythonで言語作ってみた(ply(Python Lex-Yacc)使ってみた)

2017-12-31

言語作ってみたいけどc言語かけぬ、、、と思い検索すると、ply(Python Lex-Yacc)というものを発見。使ってみた。
https://github.com/dabeaz/ply

猿でもわかる解説

自分の理解では、3つのパートからできている
「ヘルプ」と打つと使い方を表示してくれる「HELP」という機能を作る例をみていく

(0)requirements

pythonバージョンは3

% python -V
Python 3.5.0

plyというモジュールを入れておく

% pip install ply==3.10

(1)言葉を決める

トークンとやらをまず作る
トークンは文章を品詞分解したあとのパーツのようなもの、今回は「ヘルプ」と一言打つだけなので一個だけ定義する

tokens = (
    'HELP',
    )

HELPトークンに「ヘルプ」または「help」が入るとする
正規表現が使える

def t_HELP(t):
    r'ヘルプ|help'
    return t

(2)やることを決める

lexという、入力された情報を先ほど定義したトークンでどう理解するかを決めるモジュールを読み込む

import ply.lex as lex
lexer = lex.lex()

トークン「HELP」、つまり、先ほど定義した「ヘルプ」か「help」が入力されたら、printで説明文を出すようにする

def p_help(t):
    'expression : HELP'
    print( """
         「退出」と入力すれば終了します
         「入力 <文字列>」と入力すると文字列を記憶します、10個まで記憶できます
         「辞書」と入力すると記憶した文字列を表示します
         「出力 <数字>」と入力すると記憶した文字列を表示します
    """ )

ちなみに

ちなみに、トークン「INPUT」と「VAR」を使って以下のように定義されていた場合、t[1]にINPUT、t[2]にVARの値が入る

def p_input(t):
    'expression : INPUT VAR'

なので

def p_input(t):
    'expression : INPUT VAR'
    print(t[2])

とすると、以下のように値を返すことができる
(INPUTにはinput、VARには文字列何でも入るとする)

何か入力して「Enter」を押してください→ input aa
aa

(3)どう実行するかを決める

最後に、構文解析をするyaccというモジュールを読み込む

import ply.yacc as yacc
parser = yacc.yacc()

入力とパースの制御について記述(雑)

while True:
    try:
        s = input('何か入力して「Enter」を押してください→ ') # Use raw_input on Python 2
    except EOFError:
        break
    if not s: # ignore blank input
        continue
    parser.parse(s)

合体させると

以上合体させるとこんな感じに

# ==============
# 言葉を決める(トークンを定義する)
# ==============
tokens = (
    'HELP',
    )

def t_HELP(t):
    r'ヘルプ|help'
    return t

# ==============
# やることを決める(ソースをトークンに分割)
# ==============
import ply.lex as lex
lexer = lex.lex()

def p_help(t):
    'expression : HELP'
    print( """
         「退出」と入力すれば終了します
         「入力 <文字列>」と入力すると文字列を記憶します、10個まで記憶できます
         「辞書」と入力すると記憶した文字列を表示します
         「出力 <数字>」と入力すると記憶した文字列を表示します
    """ )

# ==============
# どう実行するかを決める(パーサーが構文解析をする)
# ==============
import ply.yacc as yacc
parser = yacc.yacc()

while True:
    try:
        s = input('何か入力して「Enter」を押してください→ ')   # Use raw_input on Python 2
    except EOFError:
        break
    if not s: # ignore blank input
        continue
    parser.parse(s)

これでヘルプだけ実行できるようになる

何か入力して「Enter」を押してください→ ヘルプ

         「退出」と入力すれば終了します
         「入力 <文字列>」と入力すると文字列を記憶します、10個まで記憶できます
         「辞書」と入力すると記憶した文字列を表示します
         「出力 <数字>」と入力すると記憶した文字列を表示します

何か入力して「Enter」を押してください→

コード

機能をちょっと強化すると以下のような感じに
最新版はこちら

tokens = (
    'EXIT',
    'HELP',
    'INPUT',
    'OUTPUT',
    'NUMBER',
    'VAR',
    'DICT',
    )

def t_NUMBER(t):
    r'\d+'
    try:
        t.value = int(t.value)
    except ValueError:
        print("Integer value too large %d", t.value)
        t.value = 0
    return t

t_VAR = r'[A-Za-z一-龥ぁ-んァ-ン_][A-Za-z一-龥ぁ-んァ-ン0-9_]*'
t_ignore = " \t"
t_ignore_COMMENT = r'\#.*'

def t_EXIT(t):
    r'退出|exit'
    return t

def t_HELP(t):
    r'ヘルプ|help'
    return t

def t_INPUT(t):
    r'入力|input'
    return t

def t_OUTPUT(t):
    r'出力|output'
    return t

def t_DICT(t):
    r'メモリ|辞書|dict|mem'
    return t

def t_newline(t):
    r'\n+'
    t.lexer.lineno += t.value.count("\n")

def t_error(t):
    print("Illegal character '%s'" % t.value[0])
    t.lexer.skip(1)

import ply.lex as lex
lexer = lex.lex()

names = { }

def p_help(t):
    'expression : HELP'
    print( """
         「退出」と入力すれば終了します
         「入力 <文字列>」と入力すると文字列を記憶します、10個まで記憶できます
         「辞書」と入力すると記憶した文字列を表示します
         「出力 <数字>」と入力すると記憶した文字列を表示します
    """ )

flag = 0
def p_input(t):
    'expression : INPUT VAR'
    names.update({flag:t[2]})
    global flag
    flag += 1

def p_output(t):
    'expression : OUTPUT NUMBER'
    print(names[t[2]])

def p_dict(t):
    'expression : DICT'
    print(names)

import sys
def p_exit(t):
    'expression : EXIT'
    print('また来てください')
    sys.exit()

def p_error(t):
    print("Syntax error at '%s'" % t.value)

import ply.yacc as yacc
parser = yacc.yacc()

print("""==========================
「ヘルプ」と入力して「Enter」を押すと使い方が表示されます
==========================""")
while True:
    try:
        s = input('何か入力して「Enter」を押してください→ ') # Use raw_input on Python 2
    except EOFError:
        break
    if not s: # ignore blank input
        continue
    parser.parse(s)

実行

実行するとこんな感じ

# python ply_work.py
==========================
「ヘルプ」と入力して「Enter」を押すと使い方が表示されます
==========================
何か入力して「Enter」を押してください→ ヘルプ  

         「退出」と入力すれば終了します
         「入力 <文字列>」と入力すると文字列を記憶します、10個まで記憶できます
         「辞書」と入力すると記憶した文字列を表示します
         「出力 <数字>」と入力すると記憶した文字列を表示します  

何か入力して「Enter」を押してください→ 入力 りんご
何か入力して「Enter」を押してください→ 入力 ぶどう
何か入力して「Enter」を押してください→ 入力 柿
何か入力して「Enter」を押してください→ 辞書
{0: 'りんご', 1: 'ぶどう', 2: '柿'}
何か入力して「Enter」を押してください→ 出力 2
柿
何か入力して「Enter」を押してください→ 退出
また来てください
#

色々ツッコミどころ満載ですが、一旦これで