"tokenize" --- Python ソース用のトークナイザー
**********************************************

**ソースコード:** Lib/tokenize.py

======================================================================

The "tokenize" module provides a lexical scanner for Python source
code, implemented in Python.  The scanner in this module returns
comments as tokens as well, making it useful for implementing "pretty-
printers", including colorizers for on-screen displays.

トークン列を扱いやすくするため、すべての 演算子 と 区切り文字 のトーク
ン、および "Ellipsis" を、汎用の "OP" トークン型で返します。
"TokenInfo.exact_type" プロパティで、個別のトークン型を取得できます。

警告:

  このモジュールの関数は、構文的に有効な Python コード ("ast.parse()"
  で解析しても例外を送出しないコード) を解析するためだけに設計されてい
  ます。 不正な Python コードを与えた場合の動作は **未定義** であり、
  いつでも変わる可能性があります。


入力のトークン化
================

第一のエントリポイントは *ジェネレータ* です:

tokenize.tokenize(readline)

   "tokenize()" ジェネレータは一つの引数 *readline* を必要とします。こ
   の引数は呼び出し可能オブジェクトで、ファイルオブジェクトの
   "io.IOBase.readline()" メソッドと同じインタフェースを提供している必
   要があります。この関数は呼び出しのたびに入力の一行を bytes で返さな
   ければなりません。

   このジェネレータは "TokenInfo" オブジェクトを生成します。

   バージョン 3.1 で変更: 通常のタプルではなく "TokenInfo" オブジェク
   トを yield するようになりました。

   "tokenize()" は **PEP 263** にしたがって、ソースのエンコーディング
   を UTF-8 BOM か encoding cookie を見つけて決定します。

tokenize.generate_tokens(readline)

   バイト列ではなく Unicode 文字列を読み取り、ソースをトークン化します
   。

   "tokenize()" と同じく、*readline* 引数は入力を 1 行返す呼び出し可能
   オブジェクトです。 ただし "generate_tokens()" では、*readline* は
   bytes ではなく str オブジェクトを返す必要があります。

   結果は "tokenize()" と同じく、"TokenInfo" オブジェクトを yield する
   イテレータです。 "ENCODING" トークンは生成しません。

class tokenize.TokenInfo(type, string, start, end, line)

   トークンを表す *名前付きタプル* です。 5 つのフィールドと、追加の
   "exact_type" プロパティを持ちます。

   Added in version 3.1.

   type

      "NAME" や "STRING" など、"token" の定数で定義される、トークン型
      を識別する整数です。 演算子と区切り文字の型は "OP" です。 その個
      別の型を調べるには "exact_type" を使ってください。

   string

      "str" で表したトークンのテキストです。 "STRING" トークンでは、引
      用符、接頭辞、エスケープシーケンスも、評価せずに含めます。

      "ENCODING" トークンでは、エンコーディング名です。 対応するソース
      テキストなしに挿入されるトークンでは、空文字列になることがありま
      す。 "FSTRING_MIDDLE" と "TSTRING_MIDDLE" のトークンでは、二重の
      波括弧を 1 つの波括弧に置き換えます。

   start

      ソース内でトークンが始まる位置 (その位置を含む) を指定する、整数
      の 2 要素タプル "(srow, scol)" です。 行番号は 1 から、列番号は
      0 から数えます。 列は Unicode コードポイント単位で数え、タブはタ
      ブストップまで展開せず、1 列として数えます。

      "ENCODING" トークンでは "(0, 0)" です。

   end

      トークンの終端位置 (その位置は含まない) を指定する、整数の 2 要
      素タプル "(erow, ecol)" です。 行番号は 1 から、列番号は 0 から
      数えます。 複数行にまたがるトークンでは、*erow* は最後の行、
      *ecol* はその行内の列です。

      例えば、最初の行の先頭にある名前 "spam" では、"start" は "(1,
      0)"、"end" は "(1, 4)" です。

      "ENCODING" トークンでは "(0, 0)" です。 末尾に改行がない入力の終
      わりに挿入する "NEWLINE" や "NL" トークンでは、終端の列がソース
      テキストより 1 列先になることがあります。

   line

      トークンが見つかったソース行の全体を含む "str" です。 前後のテキ
      ストと、行末文字があればそれも含みます。 複数行にまたがるトーク
      ンでは、複数のソース行を含むことがあります。 "ENCODING" と
      "ENDMARKER" のトークンでは空文字列です。

   exact_type

      "OP" トークンについて、"token" の定数で定義される個別の演算子の
      型を識別する整数です。 ほかのトークン型では "type" と同じ値です
      。 例えば "+" トークンでは、"type" は "OP"、"exact_type" は
      "PLUS" です。

      Added in version 3.3.

