こんにちは山本です。

COBOLの二回目です。

前回の最後に、フレームワークかパンチカードと書きました。

どちらにしたものかと、色々悩みました。が、フレームワークは別の記事でも出来そうですが、パンチカードは今回の記事でしか出来ない!と思ったので今回はパンチカードです

パンチカードについて

さて、パンチカードとはなんなのでしょうか?
実は良く分かっていません。

  • 幾つか知っていることを上げると、昔はプログラムを組むのに使っていた。
  • パンチャーという職業があった。

ぐらいだったりします。

まあ、調べながらやっていけばいいでしょう。

パンチカードの使い方

さて、パンチカード検索してみると、実物の画像がいくつも見つかります。
画像を見ながら、どういうものかを見ていきましょう。

まず、縦に0から9までの数字が書いています。
そして横に、80個続いています。

恐らく、0をパンチするとそのカラムは0で、1をパンチするとそのカラムは当然1となるのでしょう。

これは見ただけで、予想がつきます。

で、アルファベットはどうするの?

流石に数字だけしか使えませんってことは無いと思います。
COBOLもれっきとした高級言語ですからね。

……、どう考えても分からないので、wikiを見てみました。

     ______________________________________________
    /&-0123456789ABCDEFGHIJKLMNOPQR/STUVWXYZ
 Y / x           xxxxxxxxx
 X|   x                   xxxxxxxxx
 0|    x                           xxxxxxxxx
 1|     x        x        x        x
 2|      x        x        x        x
 3|       x        x        x        x
 4|        x        x        x        x
 5|         x        x        x        x
 6|          x        x        x        x
 7|           x        x        x        x
 8|            x        x        x        x
 9|             x        x        x        x
  |________________________________________________

なるほど、複数パンチすることでアルファベットを表現出来るんですね。
あと、記号少ないですね。「=」も使えないの?

ん?今更ながらに気づいたんですが、もしかしてカード一枚で一行ってこと?

ただでさえ、冗長なCOBOLをパンチカードでやったら、えらい枚数になりそうですね。

EBCDIC規格

wikiを見ていると、パンチカードというものは色々種類があるようです。

読む限り、EBCDIC規格がポピュラーなもののようですので、この規格でやっていこうと思います。

で、EBCDIC規格で表現出来る文字が画像の上の方に書いてますが、案外多くの記号を使えそうな感じです。

記号を一つずつ見ていくと、見たことのない記号が出てきました。

まず、Zの右にあるこの記号何?
怒りマーク?

いや、それよりも「)」の右の記号と、「;」の右の記号は何なのでしょう?

とりあえず、キーボードから打てる記号を全てタイプしてみたのですが、それらしい記号が見つかりません。

散々調べて、ようやくIBMのサイトにEBCDICについて書いてあるのを見つけました。

ASCII 文字セットと EBCDIC 文字セット

EBCDIC シンボルを上から眺めていくとそれらしいものがありました。

どうやら、Cっぽい記号は「セント」だそうです。
そして、アンダーバーっぽい記号は「論理否定」なんだそうです。

長年、働いてきましたがこんな記号初めて見ました。

何に使うかさっぱり分かりませんが、そういうものなのでしょう。。。

あと、この記号2つともASCIIコードにないですね。

そうそう、怒りマークぽいのはどうも「#」だったようです。

仕様

パンチカードのあらましは分かったので、仕様を決めていこうと思います。

とりあえず、完成版を見せることも考えると、ブラウザでというのが都合良さそうです。
なので、JavaScriptでパンチカードを作る、枚数は可変がいいですね。

上に足したり、下に足したり、あと消せたりも必要ですね。

それと、保存機能が欲しいです。

一行書くだけでも大変そうですので、保存、読み込み機能を付けましょう。

そして一番の問題は、実行はどうするか?

パンチカードをパンチしました。
はい、終わりではちょっと物足りないですね。

やはり、実行して結果が見るところまでは必要だと思います。

ブログサーバにCOBOLが動くようにセットアップして、APIを作ってもいいのですが

