変数と逐次実行システムの追加

 前回までで「引数に一つの数値や関数をとり数値を返す関数」の機能は一通りできたので、今回は「数値を保持する変数」と「変数への代入や関数・数値の評価を1行ずつ実行していく逐次実行システム」を追加してみましょう。ここまでできると、多少は「スクリプト」らしくなってきますね。

変数の管理

 今回の変数は、数値を保持するものとします。たとえば、

  a=3

 とすると変数aに3という数値が代入され、以降aという変数が出てきたらそれを3という数値として扱うわけです。変数は変数名と数値の値を持つので、以下のような変数名と値を保持する変数クラスvarを定義し、各変数をこのvarクラスのインスタンスとして扱うことにしましょう。参照fore/nextは、リストのためのものです。

  class var { // 変数クラス

      String name;
      int value;
      var fore,next;

      public var(String stArg,int iArg,var vrArg1,var vrArg2) {

          name=new String(stArg);
          value=iArg;
          fore=vrArg1;
          next=vrArg2;

      }

      public var(var vrArg) {

          name=new String(vrArg.getName());
          value=vrArg.getValue();
          fore=vrArg.getFore();
          next=vrArg.getNext();

      }

      public String getName() { return name; }
      public int getValue() { return value; }
      public var getFore() { return fore; }
      public var getNext() { return next; }

      public boolean setName(String stArg) {

          name=stArg;
          return true;

      }

      public boolean setValue(int iArg) {

          value=iArg;
          return true;

      }

      public boolean setFore(var vrArg) {

          fore=vrArg;
          return true;

      }

      public boolean setNext(var vrArg) {

          next=vrArg;
          return true;

      }

  }

 次に、この変数をまとめて管理するリストクラスvarListを定義します。このクラスはvarクラスのインスタンスをリストとして保持し、新たなvarインスタンスの追加や変数名からの値取得などの機能を持たせることにしましょう。まず、変数リスト用に最初の要素vrFirstと最後の要素arLastという二つのvar型変数を用意し、この変数を使ってリストを構成します。このリストに指定されたvarをリストに追加するadd()は、以下のようになりますね。

  private boolean add(var vrArg) {

      if (vrArg==null)
          return false;

      if (vrFirst==null) { // 変数リストに変数が一つもなければリスト作成

          vrFirst=new var(vrArg.getName(),vrArg.getValue(),null,null);
          vrLast=vrFirst;

      } else { // 変数リストの末尾に変数追加

          vrLast.setNext(new var(vrArg.getName(),vrArg.getValue(),vrLast,null));
          vrLast=vrLast.getNext();

      }

      return true;

  }

 次に、指定された変数名を持つ変数がリストの中にあるか調べるisDef()は、「リストの最初から変数を辿っていき途中で指定された変数名を見つければtrue、最後まで見つからなければfalseを返す」処理を行うようにします。

  public boolean isDef(String stArg) { // 指定された変数名が定義されているか

      var vrIndex=vrFirst;

      while (vrIndex!=null) {

          if (vrIndex.getName().equals(stArg))
              return true;

          vrIndex=vrIndex.getNext();

      }

      return false;

  }

 これと同じような処理で、指定された変数の値を返すget()も作っておきましょう。

  // 指定された変数の値を返す
  public int get(String stArg) throws IllegalArgumentException {

      var vrIndex=vrFirst;

      while (vrIndex!=null) {

          if (vrIndex.getName().equals(stArg))
              return vrIndex.getValue();

          vrIndex=vrIndex.getNext();

      }

      // 変数名が見つからなければ例外発生
      throw new IllegalArgumentException();

  }

 最後に、変数に値を代入するset()set()では、リストの中を調べ指定された変数名があればその変数に指定された値を代入、変数名がリストの中になければ指定された変数名と値で変数を作成しリストに追加する処理を行います。

  // 指定された変数に値を設定、未定義なら新規作成
  public boolean set(String stArg,int iArg) {

      if (stArg==null || stArg.length()==0)
          return false;

      var vrIndex=vrFirst;

      while (vrIndex!=null) { // 値を設定する変数を決定

          // リスト内変数
          if (vrIndex.getName().equals(stArg)) {

              vrIndex.setValue(iArg);
              return true;

          }

          vrIndex=vrIndex.getNext();

      }

      // リスト内に指定の変数名がなければ新規作成
      add(new var(stArg,iArg,null,null));

      return true;

  }

