//------------------------------------------------------------------------------
//  標準入力の各行に指定書式の連番を付けて標準出力する
//------------------------------------------------------------------------------
//
//  コマンド仕様:
//      SeqNo "<書式>" <初期値> <増分>
//
//  <書式>:
//      通常文字部:  そのまま出力する
//      変換規定部:  % で始まる以下の構成部を変換して出力する
//        ・フラグ(省略可)
//            -     左詰
//            +     正の10進数値の場合、+ 符号を付ける
//            空白  正の10進数値の場合、+ 符号の代わりに空白を付ける
//            0     数値の場合、フィールド幅の左余白を 0 で埋める
//        ・フィールド幅(省略可)
//            最少限の出力桁数
//        ・ピリオッド(省略可)
//            フィールド幅と精度の区切り
//        ・精度(省略可)
//            数値の場合、最少限の桁数
//            文字列の場合、最大字数(半角単位)
//        ・変換規定文字(変換規定部の最後の文字、省略不可)
//            連番に対して
//              d   10進数字列(半角)
//              N   10進数字列(全角)
//              x   16進数字列( 10以上の桁は a〜f )(半角)
//              X   16進数字列( 10以上の桁は A〜Z )(半角)
//              a   英小文字1字(半角)  a,b,c,...,z
//              A   英大文字1字(半角)  A,B,C,...,Z
//              z   英小文字1字(全角)  a,b,c,...z
//              Z   英大文字1字(全角)  A,B,C,...Z
//              M   丸付数字1字(全角)  @,A,...,S
//              I   イロハの1字(全角)  イ,ロ,ハ,...,ス
//              i   イ ロ ハ の1字(半角)  イ,ロ,ハ,...,ス
//              H   ひらかな1字(全角)  あ,い,う,...,ん
//              K   カタカナ1字(全角)  ア,イ,ウ,...,ン
//              k   カ タ カ ナ 1字(半角)  ア,イ,ウ,...,ン
//            標準入力に対して
//              p   標準入力から次に読み込んだ1行(改行コードを除く)
//                  %p では、フラグ、フィールド幅、精度の指定は無効
//      エスケープ文字:  \ と次の1字を以下の制御コードに変換して出力する
//          \t      TAB
//          \r      CR
//          \n      LF
//          \f      FF
//          \v      VT
//          \e      ESC
//          これ以外の場合、\ の次の1字をそのまま出力
//      《注意》
//          <書式> 内に、%p がない場合、最後にあると解釈される
//          改行コードは、"<書式>" の最後に出力される
//          <書式> 省略時は、"%d" と解釈される
//  <初期値>:
//      変換    省略/不正  有効指定
//     ------- ----------- -----------------------------------
//      %d          1       10進数字列(半角)
//      %N          1      10進数字列(半角)
//      %x %X       0       16進数字列(半角)
//      その他  最初の文字  その文字範囲内の1字、または、順番
//  <増分>:
//      正または負の10進数字列(半角)
//      省略時は、1 になる
//
//  例:
//      FSeqNo "(%d) %p"    ・・・ 各行の先頭に (1) (2) (3) ... を付加
//      FSeqNo "%N. %p"     ・・・ 各行の先頭に 1. 2. 3. ... を付加
//      FSeqNo "%p%04X"     ・・・ 各行の末尾に、4桁16進数の連番を付加

//------------------------------------------------------------------------------

#include  <stdio.h>
#include  <stdlib.h>

typedef unsigned char  UCHAR;
typedef unsigned short USHORT;
typedef enum {  FALSE = 0, TRUE = 1  }  BOOL;
typedef enum {  EOL_NONE,  EOL_CRLF,  EOL_LF,  EOL_CR  }  EOLTYP;


UCHAR*  pArgFormat = (UCHAR*)"%d";  // コマンド引数の <書式>
UCHAR*  pArgSeqNo = NULL;           // コマンド引数の <初期値>
int     SeqInc = 1;                 // コマンド引数の <増分>

int     SeqNo;                      // 現在の連番値

BOOL    IsSeqInit = FALSE;          // 連番は初期設定済みか?
BOOL    IsStdinEOF = FALSE;         // 標準入力は終了したか?
EOLTYP  EolType = EOL_CRLF;         // 標準入力の改行コード種別

#define  MAX_TXSPEC     100         // 書式内の変換規定部( %... )の最大長


///  関数先行宣言  ///
void    PrintText();
void    SetInitSeqNo( int c );
void    PrintSeqNo( UCHAR* top_tp, UCHAR* end_tp );
void    PrintLine();
void    PrintEOL();
int     Cut( int min, int v, int max );
int     Chop( int min, int v, int max );
int     Cycle( int min, int v, int max );