セキュリティ的にどうなんでしょう?
そもそもCOBOL詳しくないんで、禁止しないといけない処理などは分かりません。

ブログサーバでCOBOLを走らせなくても良い方法を色々調べたところ
Paiza.IOという、ブラウザ上で幾つかの言語のコードを実行出来るサービスが見つかりました。

幾つかの中にCOBOLも含まれており、ここで実行させればいいのではと考えました。

幸いにしてAPIも用意されているようです。

なので、以下の仕様で作成します。

  • パンチカードはjsで実装する
    • 枚数は可変にする
    • パンチした情報をエクスポート、インポート出来るようにする。
  • 実行は外部のAPIを使用する。

パンチカードを作る

いつものように、長々と過程を書き連ねていってもいいのですが
既に、長くなってきたので、出来たものを書きます。

;(function($){
  $.fn.punchCard = function(settings){
    var options = {
      template       : '',
      useDownload    : true,
      downloadBotton : '.download'
    };

    $.extend(options, settings);

    options.self = $(this);

    $.extend(options, {
      colCount : 80,
      display  : [],
      count    : [],
      space    : [],
      zone     : {x:[], y:[]},
      checks   : []
    });

    var words   = {
      0     : 0,
      1     : 1,
      2     : 2,
      3     : 3,
      4     : 4,
      5     : 5,
      6     : 6,
      7     : 7,
      8     : 8,
      9     : 9,
      X     : '&',
      X1    : 'A',
      X2    : 'B',
      X3    : 'C',
      X4    : 'D',
      X5    : 'E',
      X6    : 'F',
      X7    : 'G',
      X8    : 'H',
      X9    : 'I',
      X28   : '¢',
      X38   : '.',
      X38   : '.',
      X48   : '<',
      X58   : '(',
      X68   : '+',
      X78   : '|',
      Y     : '-',
      Y1    : 'J',
      Y2    : 'K',
      Y3    : 'L',
      Y4    : 'M',
      Y5    : 'N',
      Y6    : 'O',
      Y7    : 'P',
      Y8    : 'Q',
      Y9    : 'R',
      Y28   : '!',
      Y38   : '$',
      Y48   : '*',
      Y58   : ')',
      Y68   : ';',
      Y78   : '¬',
      '01'  : '/',
      '02'  : 'S',
      '03'  : 'T',
      '04'  : 'U',
      '05'  : 'V',
      '06'  : 'W',
      '07'  : 'X',
      '08'  : 'Y',
      '09'  : 'Z',
      '038' : ',',
      '048' : '%',
      '058' : '_',
      '068' : '>',
      '078' : '?',
      '28'  : ':',
      '38'  : '#',
      '48'  : '@',
      '58'  : "'",
      '68'  : '=',
      '78'  : '"',
    };

    if (options.template){
      $.ajax({
        url : options.template,
        async : false,
        success : function(data){
          $('head').append(data);
        }
      });
    }

    for (var i = 0; i < options.colCount; i++){
      options.display.push($("#displayTemplate").render(i));
      options.space.push($("#spaceTemplate").render(i));
      options.count.push($("#countTemplate").render(i + 1));

      options.zone.x.push($("#checkAreaTemplate").render({value:'X', display: '', column: i}));
      options.zone.y.push($("#checkAreaTemplate").render({value:'Y', display: '', column: i}));

      for (var j = 0; j <= 9; j++){
        if (!options.checks[j]){
          options.checks[j] = [];
        }
        options.checks[j].push($("#checkAreaTemplate").render({value:j, display: j, column: i}));
      }
    }

    options.item = {
      display : options.display.join(''),
      space   : options.space.join(''),
      count   : options.count.join(''),
      zone    : {
        x : options.zone.x.join(''),
        y : options.zone.y.join('')
      },
      checks  : [],
    };

    for (var j = 0; j <= 9; j++){
      options.item.checks[j] = options.checks[j].join('');
    }

    options.slot = $('<div>').attr('id', 'cardSlot');
    options.slot.append($('#itemTemplate').render(options.item));

    options.self.prepend(options.slot);

    options.statement = $('<div>').attr('id', 'statement');
    options.self.append(options.statement.hide());

    var set = function(){
      $('.punch').on('click', function(){
        var $card   = $(this).closest('.card');
        var checked = [];

        if ($(this).hasClass('active')){
          $(this).removeClass('active');
        }
        else{
          $(this).addClass('active');
        }

        $('.display_'+$(this).attr('data-column'), $card).text('');
        $('.column_'+$(this).attr('data-column'), $card).each(function(){
          if ($(this).hasClass('active')){
            checked.push($(this).data('value'));
          }
        });

        var selected = checked.join('');
        $('.display_'+$(this).attr('data-column'), $card).text(selected in words ? words[selected] : '');

        updateStatement();
      });

      $('.addTop').on('click', function(){
        $(this).closest('.card').before($('#itemTemplate').render(options.item));
        reset();
      });
      $('.addBottom').on('click', function(){
        $(this).closest('.card').after($('#itemTemplate').render(options.item));
        reset();
      });
      $('.deleteRow').on('click', function(){
        $(this).closest('.card').remove();
        console.log($('.deleteRow').size());
        reset();
      });
    }

    var reset = function(){
      $('.punch, .addTop, .addBottom, .deleteRow').off('click');
      set();
    };

    var getJsonCode = function(){
      var ret = [];
      $('.card').each(function(){
        var selected = [];
        for (var i = 0; i < options.colCount; i++){
          var checked = [];
          $('.column_'+i, $(this)).each(function(){
            if ($(this).hasClass('active')){
              checked.push($(this).data('value'));
            }
          });
          selected.push(checked.join(''));
        }
        ret.push(selected);
      });
      return ret;
    }

    if (options.useDownload){
      $(options.downloadBotton).on('click', function(){
        var blob = new Blob(
          [JSON.stringify(getJsonCode())],
          { type: 'application\/json' }
        );

        $(this).attr({
          download:'code.json',
          href:URL.createObjectURL(blob)
        });
      });
    }
    else{
      $(options.downloadBotton).remove();
    }

    var updateCard = function(values){
      options.slot.children('.card').remove();
      for (var i in values){
        options.slot.append($('#itemTemplate').render(options.item));
        var current = options.slot.children('.card').last();
        for (var j in values[i]){
          var col = values[i][j];
          if (col != ''){
            var checked = col.split('');
            $('.column_'+j, current).each(function(){
              if ($.inArray($(this).attr('data-value'), checked) != -1){
                $(this).addClass('active');
              }
            });
            $('.display_'+j, current).text(col in words ? words[col] : '');
          }
        }
      }
      updateStatement();
      reset();
    }

    var updateStatement = function(){
      var statements = [];

      $('.card', options.slot).each(function(){
        var row = '';
        $(this).find('.display').each(function(){
          row += $(this).text() ? $(this).text() : ' ';
        });
        statements.push(row);
      });
      options.statement.text(statements.join("\n")+"\n");
    }

    $(document).bind('drop', function(e){
      e.preventDefault();
      var files = e.originalEvent.dataTransfer.files;

      var reader = new FileReader();

      reader.onload = function(e) {
        try{
          updateCard(JSON.parse(reader.result));
        }
        catch(e){
          console.log(e);
          alert("ファイルの形式が正しくありません。")
        }
      }

      reader.readAsText(files[0], 'UTF-8');

    }).bind('dragenter dragover', function(){
      return false;
    });

    set();
  };
})(jQuery);