逐次実行と変数リストの作成

 以上で変数を扱う仕組みができましたので、次は実際に変数を定義し変数のリストを作成していく仕組みを考えてみます。前回までは1つの関数を入力してその値を取得していましたが、今回は変数の定義と変数の値が実際に適用される様子を確認するためにいくつかの関数や代入文を並べた「スクリプト」を入力し、それを逐次実行して行くようにしましょう。

 今回のスクリプトは「1行1文の評価可能な値、または値の代入文の並び」とします。評価可能な値とは、前回までで扱って来たような関数、数値、そして今回導入した定義済み変数で、いずれも評価によって数値を持つものです。また、代入とは既存の変数、あるいは未定義で有効な変数名を持つ変数(候補)に対する値の代入で、「変数名=値」という構文を持つものとします。変数名には()=以外の1文字以上の文字列を使用できるようにしましょう。なお、未定義の変数は変数として存在しない以上「値」を持たないので、代入する値(右辺)に未定義の変数名を指定することはできません。

スクリプトの例

・値文
  123
  inc(123)

・代入文
  a=123
  b=a
  c=inc(b)

 つまり、今回のスクリプトで有効な構文は

 値
 変数名=値

 という2種類の文のみです。そして、ある文が値であるためには数値か有効な値を持つ関数、あるいは定義済み変数である必要があります。これらの文を評価し実行するには、最初に受け取った文が構文として正しいか、チェックする必要がありますね。

 まず、値かどうかのチェックですが、これは数値・定義済み変数といったすぐに値として評価できる値であるか、関数であるか、確認する処理になります。数値であるかどうかは前回までのisNumをそのまま使えますし、定義済み変数であるかの判定も変数リストのisDef()を呼び出せば良いでしょう。今回は、すぐに数値化できる数字列や変数をまとめて数値として扱い、数字列の数値を取得するparseNum()を、数字列と変数の数値を取得するように変更します。

  private int parseNum(String stArg) { // 数値を評価し値を返す

      int num=0,position=1,len=stArg.length();

      if (stArg.length()==0)
          return 0;

      if (list.isDef(stArg)) // 定義済み変数ならその値を返す
          return list.get(stArg);

      for (int i=1;i<=len;i++)
          switch (stArg.charAt(len-i)) {

              case '0':
              case '1':
              case '2':
              case '3':
              case '4':
              case '5':
              case '6':
              case '7':
              case '8':
              case '9':

                  num+=(stArg.charAt(len-i)-'0')*position;
                  position*=10; // 位取り

                  break;

              default:

                  return 0; // 数値以外の文字があれば0

          }

      return num;

  }

 次に関数ですが、関数であるためには引数が値である必要がありました。そして、今回もその条件はまったく同じです。違いは値として数字列と関数のほか、定義済み変数も認識するくらい。といっても、関数の引数検査は実際には関数かどうかの判定時ではなく関数の評価時にまとめて行うので、関数かどうかの評価(isFunc())自体はまったく同じです。前回までの評価関数parse()では、引数が関数であれば再帰、数字であれば評価、としていましたので、今回は数値として定義済み変数も認識するようにして評価関数getValue()で以下のような関数評価を行いましょう。

  String stStr=getArg(stArg); // 関数の引数取得

  if (isFunc(stStr)) // 関数の引数が関数なら、再帰呼び出し
      return getValue(getFunc(stArg)+"("+getValue(stStr)+")");

  if (isNum(stStr) || list.isDef(stStr)) { // 関数の引数が数値なら評価

      String stFunc=getFunc(stArg);
      int arg=parseNum(stStr);

      if (stFunc.equals("inc"))
          return arg+1;

      if (stFunc.equals("sqr"))
          return arg*arg;

  }

 今回は、引数が「数値」であることの条件に数字列である(isNum()が真)ことのほか、定義済み変数である(list.isDef()が真)を追加してあります。

 以上までで「数字列」「定義済み変数」「関数」という「値として評価可能な要素」すべての評価ができるようになりました。これらをまとめて、文字列の値を評価するgetValue()を以下のように定義します。getValue()では、評価時にエラーになったら例外を投げるようにしました。

  private int getValue(String stArg) throws IllegalArgumentException {

      if (isNum(stArg)) // 数値なら、その値を返す
          return parseNum(stArg);

      if (list.isDef(stArg)) // 定義済み変数ならその値を返す
          return list.get(stArg);

      if (!isFunc(stArg)) // 数値でも関数でもなければエラー
          throw new IllegalArgumentException();

      String stStr=getArg(stArg); // 関数の引数取得

      if (stStr.length()==0)
          throw new IllegalArgumentException();

      if (isFunc(stStr)) // 関数の引数が関数なら、再帰呼び出し
          return getValue(getFunc(stArg)+"("+getValue(stStr)+")");

      if (isNum(stStr) || list.isDef(stStr)) { // 関数の引数が数値なら評価

          String stFunc=getFunc(stArg);
          int arg=parseNum(stStr);

          if (stFunc.equals("inc"))
              return arg+1;

          if (stFunc.equals("sqr"))
              return arg*arg;

      }

      throw new IllegalArgumentException();

  }