//-----------------------------------------------------------------------------

int     main( int argc, char *argv[] )
{
    switch( argc )
    {
      default:
      case 4:   SeqInc = atoi( argv[3] );
      case 3:   pArgSeqNo  = (UCHAR*) argv[2];
      case 2:   pArgFormat = (UCHAR*) argv[1];
      case 1:   break;
    }

    PrintText();
    return 1;
}

//-----------------------------------------------------------------------------

// 指定の書式で連番を付けた各行を標準出力にプリントする

void    PrintText()
{
    int     c;
    UCHAR*  cp;
    BOOL    IsLineOut;
    UCHAR   TxSpec[ MAX_TXSPEC + 4 ];   // 変換規定部( %... )の文字列バッファ
    UCHAR*  tp;
    UCHAR*  lim_tp;

    TxSpec[0] = '%';
    cp = pArgFormat;
    IsLineOut = FALSE;

    for( ;; )
    {
      LOOP:
        switch( c = *cp++ )
        {
          case '%':     break;

          case 0:       // <書式> の終端に至った時
          TXSPEC_END:
                if( ! IsLineOut )   // <書式> 内の %p の指定がなかった?
                    PrintLine();
                PrintEOL();

                if( IsStdinEOF )
                    return;
                else if(( c = getchar() ) == EOF )
                    return;
                else
                    ungetc( c, stdin );

                cp = pArgFormat;
                IsLineOut = FALSE;
                continue;

          case '\\':    // エスケープ文字
                switch( *cp++ )
                {
                  case 't':   c = '\t';     break;      // TAB
                  case 'r':   c = '\r';     break;      // CR
                  case 'n':   c = '\n';     break;      // LF
                  case 'f':   c = 0x0C;     break;      // FF
                  case 'v':   c = 0x0B;     break;      // VT
                  case 'e':   c = 0x1B;     break;      // ESC
                  default:    c = cp[-1];   break;      // 未定義
                }
                // 制御は下に行く

          default:      putchar( c );
                        continue;
        }

      // % 以降の変換規定部を走査
        tp = &TxSpec[1];
        lim_tp = &TxSpec[ MAX_TXSPEC ];

        while( tp < lim_tp )
        {
            switch( *tp++ = *cp++ )
            {
              case 'd':     // (半角)10進数字列
              case 'x':     // (半角)16進数字列( 10以上の桁は a〜f )
              case 'X':     // (半角)16進数字列( 10以上の桁は A〜Z )
              case 'a':     // (半角)英小文字1字
              case 'A':     // (半角)英大文字1字
              case 'z':     // (全角)英小文字1字
              case 'Z':     // (全角)英大文字1字
              case 'N':     // (全角)10進数字列
              case 'M':     // (全角)丸付数字(1字)
              case 'I':     // (全角)イロハ  (1字)
              case 'i':     // (半角)イ ロ ハ   (1字)
              case 'H':     // (全角)ひらかな(1字)
              case 'K':     // (全角)カタカナ(1字)
              case 'k':     // (半角)カ タ カ ナ (1字)
                *tp = 0;
                if( ! IsSeqInit )
                    SetInitSeqNo( tp[-1] );
                PrintSeqNo( TxSpec, tp - 1 );
                goto LOOP;
                break;

              case 'p':     // 標準入力の1行を標準出力
                PrintLine();
                IsLineOut = TRUE;
                goto LOOP;

              case '-':  case '+':  case ' ':  case '.':
              case '0':  case '1':  case '2':  case '3':  case '4':
              case '5':  case '6':  case '7':  case '8':  case '9':
                continue;

              case 0:   // 変換規定文字がない場合
                fputs( (char*) TxSpec, stdout );    // そのまま標準出力
                goto TXSPEC_END;

              default:  // 変換規定文字が所定外の場合
                *--tp = 0;
                fputs( (char*) TxSpec, stdout );    // そのまま標準出力
                --cp;
                goto LOOP;
            }
            //(注)ここに来ることはない
        }
        // 変換規定文字列部が長すぎる場合
        c = cp[-1];
        if(( 0x81 <= c  &&  c <= 0x9F ) || ( 0xE0 <= c  &&  c <= 0xFC ))
            --cp, --tp;     // 全角第1バイトの場合
        *tp = 0;
        fputs( (char*) TxSpec, stdout );    // そのまま標準出力
        //goto LOOP;
    }
}

//-----------------------------------------------------------------------------

///  各種の単字連番表  ///