はい、パンチカードです。

使ってみて分かったんですが、一行書くだけでもとても時間がかかりますね。

API

次は実行出来るようにします。

ドキュメント

APIのドキュメントを読んでみます。

簡単にまとめると
まず、createで実行文を送り、IDを取得する。

次に、get_statusで取得したIDを送り、createで行った処理の状態を確認する。

処理が終わったのを確認したら、get_detailsで実行結果を取得するようです。

get_statusをポーリングする必要があるくらいで、特に難しいことはないようですね。

なので、実装しました。

;(function($){
  $.fn.paizaIO = function(settings){
    var options = {
      display  : undefined,
      source   : undefined,
      language : undefined,
      api_key  : 'guest'
    };

    $.extend(options, settings);

    options.self = $(this);
    options.allowLanguage = ['c', 'cpp', 'objective-c', 'java', 'scala', 'swift', 'csharp', 'go', 'haskell', 'erlang', 'perl', 'python', 'python3', 'ruby', 'php', 'bash', 'r', 'javascript', 'coffeescript', 'vb', 'cobol', 'fsharp', 'd', 'clojure', 'mysql'];

    var isAllowLanguage = function(language){
      return $.inArray(language.toLowerCase(), options.allowLanguage) != -1;
    };

    if (!isAllowLanguage(options.language)){
      alert('使用可能な言語以外が指定されています。');
    }

    var getStatus = function(id){
      var timer = setInterval(function(){
        $.ajax({
          type: "get",
          url: "http://api.paiza.io/runners/get_status",
          data: {
            id       : id,
            api_key  : options.api_key
          },
          success: function(res){
            if (res.status == 'completed'){
              clearInterval(timer);
              getResult(id);
            }
          },
          error: function(){
            clearInterval(timer);
            alert('通信に失敗しました。');
          }
        })
      }, 500);
    };

    var getResult = function(id){
      $.ajax({
        type: "get",
        url: "http://api.paiza.io/runners/get_details",
        data: {
          id      : id,
          api_key : options.api_key
        },
        success: function(res){
          showResult(res);
        }
      });
    };

    var showResult = function(res){
      if ($.inArray(res.build_result, ['failure', 'error']) != -1){
        $(options.display).text(res.build_stderr);
      }
      else{
        $(options.display).text(res.stdout);
      }
      buttonReset();
    };

    var buttonReset = function(){
      options.button.button('reset');
    };

    options.self.on('click', function(){
      options.button = $(this).button('loading');
      if (options.source && $(options.source).is('*') && options.language){
        var source = '';
        switch ($(options.source)[0].localName){
          case 'input':
          case 'textarea':
            source = $(options.source).val();
          default:
            source = $(options.source).text();
        }
        if (source && isAllowLanguage(options.language)){
          $.ajax({
            type: 'post',
            url: 'http://api.paiza.io/runners/create',
            data: {
              source_code : source,
              language    : options.language,
              api_key     : 'guest'
            },
            success: function(res){
              getStatus(res.id);
            },
            error: function(res){
              alert('通信に失敗しました。')
              buttonReset();
            }
          });
        }
      }
    });
  };
})(jQuery);