"token" モジュールのすべての定数は、"tokenize" からも公開されています
。

もう一つの関数がトークン化プロセスを逆転するために提供されています。こ
れは、スクリプトを字句解析し、トークンのストリームに変更を加え、変更さ
れたスクリプトを書き戻すようなツールを作成する際に便利です。

tokenize.untokenize(iterable)

   トークンの列を Python ソースコードに変換します。 *iterable* は少な
   くとも二つの要素、トークンタイプおよびトークン文字列、からなるシー
   ケンスを返さなければいけません。 その他のシーケンスの要素は無視され
   ます。

   結果を再びトークン化すると入力と一致することが保証されるため、情報
   を失わずに往復変換できます。 ただし保証されるのはトークン型とトーク
   ン文字列だけで、トークン間の空白 (列位置) は変わることがあります。

   "tokenize()" が最初に出力するトークンである "ENCODING" を使ってエン
   コードしたバイト列を返します。 入力にエンコーディングのトークンがな
   ければ、代わりに str を返します。

"tokenize()" はトークナイズしようとしているソースファイルのエンコーデ
ィングを検出する必要があります。これを行うために使っている関数が公開さ
れています:

tokenize.detect_encoding(readline)

   "detect_encoding()" 関数は Python のソースファイルをデコードするの
   に使うエンコーディングを検出するために使われます。 "tokenize()" ジ
   ェネレータと同じ readline を引数として取ります。

   "readline" を最大 2 回呼び出し、使うエンコーディング (文字列) と、
   読み込んだ行のリスト (バイト列からデコードしないまま) を返します。

   UTF-8 BOM か **PEP 263** で定義されている encoding cookie からエン
   コーディングを検出します。BOMと cookie の両方が存在し、一致していな
   い場合、"SyntaxError" が送出されます。 BOM が見つかった場合はエンコ
   ーディングとして "'utf-8-sig'" が返されます。

   エンコーディングが指定されていない場合、デフォルトの "'utf-8'" が返
   されます。

   Python のソースファイルを開くには "open()" を使ってください。これは
   "detect_encoding()" を利用してファイルエンコーディングを検出します
   。

tokenize.open(filename)

   "detect_encoding()" を使って検出したエンコーディングを利用して、フ
   ァイルを読み出し専用モードで開きます。

   Added in version 3.2.