const USHORT  HankLower[] = 
{
    'a','b','c','d','e','f','g','h','i','j','k','l','m',
    'n','o','p','q','r','s','t','u','v','w','x','y','z',
};

const USHORT  HankUpper[] = 
{
    'A','B','C','D','E','F','G','H','I','J','K','L','M',
    'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
};

const USHORT  ZenkLower[] = 
{
    'a','b','c','d','e','f','g','h','i','j','k','l','m',
    'n','o','p','q','r','s','t','u','v','w','x','y','z',
};

const USHORT  ZenkUpper[] = 
{
    'A','B','C','D','E','F','G','H','I','J','K','L','M',
    'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
};

#define  N_ALPHAS   26


const USHORT  MaruNum[] =
{
    '@','A','B','C','D','E','F','G','H','I',
    'J','K','L','M','N','O','P','Q','R','S',
};

#define  N_MARUS  ( sizeof( MaruNum ) / sizeof( USHORT ))


const USHORT  ZenkIroha[] = 
{
    'イ','ロ','ハ','ニ','ホ','ヘ','ト','チ','リ','ヌ','ル','ヲ',
    'ワ','カ','ヨ','タ','レ','ソ','ツ','ネ','ナ','ラ','ム',
    'ウ','ヰ','ノ','オ','ク','ヤ','マ','ケ','フ','コ','エ','テ',
    'ア','サ','キ','ユ','メ','ミ','シ','ヱ','ヒ','モ','セ','ス',
};

const USHORT  HankIroha[] = 
{
    'イ','ロ','ハ','ニ','ホ','ヘ','ト','チ','リ','ヌ','ル','ヲ',
    'ワ','カ','ヨ','タ','レ','ソ','ツ','ネ','ナ','ラ','ム',
    'ウ','ィ','ノ','オ','ク','ヤ','マ','ケ','フ','コ','エ','テ',
    'ア','サ','キ','ユ','メ','ミ','シ','ェ','ヒ','モ','セ','ス',
};
// 《注意》「ヰ」,「ヱ」 に相当する文字がないため 「ィ」,「ェ」 で代用

#define  N_IROHAS   47


const USHORT  ZenkHira[] = 
{
    'あ', 'い', 'う', 'え', 'お',
    'か', 'き', 'く', 'け', 'こ',
    'さ', 'し', 'す', 'せ', 'そ',
    'た', 'ち', 'つ', 'て', 'と',
    'な', 'に', 'ぬ', 'ね', 'の',
    'は', 'ひ', 'ふ', 'へ', 'ほ',
    'ま', 'み', 'む', 'め', 'も',
    'や', 'ゆ', 'よ',
    'ら', 'り', 'る', 'れ', 'ろ',
    'わ', 'を', 'ん',
};

const USHORT  ZenkKata[] = 
{
    'ア', 'イ', 'ウ', 'エ', 'オ',
    'カ', 'キ', 'ク', 'ケ', 'コ',
    'サ', 'シ', 'ス', 'セ', 'ソ',
    'タ', 'チ', 'ツ', 'テ', 'ト',
    'ナ', 'ニ', 'ヌ', 'ネ', 'ノ',
    'ハ', 'ヒ', 'フ', 'ヘ', 'ホ',
    'マ', 'ミ', 'ム', 'メ', 'モ',
    'ヤ', 'ユ', 'ヨ',
    'ラ', 'リ', 'ル', 'レ', 'ロ',
    'ワ', 'ヲ', 'ン',
};

const USHORT  HankKata[] = 
{
    'ア', 'イ', 'ウ', 'エ', 'オ',
    'カ', 'キ', 'ク', 'ケ', 'コ',
    'サ', 'シ', 'ス', 'セ', 'ソ',
    'タ', 'チ', 'ツ', 'テ', 'ト',
    'ナ', 'ニ', 'ヌ', 'ネ', 'ノ',
    'ハ', 'ヒ', 'フ', 'ヘ', 'ホ',
    'マ', 'ミ', 'ム', 'メ', 'モ',
    'ヤ', 'ユ', 'ヨ',
    'ラ', 'リ', 'ル', 'レ', 'ロ',
    'ワ', 'ヲ', 'ン',
};

#define  N_KANAS   46

//-----------------------------------------------------------------------------

// 指定の変換規定文字に基づいて、連番のタイプと初期値を決定する