文の構文解析と実行

 値の形式検査と取得ができるようになったので、次は代入文の判定です。代入文は、変数名=値という構文で、言い換えれば「=で区切られた変数名と値2つの要素からなる文」ということになります。今回は、この判定のためにまず文字列を指定したcharで区切り結果をString配列で返すsplit()を定義することにします。

  private String[] split(String stArg,char cArg) {

      int i,n=1,index=0,len=stArg.length(),pos=0;

      for (i=0;i<len;i++) // cArgの数を数える
          if (stArg.charAt(i)==cArg)
              n++;

      String stReturn[]=new String[n];

      for (i=0;i<len;i++)
          if (stArg.charAt(i)==cArg) {

              stReturn[index++]=stArg.substring(pos,i);

              pos=i+1;

          }

      stReturn[n-1]=stArg.substring(pos,len);

      return stReturn;

  }

 このsplit()は、1文字の区切り文字で文字列を分割できるので、たとえば

  String str[]=split("a=2",'=');

 とするとString配列strは、str[0]="a"str[1]="2"となります。代入文であるための条件は、このようにして分割した文字列配列の要素数が2でかつ最初の要素が変数名、次が値である、ということになります。今回は、文字列が変数名になりうる文字列か判定するisVariable()と値になりうるか判定するisValue()という2つの関数を用意し、代入文であるか判定するisSubstitution()を以下のように定義しました。

  private boolean isSubstitution(String stArg) {

      String stElements[]=split(stArg,'=');

      if (stElements.length!=2)
          return false;

      return isVariable(stElements[0]) && isValue(stElements[1]);

  }

 なお、isValue()では未定義の変数名でも真を返すため実際には値がどうか正確に判定できませんが、実行段階で値でないものを代入するとエラーになるので実用上は問題ありません。

 これで値の評価や代入文の判定ができるようになったので、スクリプトを1行ずつ実行するrun()を作りましょう。run()では最初に変数リストlistを新規に作成し、変数をクリアします。次に、入力されたスクリプトstArgsplit()を使って改行で区切り、配列stLines[]に格納しましょう。これで、スクリプトの各行をstLines[0]などの形で参照できるようになります。

  list=new varList();
  String stLines[]=split(stArg,'\n');

 スクリプト各文の実行では最初にstLines[]から1行取り出し、isSubstitution()でその文が代入文か調べます。そして、代入文なら=で文字列を切り分けます。

  String stTmp[]=split(stLines[i],'=');

 これで、stTmp[0]には、代入文の=の前の部分、つまり値を代入する変数名が、stTmp[1]には代入される値の文字列が入りました。あとは、stTmp[1]getValue()で評価し、その値を変数リストで代入するだけです。

  try {
      value=getValue(stTmp[1]);
  } catch (IllegalArgumentException e) {
      bErr=true;
  }

  if (!bErr)
      list.set(stTmp[0],value);

 文が代入文でない場合、次は値であるかisValue()で調べます。そして、値であればその値をgetValue()で評価します。

  value=getValue(stLines[i]);

 どちらの判定でもひっかからない、つまり代入でも値でもない文はエラーです。以上が今回のスクリプト実行の本体で、まとめると以下のようになります。

  • 変数リストクリア
  • スクリプトを改行で区切り各行を配列stLines[]に格納
  • ループ:i=0;i<スクリプト行数;i++
  •  文stLines[i]が代入文か検査し、代入文であれば代入
  •  文stLines[i]が値か検査し、値であれば評価
  •  実行結果をテキストエリアtaViewに表示
  • ループ終了

 さらに、最後に変数リスト内のすべての変数も表示するようにしたので、スクリプトを実行するrun()は以下のようになります。

  private void run(String stArg) { // スクリプト実行

      list=new varList(); // 変数リスト初期化

      // スクリプトを行ごとに配列に格納
      String stLines[]=split(stArg,'\n');

      // 各行を1行毎に実行
      for (int i=0;i<stLines.length;i++) {

          boolean bErr=false;
          int value=0;

          taView.append(String.valueOf(i+1)+" - "+stLines[i]+":");

          if (isSubstitution(stLines[i])) { // 代入文

              String stTmp[]=split(stLines[i],'=');

              try {
                  value=getValue(stTmp[1]);
              } catch (IllegalArgumentException e) {
                  bErr=true;
              }

              if (!bErr) { // エラーがなければ代入を実行

                  list.set(stTmp[0],value);
                  taView.append(stTmp[0]+"="+String.valueOf(value));

              }

          } else if (isValue(stLines[i])) { // 値文

              try {
                  value=getValue(stLines[i]);
              } catch (IllegalArgumentException e) {
                  bErr=true;
              }

              if (!bErr)
                  taView.append(String.valueOf(value));

          } else
              bErr=true;

          if (bErr)
              taView.append("Error");

          taView.append("\n");

      }

      taView.append("Variable:\n");

      // 変数リストの最初の変数を取得
      var vrIndex=list.getFirst();

      // 変数リストをたどって全変数を表示
      while(vrIndex!=null) {

          taView.append(vrIndex.getName()+":"+vrIndex.getValue()+"\n");
          vrIndex=vrIndex.getNext();

      }

  }

プログラム

 実行したらScript欄にスクリプトを改行を入れながら1行ずつ入力し、Runボタンをクリックして実行してみてください。評価や代入の様子、また変数の一覧が順次Output欄に表示されます。使用可能な構文は、数値列と関数、定義済み変数といった値文と変数に対する値の代入文で、関数は前回同様inc()とsqr()が使用できます。また、ClearボタンでOutput欄をクリアできます。

実行例

Script

  inc(2)
  a=inc(1)
  b=inc(inc(inc(a)))
  c=sqr(inc(b))

Output

  1 - inc(2):3
  2 - a=inc(1):a=2
  3 - b=inc(inc(inc(a))):b=5
  4 - c=sqr(inc(b)):c=36
  Variable:
  a:2
  b:5
  c:36

プログラムソース表示

数学アルゴリズム演習ノート プログラミング資料庫