exception tokenize.TokenError

   docstring や複数行にわたることが許される式がファイル内のどこかで終
   わっていない場合に送出されます。例えば:

      """docstring の
      始まり

   もしくは:

      [1,
       2,
       3


コマンドラインでの使用
======================

Added in version 3.3.

"tokenize" モジュールは、コマンドラインからスクリプトとして実行できま
す。 次のようにするだけです:

   python -m tokenize [-e] [filename.py]

以下のオプションが使用できます:

-h, --help

   このヘルプメッセージを出力して終了します

-e, --exact

   exact type を使ってトークン名を表示します

"filename.py" が指定された場合、その内容がトークナイズされ stdout に出
力されます。指定されなかった場合は stdin からトークナイズします。

Added in version 3.15: 出力はデフォルトで色付きになり、環境変数で制御
できます。


使用例
======

スクリプト書き換えの例で、浮動小数点数リテラルを Decimal オブジェクト
に変換します:

   from tokenize import tokenize, untokenize, NUMBER, STRING, NAME, OP
   from io import BytesIO

   def decistmt(s):
       """Substitute Decimals for floats in a string of statements.

       >>> from decimal import Decimal
       >>> s = 'print(+21.3e-5*-.1234/81.7)'
       >>> decistmt(s)
       "print (+Decimal ('21.3e-5')*-Decimal ('.1234')/Decimal ('81.7'))"

       The format of the exponent is inherited from the platform C library.
       Known cases are "e-007" (Windows) and "e-07" (not Windows).  Since
       we're only showing 12 digits, and the 13th isn't close to 5, the
       rest of the output should be platform-independent.

       >>> exec(s)  #doctest: +ELLIPSIS
       -3.21716034272e-0...7

       Output from calculations with Decimal should be identical across all
       platforms.

       >>> exec(decistmt(s))
       -3.217160342717258261933904529E-7
       """
       result = []
       g = tokenize(BytesIO(s.encode('utf-8')).readline)  # tokenize the string
       for toknum, tokval, _, _, _ in g:
           if toknum == NUMBER and '.' in tokval:  # replace NUMBER tokens
               result.extend([
                   (NAME, 'Decimal'),
                   (OP, '('),
                   (STRING, repr(tokval)),
                   (OP, ')')
               ])
           else:
               result.append((toknum, tokval))
       return untokenize(result).decode('utf-8')

コマンドラインからトークナイズする例。次のスクリプトが:

   def say_hello():
       print("Hello, World!")

   say_hello()

トークン化すると次の出力になります。 最初の列はトークンが見つかった行
と列の座標の範囲、2 列目はトークン名、最後の列はトークンの値 (ある場合
) です:

   $ python -m tokenize hello.py
   0,0-0,0:            ENCODING       'utf-8'
   1,0-1,3:            NAME           'def'
   1,4-1,13:           NAME           'say_hello'
   1,13-1,14:          OP             '('
   1,14-1,15:          OP             ')'
   1,15-1,16:          OP             ':'
   1,16-1,17:          NEWLINE        '\n'
   2,0-2,4:            INDENT         '    '
   2,4-2,9:            NAME           'print'
   2,9-2,10:           OP             '('
   2,10-2,25:          STRING         '"Hello, World!"'
   2,25-2,26:          OP             ')'
   2,26-2,27:          NEWLINE        '\n'
   3,0-3,1:            NL             '\n'
   4,0-4,0:            DEDENT         ''
   4,0-4,9:            NAME           'say_hello'
   4,9-4,10:           OP             '('
   4,10-4,11:          OP             ')'
   4,11-4,12:          NEWLINE        '\n'
   5,0-5,0:            ENDMARKER      ''

トークンの exact_type 名は "-e" オプションで表示できます:

   $ python -m tokenize -e hello.py
   0,0-0,0:            ENCODING       'utf-8'
   1,0-1,3:            NAME           'def'
   1,4-1,13:           NAME           'say_hello'
   1,13-1,14:          LPAR           '('
   1,14-1,15:          RPAR           ')'
   1,15-1,16:          COLON          ':'
   1,16-1,17:          NEWLINE        '\n'
   2,0-2,4:            INDENT         '    '
   2,4-2,9:            NAME           'print'
   2,9-2,10:           LPAR           '('
   2,10-2,25:          STRING         '"Hello, World!"'
   2,25-2,26:          RPAR           ')'
   2,26-2,27:          NEWLINE        '\n'
   3,0-3,1:            NL             '\n'
   4,0-4,0:            DEDENT         ''
   4,0-4,9:            NAME           'say_hello'
   4,9-4,10:           LPAR           '('
   4,10-4,11:          RPAR           ')'
   4,11-4,12:          NEWLINE        '\n'
   5,0-5,0:            ENDMARKER      ''

バイト列ではなく Unicode 文字列を読み取り、"generate_tokens()" でプロ
グラムからファイルをトークン化する例です。

   import tokenize

   with tokenize.open('hello.py') as f:
       tokens = tokenize.generate_tokens(f.readline)
       for token in tokens:
           print(token)

または、"tokenize()" でバイト列を直接読み取ります。

   import tokenize

   with open('hello.py', 'rb') as f:
       tokens = tokenize.tokenize(f.readline)
       for token in tokens:
           print(token)