void    SetInitSeqNo( int c )
{
    UCHAR*  cp;
    const USHORT*  kp;
    int     i;
    int     N;
    int     k;

    IsSeqInit = TRUE;

    switch( c )
    {
      default:
      case 'd':     // 10進数字列(半角)
      case 'N':     // 10進数字列(全角)
        if( pArgSeqNo != NULL )
            SeqNo = atoi( (char*) pArgSeqNo );
        else
            SeqNo = 1;
        return;

      case 'x':     // 16進数字列( 10以上の桁は a〜f )(半角)
      case 'X':     // 16進数字列( 10以上の桁は A〜Z )(半角)
        SeqNo = 0;
        if( pArgSeqNo != NULL )
        {
            for( cp = pArgSeqNo ;; cp++ )
            {
                switch( *cp )
                {
                  case '0':  case '1':  case '2':  case '3':  case '4':
                  case '5':  case '6':  case '7':  case '8':  case '9':
                    SeqNo = ( SeqNo << 4 ) + ( *cp - '0' );
                    continue;

                  case 'A':  case 'B':  case 'C':  case 'D':  case 'E':  case 'F':
                    SeqNo = ( SeqNo << 4 ) + ( *cp - 'A' + 10 );
                    continue;

                  case 'a':  case 'b':  case 'c':  case 'd':  case 'e':  case 'f':
                    SeqNo = ( SeqNo << 4 ) + ( *cp - 'a' + 10 );
                    continue;

                  default:  break;
                }
                break;
            }
        }
        return;

      case 'a':     // 英小文字半角1字
        kp = HankLower;
        N = N_ALPHAS;
        break;

      case 'A':     // 英大文字半角1字
        kp = HankUpper;
        N = N_ALPHAS;
        break;

      case 'z':     // 英小文字全角1字
        kp = ZenkLower;
        N = N_ALPHAS;
        break;

      case 'Z':     // 英大文字全角1字
        kp = ZenkUpper;
        N = N_ALPHAS;
        break;

      case 'I':     // イロハ(全角1字)
        kp = ZenkIroha;
        N = N_IROHAS;
        break;

      case 'i':     // イ ロ ハ (半角1字)
        kp = HankIroha;
        N = N_IROHAS;
        break;

      case 'H':     // (全角)ひらかな(1字)
        kp = ZenkHira;
        N = N_KANAS;
        break;

      case 'K':     // (全角)カタカナ(1字)
        kp = ZenkKata;
        N = N_KANAS;
        break;

      case 'k':     // (半角)カ タ カ ナ (1字)
        kp = HankKata;
        N = N_KANAS;
        break;

      case 'M':     // 丸付数字(全角1字)
        kp = MaruNum;
        N = N_MARUS;
        break;
    }

    SeqNo = 0;
    if( pArgSeqNo != NULL )     // 連番の <初期値> の指定あり?
    {
        if( *pArgSeqNo >= 0x80 )    // <初期値> は、実文字で指定?
        {
            if( *kp < 0x100 )   // 対象は半角?
                k = *pArgSeqNo;
            else                // 対象は全角
                k = ( pArgSeqNo[0] << 8 ) + pArgSeqNo[1];

            for( i = 0 ; i < N ; i++ )
            {
                if( kp[i] == k )
                {
                    SeqNo = i;
                    break;
                }
            }
        }
        else    // <初期値> が番号(数字)で指定されている場合
            SeqNo = Chop( 1, atoi( (char*) pArgSeqNo ), N ) - 1;
    }
    return;
}

//-----------------------------------------------------------------------------

// 指定の変換規定に従って、現連番を標準出力にプリントし、連番を次値に更新
//    top_tp = 書式変換規定部の最初の文字( % )のアドレス
//    end_tp = 書式変換規定部の最後の文字( 変換規定文字 )のアドレス

