ソースコードにスクリプトを埋め込んで、ソースコードを生成させる

はじめに

わりと一発ネタ。大したことやってません。仕事で必要に感じたので作ってみました。せっかくなのでここでも紹介。

タイトルをもっと適切なものに書き換えたい。

背景

諸々の事情から、部分的に冗長なコードを書かないといけない時があると思います。

そんなときは、コードを生成するスクリプトを書くのが一般的な対応法かと思います。

例えばこんな感じに。(もうちょっと現実にありえそうな例を出したかったのですが……)

% cat hoge.cpp

void hogehoge( ) {

/* ここに冗長なコードを書きたい */

}

... snip ...

% cat hoge.py
#!/usr/bin/python

for num in range( 20 ):
  print '  hoge%02d = fuga[%d] ;' % ( num, num )

% ./hoge.py
  hoge00 = fuga[0] ;
  hoge01 = fuga[1] ;
  hoge02 = fuga[2] ;

... snip ...

  hoge19 = fuga[19] ;

で、この./hoge.pyの出力を./hoge.cppの該当部分にコピペする、と。

問題

このやり方だと、hoge.pyとhoge.cppの二つを管理しないといけないのが嫌な感じです。

解決案

じゃあhoge.cppの中にhoge.pyが埋め込めればいいんじゃね? って考えてこんなpythonコードを書いてみました。

#!/usr/bin/python

"""
embedded_procedure.py

this script enables us to embed some script into your source code.
"""

import sys
import optparse
import re
import commands

if __name__ == '__main__':

  usage = "%prog [options] [filename]"
  parser = optparse.OptionParser( usage=usage )
  parser.parse_args( )

  if len( sys.argv ) == 1:
    stream = sys.stdin
  elif len( sys.argv ) == 2:
    stream = open( sys.argv[ 1 ], 'r' )
  else:
    parser.print_help( )
    exit( )

  command_file = '.tmp.command'
  command = ''

  re_begin = re.compile( r'^\s*/\*\*\*\*\s*([^\s]+)' )
  re_head  = re.compile( r'^\s*\*\s' )
  re_end   = re.compile( r'^\s*\*/' )
  match = False
  buffer = ''
  for line in stream:
    m = re_begin.match( line )
    if m:
      match = True
      command = m.group( 1 )
    elif match and re_end.match( line ):
      match = False
      fp = open( command_file, 'w' )
      fp.write( buffer )
      fp.close( )
      print commands.getoutput( '%s %s' % ( command, command_file ) )
      commands.getoutput( 'rm -f %s' % command_file )
      buffer = ''
    elif match:
      buffer += re_head.sub( '', line )
    else:
      print line,

これを使うと、先の例はこんな感じに書き換えられます。

% cat hoge.cpp

void hogehoge( ) {

  /**** python
   * for num in range( 20 ):
   *   print '  hoge%02d = fuga[%d] ;' % ( num, num )
   */

}

... snip ...

% ./embedded_procedure.py hoge.cpp

void hogehoge( ) {

  hoge00 = fuga[0] ;
  hoge01 = fuga[1] ;
  hoge02 = fuga[2] ;

... snip ...

  hoge19 = fuga[19] ;

}

... snip ...

これで、hoge.cppのみを管理すればよくなりました。

使い方

テキスト中にこのような記述があると

(テキストならば何でもok *.cppである必要はない)

  /**** command
   * program
   */

以下と等価な処理が行われます。

% echo program > .tmp.command
% command .tmp.command
% rm .tmp.command

commandの部分をpythonではなくrubyperlにしておけば、それぞれのスクリプト言語を埋め込むことも出来ます。

終わりに

./hoge.cppと./embedded_procedure.py ./hoge.cppによって出力されたファイルと、結局は二つ管理しないといけないケースが多そうな気も。

あと、もっと一般的なツールや解決法がありそうな気も。