少々というか、かなりエラー処理を端折っているのですが、動くのでよいでしょう。

使ってみる

パンチカードと、APIを組み合わせてみました。

流石に書いた事が無い方に、COBOLを試してみてとは言えませんので、簡単なサンプルファイルを用意しました。

このサンプルを、画面にドラグしていただくと、パンチされた状態となります。

パンチした状態で、ページ下部の実行を押して頂くと、パンチカードの内容が実行されます。

サンプルが簡単すぎるとか、ご意見もあるかと思いますが、これ打つの結構大変なんですよ。

あと、PaizaIOの仕様上、COBOLは8カラム目から記述する必要があるようです。

余り物

ふと思いました。

パンチカードで使用出来る記号も案外多いですし、APIも色々な言語に対応しているので他の言語でも出来るのではないか?

なので、PHP版も用意しました。

サンプルファイルも用意してありますので、お試しください。

処理が英字大文字で書け、一部の記号でのみ記述が出来る言語であれば、どれもパンチカードで書くことが出来そうですね。

まとめ

お題はCOBOLだったはずなんですが、ほとんどパンチカードの話になっていますね。

まあ、これでパンチカードを触ったことがないかたでも、パンチカードが如何なるものか分かるのではないかと思います。

そうそう、パンチカードを調べていた際に引き込まれる記事がありました。
http://blog.mwsoft.jp/article/75825730.html

  • 4~5回コンパイラにかけられる
  • バイナリのパンチカードが生成される
  • 哲学的を話す機会

凄い時代もあったものです。