void    PrintSeqNo( UCHAR* top_tp, UCHAR* end_tp )
{
    int     v;
    UCHAR   s[4];

    v = SeqNo;
    SeqNo += SeqInc;

    switch( *end_tp )
    {
      case 'd':     // 10進数字列(半角)
      case 'x':     // 16進数字列( 10以上の桁は小文字 )
      case 'X':     // 16進数字列( 10以上の桁は大文字 )
        printf( (char*) top_tp, v );
        break;

      case 'a':     // 英小文字半角1字
        SeqNo = Cycle( 0, SeqNo, N_ALPHAS - 1 );
        v = HankLower[ v ];
      PRINT_SEQ_HANK:
        *end_tp = 'c';
        printf( (char*) top_tp, v );
        break;

      case 'A':     // 英大文字半角1字
        SeqNo = Cycle( 0, SeqNo, N_ALPHAS - 1 );
        v = HankUpper[ v ];
        goto PRINT_SEQ_HANK;

      case 'z':     // 英小文字(全角1字)
        SeqNo = Cycle( 0, SeqNo, N_ALPHAS - 1 );
        v = ZenkLower[ v ];
      PRINT_SEQ_ZENK:
        s[0] = v >> 8;
        s[1] = v & 0xFF;
        s[2] = 0;
        *end_tp = 's';
        printf( (char*) top_tp, s );
        break;

      case 'Z':     // 英大文字(全角1字)
        SeqNo = Cycle( 0, SeqNo, N_ALPHAS - 1 );
        v = ZenkUpper[ v ];
        goto PRINT_SEQ_ZENK;

      case 'M':     // 丸付数字(全角1字)
        SeqNo = Cycle( 0, SeqNo, N_MARUS - 1 );
        v = MaruNum[ v ];
        goto PRINT_SEQ_ZENK;

      case 'I':     // イロハ(全角1字)
        SeqNo = Cycle( 0, SeqNo, N_IROHAS - 1 );
        v = ZenkIroha[ v ];
        goto PRINT_SEQ_ZENK;

      case 'i':     // イ ロ ハ (半角1字)
        SeqNo = Cycle( 0, SeqNo, N_IROHAS - 1 );
        v = HankIroha[ v ];
        goto PRINT_SEQ_HANK;

      case 'H':     // (全角)ひらかな(1字)
        SeqNo = Cycle( 0, SeqNo, N_KANAS - 1 );
        v = ZenkHira[ v ];
        goto PRINT_SEQ_ZENK;

      case 'K':     // (全角)カタカナ(1字)
        SeqNo = Cycle( 0, SeqNo, N_KANAS - 1 );
        v = ZenkKata[ v ];
        goto PRINT_SEQ_ZENK;

      case 'k':     // (半角)カ タ カ ナ (1字)
        SeqNo = Cycle( 0, SeqNo, N_KANAS - 1 );
        v = HankKata[ v ];
        goto PRINT_SEQ_HANK;

      case 'N':     // 10進数字列(全角)
        {
            #define MAX_SEQNO_LEN  100
            UCHAR   SeqNoText[ MAX_SEQNO_LEN + 4 ];
            UCHAR*  cp;
            int     c;

            // 現連番値を10進数字列(半角)に変換
            *end_tp = 'd';
            if( _snprintf( (char*) SeqNoText, MAX_SEQNO_LEN,
                    (char*) top_tp, v ) < 0 )
                SeqNoText[ MAX_SEQNO_LEN ] = 0; // 最大サイズを超えた場合

            // 半角→全角に変換して出力
            for( cp = SeqNoText ; ( c = *cp++ ) != 0 ; )
            {
                if( '0' <= c  &&  c <= '9' )
                    c = '0' + ( c - '0' );
                else if( c == '-' )
                    c = '−';
                else if( c == ' ' )
                    c = ' ';
                //(注)それ以外の文字は、そのまま

                if( c >= 0x100 )
                    putchar( c >> 8 );
                putchar( c & 0xFF );
            }
        }
        break;
    }
}

//-----------------------------------------------------------------------------

// 標準入力から読み出した1行分を標準出力する(ここでは改行コードは出力しない)

void    PrintLine()
{
    int  c;

    for( ;; )
    {
        switch( c = getchar() )
        {
          case '\r':    switch( c = getchar() )
                        {
                          default:      ungetc( c, stdin );
                                        EolType = EOL_CR;
                                        break;
                          case EOF:     IsStdinEOF = TRUE;
                                        EolType = EOL_CR;
                                        break;
                          case '\n':    EolType = EOL_CRLF;
                                        break;
                        }
                        break;

          case '\n':    EolType = EOL_LF;
                        break;

          case EOF:     IsStdinEOF = TRUE;
                        EolType = EOL_NONE;
                        break;

          default:      putchar( c );
                        continue;
        }
        break;
    }
}

//-----------------------------------------------------------------------------

// 標準入力から読み出したのと同じ改行コードを標準出力する

void    PrintEOL()
{
    switch( EolType )
    {
      case EOL_NONE:    break;
      case EOL_CRLF:    putchar( '\r' );
      case EOL_LF:      putchar( '\n' );    break;
      case EOL_CR:      putchar( '\r' );    break;
    }
}

//-----------------------------------------------------------------------------

int  Cut( int min, int v, int max )
{
    return ( v <= min ) ? min : (( max <= v ) ? max : v ) ;
}

int  Cycle( int min, int v, int max )
{
    int  d = max - min + 1;
    while( v < min )
        v += d;
    return ( v - min ) % d + min;
}

int  Chop( int min, int v, int max )
{
    return ( v < min || max < v ) ? min : v ;
}

//-----------------------------------------------------------------------------