Skip to content

teddokano/CaFE2

Repository files navigation

/*
 *                   ##
 *                  ##             cafe (CaFE) : Calcurator, Function Expandable
 *                  ##             
 *   ###   ####   ######  ###       an RPN calculator for command line interface
 * ##     ##  ##    ##   ## ##
 * ##     ##  ##    ##   ###             (c) Tsukimidai Communications Syndicate
 *   ###   ### ##   ##    ####               1991 - 2006
 */

/**
***   version 2.0 release 0.5
**/

「cafe」 ユーザ・ガイド

cafe version 2.0 release 0.5 ユーザ・ガイド version 0.5

May-2005 start Sep-2006 current

(c) 月見台通信シンジケート All rights reserved 2005, 2006


0. cafeについて(特徴)

###0.1 cafe

cafeはUNIXターミナル環境用に作られた計算機プログラムです.
「逆ポーランド記法(RPN)」と呼ばれる,少し変わった使い方をします.
HPの電卓のような入力の方法です.その方法については次の章で紹介します.

単純な計算の他に,少し便利な機能を追加してあります.

「cafe」の名は,旧バージョンでは「CaFE」としていましたが,UNIXの慣例に倣いすべて小文字での記述に変えました.
「CaFE」は「Calculator, Function Expandable」(関数拡張可能な計算機)の略です.

###0.2 なぜこれを作ったか?

cafeは元々,C言語のバイブル「リッチー&カーニハンの『プログラミング言語 C』」に載っていたRPN計算機のサンプルプログラムを出発点として,それに改良を加えたものです.1991年に(本を見ながら)最初のものを書き,その後自分用として少しづつ手を加えながら使ってきました.
このバージョン「バージョン2」は,その元になったプログラム(バージョン1)に大きな不足を感じるようになったため,2005年にすべてを書き直し,現在にいたるまで拡張を続けているものです.

最初のバージョンを使っていた2000年までの私のメイン・コンピュータ環境は,MacOS9以前のMacOSでした.このためUNIXを日常的に使うことは少なく,バージョン1の限られた機能でも十分でそれほど不満に感じることはありませんでした.

しかしUNIXをベースとした新しいOS「Mac OS X」が登場し,それ移行した後には,ターミナルを開いて作業することも多くなってきました.そしてUNIXシェルの機能に慣れるに従って,CaFEの従来のバージョンでは不便を感じるようになり,我慢できなくなってきたのです.
たとえば,CaFEはコマンドライン(キー入力)によっチて操作するプログラムであるにも関わらず,上下矢印キーによるヒストリ・ブラウズ機能や,タブキーによる入力補完機能などの対話的なプログラムに必要な仕組みが実装されていませんでした.
さらにそのソースコード自体も,切った貼ったを繰り返した果てに非常に読みづらく,さらには古いK&Rスタイルも混じったもので,新たに手を加えるのが億劫になるような代物と化していました.

この「バージョン2」は上記のような様々な点を改善するために作られました.このほかこの新しいバージョンではその他の使い勝手の向上のためにいくつもの機能が追加されました.

###0.3 動作の環境

cafeはコマンドラインで使用するアプリケーションです.Mac OS Xではターミナルを開いてその中で動作します.Linixでも動作します.

###0.4 インストール

cafeはgithubでホスティングされています.これを適当なディレクトリでcloneすると,「CaFE2」という名のフォルダが作られ,その中に必要なファイルが入っています.

git clone https://github.com/teddokano/CaFE2.git

この後,CaFE2フォルダに移って「./configure; make」を実行します.これでcafeがビルドされ,ライブラリファイルに必要な変更が加えられます.
このあと「sudo make install」のコマンドを実行し,実行ファイルとライブラリファイルをインストールすると準備完了です.

メモ : 
「Makefile」をデフォルトのまま使用し,「make install」した場合,cafeはユーザ・ディレクトリにインストールされます.この時,cafeの実行ファイルは「‾/bin/」にコピーされるので,このディレクトリにパスを設定しておかなければなりません.

メモ : 
一方Makefile中のINSTALL_TARGET変数に「PUBLIC」を設定すると(デフォルトでは「PRIVATE」になっている),インストールは「/usr/local/bin/」に行われます.またライブラリファイルも「/etc/」に置かれます.PUBLICインストールを設定した場合には,make installを実行する際に管理者権限が必要になります.  

メモ : 
「make」はUNIX互換環境で動作します.DJGPP等をインストールしたDOS等の非UNIX環境では動作しません.これはUNIXのコマンドをMakefile内部で使用しているためです.

##1. 簡単な使い方

###1.1 起動と終了

起動します.起動の仕方は先に書いた通り「cafe」と打つだけです.
cafeを終了するには「quit」あるいは「qq」とリターン・キーを打ちます.

###1.2 計算

簡単な計算の例を示します.

起動後の画面にはプロンプトが表示されています.cafeで計算を行う際には,値や演算子(たとえば「+」や「-」など)を入力します.

入力はRPN記法で行います.RPN記法は一見奇妙な書き方に見えますが,非常に理にかなった入力方法です.

たとえば
「3 + 5」を計算するには,
「3 5 +」のように文字を並べ,リターン・キーを打ちます.そうするとその結果「8」が次の行のプロンプトに表示されます.

CaFE (empty) % 3 5 +
CaFE (    8) % 

この計算を例にどのように実行されるのかを見てみます.

###1.3 計算はどのように行われるのか?(逆ポーランド記法(RPN)について)

リターン・キーが押された後,「3 5 +」の入力文字が左から順に解釈されていきます.
最初に(最も左に)数値の「3」があります.この値がスタック・メモリに入れられます.

スタック・メモリとは...RPN方式で計算を行う場合に使われるメモリで,計算する値を出し入れする場所です.この出し入れには決まりがあり,メモリの容量の許す限りデータをどんどん入れていくことができます.スタック・メモリからデータを取り出すと,必ず最後のデータが出てくるようになっています.

一般にスタック・メモリに値を入れる作業を「プッシュ」,取り出す作業を「ポップ」と言います.
以降このユーザガイドでは,その都度「スタック・メモリ」と書くのは面倒なので,単に「スタック」と言うことにします.

さて,既に「3」がスタックにプッシュされました.次は「5」です.これも「3」と同様にスタックにプシュされます.これでこの二つの値がメモリに入りました.この次の文字は数字ではなく足し算を示す文字「+」です.

cafeはこの「+」の文字を「スタックから2個の数値をポップし,それ同士を足し合わせ,その結果をスタックにプッシュし直す」と言う意味に使います.このため「+」の文字(演算子と言います)によって2つの値,まず最後にプッシュされた「5」がポップされ次に「3」がポップされ,この両値から計算された新しい値がスタックにプッシュされます.

この結果がプロンプトに表示されます.このプロンプトに表示されている値は「最後にスタックにプッシュされたもの」です.

メモ : cafeではスタックにプッシュできるデータの数に制限はありません.cafeを実行するコンピュータに搭載されている記憶装置による制限があるのみです.

###1.4 スタックのクリア

スタックをクリアするコマンドがあります「cl」とリターン・キーを押すと,スタックの中身がすべて消えます.クリアするとプロンプトにはスタックの中身が空であることを示す「empty」が表示されます.

###1.5 入力の方法

「3 5 +」は1行で書かなくても構いません.まず「3」を打ってリターン・キーを押し,「5」を打ってリターン・キーを押し,最後に「+」とリターン・キーでも同じ結果が得られます.
それぞれのリターン・キーを打った後,次のプロンプトには,それぞれ入力した値が表示されます.

CaFE (empty) % 3
CaFE (    3) % 5
CaFE (    5) % +
CaFE (    8) % 

このように各数値や演算子はスペースやリターン・キーで区切りながら打つということになります.
それぞれの場合,cafeの動作は違いますが,計算の結果に違いはありません.

###1.6 足し算と掛け算,計算の順序(優先順位)

さて次は少し複雑な計算を例に挙げます.通常の計算では掛け算,割り算を先に行うルール(優先順位)があるため,例えば「4と3を足したものに5を掛ける」ような場合にはカッコを付けて「(4 + 3) * 5」のように書かなければなりません.

このカッコをつけるというのは意外に大変なもので,先に計算しておかなければならない部分が増えるにつれて入力の手間がかかり,さらに開いたカッコが的確に閉じているか,その対応を確認する面倒がつきまといます.

RPNではこのカッコが必要ありません.カッコがない代わりにその順番で優先順位を指定します.先ほどの例は「4 3 + 5 *」のように入力するだけとなります.この記法は先に日本語で書いた「4と3を足したものに5を掛ける」をそのまま表現したものです.
このようにRPNを理解すると,より簡単でしょう.

CaFE (empty) % 4 3 + 5 *
CaFE (   35) % 

「4 3 + 5 」では4と3がたされた結果「7」がスタックにプッシュされているため,この値に続いて「5」をプッシュし,「」で7と5を掛けた結果が得られます.

###1.7 簡単な応用

たくさんの数値をすべて足し合わせるような計算をする場合,たとえば通常の表記では
「1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10」のように書かれる計算では,まずすべての数値をスタックにプッシュしておき,後から計算を行わせることができます.つまり
「1 2 3 4 5 6 7 8 9 10 + + + + + + + + +」(1から10までの10個の数値と
9個の「+」)のように書くことができます.

CaFE (empty) % 1 2 3 4 5 6 7 8 9 10 + + + + + + + + +
CaFE (   55) % 

この場合,最初の「+」による計算はスタックからポップされた「10」と「9」に対して行われ,「19」がプッシュされます.次の「+」では「19」と「8」がポップされ「27」をプシュする...これが繰り返され,最終的に「55」の値が得られます.

cafeでは特にスタック全体の総和を計算するために「aa」というコマンドが用意されています.「aa」は「Add All」の略です.これを使うと「+」を続けて書く手間がなくて済みます.
「1 2 3 4 aa」のように使います.

CaFE (empty) % 1 2 3 4 aa
CaFE (   10) % 

###1.8 便利な機能

####1.8.1 ヒストリ機能

CaFEにはヒストリ機能が用意されています.前に入力したものと似たようなものを入力するような場合に便利です.矢印キーの上向きのキーで前に入力したものを遡って表示します.
上向き矢印キーで目的の入力表示を行き過ぎた場合,下向きのキーで戻ってくることができます.

####1.8.2 アン・ドゥ(undo)機能

注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意
ユーザスタック機能の追加後,undoコマンドのサポートを暫定的に停止しています.20060602
注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意

計算結果が期待したものと違い,やり直すような場合,「undo」コマンドが使えます.
undoはスタックをその一つ前の状態に戻してくれます.

####1.8.3 補完,候補表示機能

コマンド入力の補完機能があります.たとえば上記の「undo」を入力する時に,「un」までを入力し,タブ・キーを押すと「undo」のコマンドが現れます.これが補完機能です.
あるいは「q」まで入力してからタブ・キーを押すと「qq」と「quit」が表示されます.これはqで始まるコマンドの候補のリストです.

何も入力せずにタブ・キーを押した場合にはすべてのコマンド候補が現れます.
ダブルクォーテーション「"」を単語の先頭とした場合にはディレクトリやファイルの名前が候補として現れます.CaFEでの「"」は文字列を用いる場合に使われる記号となっており,ファイルを指定する場合は,必ず文字列としてその名前が与えられるため,このようなファイル名補完機能が用意されています.

さらにこの後説明する「関数」についても,名前の補完が行われます.もし既に定義された関数があるなら,コマンドと同様に候補の中に関数の名前が現れます.

###1.9 文字の扱い(区切り文字)

cafeでは特定の文字を特別扱いします.たとえば先の例で示した「+」コマンドは,これだけで単独の区切り文字としての意味も持っています.通常は数値やコマンドの間にはスペースが「各コマンドや数値の区切りを示すもの」として使われますが,このスペースの冗長性を省くためにこのような特別な文字が用意されています.

つまり「4 3 +」は「4 3+」と書くことができるし,「1 2 3 4 5 6 7 8 9 10 + + + + + + + + +」は「1 2 3 4 5 6 7 8 9 10+++++++++」のように記号を連続してまとめて書くことができます.
この他,下に示した文字はすべて区切り文字としての機能を持っています.

区切り文字一覧
  +
  -
  *
  /
  :
  ;
  <
  >
  (
  )
  [
  ]

次のような入力

 11 22*alpha/>beta[<gamma]

があった場合には,cafeはそれを

11 22 * alpha / > beta [ < gamma ]

のように解釈します (この例のalpha,beta,gammaは単語の例です).

このうち「+」,「-」だけはさらに特別な扱いをうけます.この二つは数値の前につけられた場合に符号を示す文字となるため,スペースに続いて用いられた場合には区切りとはならずにそれに続く文字と一緒の部分として扱われます.

##2. 拡張機能の使い方(計算機としての拡張機能)

###2.1 計算をまとめる : 関数

同じ計算を何度も行うようなときには関数を使うと便利です.
たとえば何らかの数値を入力した後に毎回1024を掛ける作業を行わなければならない場合,その都度「1024 *」を入力するのは大変です.この「1024 *」を他の記号や名前で代替できるようにする仕掛けが関数です.
関数を使うにはまず関数を作らなければなりません.関数を作ることを「関数を定義する」といいます.関数の定義は以下の例のように行います.

: K 1024 * ;

この例では「k」という(名前の)関数を定義しています.最初の「:」が関数の定義を行うことを示しています.これに続く最初の部分が関数の名前となります.その次の部分から「;」までが関数の中身(ボディ)となります.
「;」は関数定義の終わりを示す記号です.いったん関数を定義すると,それ以降の計算を行うときに使うことができます.

例を示しましょう.上記の関数を定義しておいて,

27 K

を計算すると「27648」の結果が得られます.

CaFE (empty) % : K 1024 * ;
CaFE (empty) % 27 K
CaFE (27648) % 

メモ : 現在のバージョンでは,コマンドライン上で複数行にわたる関数の定義を行うことはできません.
後述するファイル内での記述ではこれが可能です.
コマンドライン上で関数の定義を行う際に最後の「;」を忘れた場合には,自動的に「;」が付加されます.

メモ : 関数には必ず名前が必要です.名前無しの関数を定義しようとするとエラーとなります.
一方名前だけで中身(ボディ)の無い関数は定義可能です.

メモ : 「:」,「;」は共に区切り文字であるため,これらの文字の前後にスペースをつける必要はありません.
「: K 1024 * ;」は「:K 1024 *;」としても同じように処理が行われます.

関数のボディの長さに制限はありません.自由な長さの計算処理を定義できます.

関数名を考える際には,区切り文字に気をつけなくてはなりません.区切り文字は一連の文字の列をそこで分断してしまうので,これを関数名の一部として使うことはできません.つまり「:test*function 1024 *;」のような宣言をしようとすると,これを読み込んだ際に「:test * function 1024 *;」のように分解してしまいます.またたとえこのような関数名が定義できたとしても,この関数を呼び出すことはできません.

###2.2 条件実行

####2.2.1 条件実行「if」

何らかの条件によって,これに続く計算を「する」か「しない」かをコントロールしたいときがあります.これを「条件実行」と呼びます.cafeでは「if」コマンドを用いてこの機能を使うことができます.

「if」コマンドは少し複雑です.次のようなルール(文法)で書いてやらなくてはなりません.

  if 条件 実行するコマンドまたは関数

ifの後ろにどのようなとき(条件)に,何を実行する(コマンド)か,を書くことになります.条件の指定方法は4種類用意されており,「positive」「negative」「zero」「true」が指定できます.それぞれスタックにプッシュの値が「正」「負」「ゼロ」「ゼロでない」場合にそれに続くコマンドが実行されます.

メモ : 「if」に続く条件の指定は各条件の最初の一文字だけあれば問題ありません.つまり「positive」「negative」「zero」「true」の各指定はそれぞれ「p」「n」「z」「t」と書いても同じように実行されます.

次は「スタック上の値がゼロでなければ割り算を行う」例です.「ゼロで割る」計算は行えません.この「if」の処理が無ければエラーとなってしまいます.このように「if」を用いることでエラーを回避できます.

if true /

ifで指定できるコマンドは単一のコマンドまたは関数に限られます.次の例は絶対値を求める計算を書いた例ですが,残念ながら期待した通りには動きません.ここでは「もしスタック上の値が負の値であれば『-1を掛ける』」というような計算を行いたいのですが...

if negative -1 *

この例では「-1」の部分だけがifでコントロールされる部分と解釈されてしまいます.もしスタックの値が「負」であれば「-1」がプッシュされます.そうでなければ何もプッシュされません.このあとどちらの場合もスタックに対する掛け算が行われることになります.
もちろんこの「-1 *」を,あらかじめ一つの関数として定義しておいて,ifの制御にそれを用いることも可能ですが,記述が煩雑になり,書くにも読むにも良いことはありません.

これを回避するために複数のコマンドや数値を一つにまとめる方法が用意されました.「''」のような一対のシングルクォートを使います.この記号に囲まれた範囲は単一コマンド(厳密に言うと一つの語,cafeではこの一つの語として扱われる語を「トークン(token)」と呼びます)として扱われます.

メモ : シングルクォートの扱いはいささか面倒な場合があります.このような場合には「当座関数」を使うことができます.当座関数については後の章で説明します.

シングルクォートを用いて先ほどの絶対値を求める計算は以下のように書くことができます.

if negative '-1 *'

スタックの値が負であった場合には「'-1 *'」が計算されます.それ以外の場合には何も行われません.
さらにこれを関数として定義しておくと便利でしょう.次のように書きます(abs は absoluteの略です).

: abs if negative '-1 *' ;

下はその実行例です.

CaFE (empty) % : abs if negative '-1 *' ;
CaFE (empty) % 3 abs
CaFE (    3) % -5 abs
CaFE (    5) % 

####2.2.2 条件実行「ifelse」

条件実行には「if」以外に,もう一つ「ifelse」が用意されています.以下の文法を用います.

ifelse 条件 コマンド1 コマンド2

スタック上の値が条件に指定したもの通りなら「コマンド1」を実行し,そうでないなら「コマンド2」を実行します.
スタック上の値が正か負かで表示を変化させる例です.

ifelse positive 'print POSITIVE' 'print NEGATIVE'

スタックの値が正なら「POSITIVE」,負なら「NEGATIVE」を表示します.
「print」はそのコマンドに続く語を文字列として扱い,それを表示します.

CaFE (    7) % ifelse positive 'print POSITIVE' 'print NEGATIVE'
POSITIVE
CaFE (    7) % 
CaFE (    7) % -7
CaFE (   -7) % ifelse positive 'print POSITIVE' 'print NEGATIVE'
NEGATIVE
CaFE (   -7) % 

メモ : if,ifelseはスタック上の値を見にいきますが,値はスタックからポップされずに 残ります.

メモ : この例はあまりいい例ではないですね.値が0のときにも「NEGATIVE」が表示されてしまいます.この問題は以下のようにすることで解決できます.

ifelse zero 'print ZERO' 'ifelse positive ¥'print POSITIVE¥' ¥'print NEGATIVE¥''

メモ : printコマンドはRPN記法に則っていません(ifやifelseもそうです).これについては[3.4]節「例外的な文法を用いるコマンド」で詳しく解説します.

###2.3 繰り返し実行(ループ)

何度も同じ計算を繰り返す場合に,その都度その処理を入力するのは大変です.このためコマンドを繰り返し実行する仕組みが用意されています.

「times」コマンドを使います.timesコマンドは以下のように書きます.

times コマンド

スタック上の値をポップしその値で示された回数だけ指定されたコマンドを実行します.コマンドで指定できるのは一つのコマンドだけとなっています(ifと同様です).
次の例ではtimesを用いて「Hello!」を10回表示しています.

CaFE (empty) % 10 times 'print A'
AAAAAAAAAA
CaFE (empty) % 

注意 : cafeには「times」の他に「time」コマンドが用意されています.「time」は現在時間を計算するための値を得るコマンドになっています.

次はtimesを用いた計算の例です.

1 10 times '2 *'

この例では次のような動作となります.

  1. まずスタックに「1」を,次に「10」をプッシュ.
  2. timesコマンドがスタックをポップします.ポップされた値は10だったので「2 *」を10回実行する.
CaFE (empty) % 1 10 times '2 *'
CaFE ( 1024) % 

2.4 変数

データを保存する場所として,スタックの他に「変数」が用意されています.
スタック以外の一時的にデータを置く場所として変数を使う事が出来ます.

変数の読み書きには「>」と「<」のコマンドと変数の名前を組み合わせて使います.
下の例では「variable_sample」という名の変数に数値l1234を書き込み,その後読み出しています.

CaFE (empty) % 1234
CaFE ( 1234) % > variable_sample  
CaFE (empty) % < variable_sample
CaFE ( 1234) % 

この名前に使用できる文字,文字数には特に制限はありません.

メモ : この記号を矢印とみなして,左側にスタック,右側に変数があるものと考えるとより直感的です.

メモ : この例で示した変数は関数からのアクセスが出来ません!
関数から自由にアクセスできる変数は「グローバル変数」と呼ばれ,この先で詳しい説明が行われます.

メモ : グローバル変数以外にももうひとつ,関数を呼び出した側の変数をアクセスする手段が用意されています.
これはこの先で「通常変数のアクセス範囲変更」の節で解説します.

メモ : 「>」,「<」は区切り文字です.
なので,変数alphaの内容をbetaにコピーする場合,「beta」のように書いても問題はありません.

##3. 詳しい使い方

###3.1 データ型

####3.1.1 スタックに置くことのできるデータ.

cafeで扱えるデータは数値,文字列と後に解説するユーザスタックの3種類です.
スタックにおけるデータはすべて変数に置くことができます.

#####3.1.1.1 数値

cafeが扱う数値には整数型,浮動小数点型の2種類があります.
これらの型の選択は入力や計算の結果によって自動的に選択されるようになっています.

数値の指定(入力するとき)は単純に数字を並べたもの,正/負の符号付き,小数点を含んだ表現や「e」を用いる浮動小数表現,さらに「0x」をプリフィクスとして用いる16進数表現を使うことができます.

下はその例です.

1234
-567
+89
3.14
5.6e8
234e-9
0xABCD

取り扱うことのできる最大,最小の整数値は実行環境に依存します.cafeでは特にこれを規定していません.

型の話が出たので一つの関数の例をあげておきます.
下の例は「‾」という名前をつけたビット反転を行う関数です.

: ‾ 1 + -1 * ;

この関数を実際に実行してみると,以下のような結果となります.「hex」は結果表示モードを16進数に変更するコマンドです(10進数表示モードへの変更は「dec」コマンドを使います).
この実行環境では整数の表現に2の補数が使われているため,-1をかけることでビットの反転が行われているのがわかります.また-1と表示されている整数を16進表示に切り替えたことで,このcafeを実行している環境では整数が32ビットで扱われていることも確認できます.

CaFE (empty) % : ‾ 1 + -1 * ;
CaFE (empty) % 0
CaFE (    0) % ‾
CaFE (   -1) % hex
CaFE (0xFFFFFFFF) % 	
CaFE (0xFFFFFFFF) % 0xAAAA5555
CaFE (0xAAAA5555) % ‾
CaFE (0x5555AAAA) % 

注意 : 非常に大きな/小さな整数を入力する場合には注意が必要です.
整数はcafe内部では整数は,常に符号付きの整数として扱われます.もしその表現範囲を超える数値が入力される場合,warningを表示し,自動的に浮動小数型に変換してスタックにプッシュされます.
16進数で入力した場合には入力の時点ではその値は符号なしの整数として扱われます.

CaFE (empty) % 2147483647
CaFE (2147483647) % 2147483648
warning : converted to float
CaFE (2.14748e+09) % 
CaFE (2.14748e+09) % 0x80000000
CaFE (-2147483648) % 

メモ : 整数入力の記述はC言語のそれに倣います.サフィックスなしの整数は10進,0xのサフィックス,0で始まる数値はそれぞれ16進,8進のデータとして扱われます.

#####3.1.1.2 文字列

スタックには数だけではなく,文字列もプッシュできます.いくつかのコマンドではスタックに文字列を置いてから実行するようになっています.

たとえばファイル操作のコマンドを用いる際には,そのファイル名を指定しなければなりません.コマンドを実行する際にはそのファイル名をスタックからポップします.

文字列データの長さには制限はありません(コンピュータに搭載されるのメモリサイズによる制限はあります).
文字列をスタックにプッシュする場合には,文字列を「" "」ダブル・クォートで囲みます.

プッシュされる文字列はこのダブル・クォートを除いた文字列データがプッシュされます.プロンプト表示ではこの文字列は「" "」付きで表示されますが,これは便宜的に「文字列データ」であることを示すために付けられているもので,実際のデータには含まれません.

これは「.(ピリオド)」コマンドで確認できます.このコマンドは最後にスタックにプッシュされたデータを次の行に表示するものです.

CaFE (empty) % "sample string"
CaFE ("sample string") % .
sample string
CaFE ("sample string") % 

もしダブル・クォートを文字列に含めたいなら,バックスラッシュをダブル・クォートの前に付けて文字列の終わりでないことを示します.

CaFE ("sample string") % "¥"quote sample¥""
CaFE (""quote sample"") % .
"quote sample"
CaFE (""quote sample"") % 

文字列としてデータをポップしたとき,元のデータが数値であったなら,そのデータは文字列に変換されます.たとえば数値333がスタックにあり,これを「save」コマンドで使った場合,"333"がファイルの名前として使われることになります.

メモ : 文字列の中ではC言語のようなエスケープシーケンスを使うことができます.サポートされるエスケープシーケンスは以下の通りです.

	¥n    : 改行
	¥v    : 改行
	¥f    : 改行
	¥r    : 復帰
	¥t    : タブ
	¥¥    : バックスラッシュ
	¥'    : シングル・クォート
	¥"    : ダブル・クォート
	¥xHHH : 16進数

#####3.1.1.3 ユーザスタック

スタックには新たなスタック(ユーザスタック)を置くことも可能です.
ユーザスタックについては先の章で取り上げます.

####3.1.2 数値の扱われ方

先に説明したとおり,数値は内部的に整数型と浮動小数点型で扱われます.
特に変換の必要のない場合,入力された値は整数として扱われます.
整数型と浮動小数点型で計算をした場合や,整数同士の除算ではあるけれど割り切れない場合,さらに数学関数を用いたときには計算結果は浮動小数点型となります.また「float」コマンドで整数型データを浮動小数点型に明示的に変換することもできます.

除余(モジュロ)計算の結果は常に整数型となります.「trunc」コマンドは浮動小数点型データを整数型に変換することができます(正では小数点以下を切り下げ,負の数では切り上げます).

整数型,浮動小数点型で扱える正の最大値ならびに負の最大値,浮動小数点型の精度はcafeを実行する環境に依存します.

###3.2 スタックの操作

スタックは上記のような型のデータを格納します.
cafeのスタックには格納するデータの数に制限はありません(コンピュータに搭載されるメモリサイズによる制限はあります.しかしスタック操作の一部は後述する「インデックス」を用いるためそのコンピュータの扱える整数型の最大値までとなります.).

スタックは「cl」コマンドによってクリアできます.clはスタック上のデータをすべて消去します.

スタックへのデータの格納状況は「stack」コマンドで確認できます.各データには「インデックス(スタック・ポインタ)」と呼ばれる数が何番目に格納されたデータであるかを示します.インデックスは最初にプシュされたデータが0となり,それ以降1,2,3と続きます.
stackコマンドで表示される左端の「sp」はスタックポインタを意味しています.
それに続くカギカッコ内がスタックポインタの値です.右端にはスタック内のデータが表示されます.
スタックポインタとデータの値の間に丸カッコで示されているものは,データの型です.「i」,「f」,「s」はそれぞれ,整数型,浮動小数点型,文字列型を示しています.

「depth」コマンドはスタックにいくつのデータがあるかをチェックし,その値をスタックにプッシュします.

CaFE (empty) % 1 2.3 "test" -7   
CaFE (   -7) % stack
sp[  0] (i)    1
sp[  1] (f)    2.3
sp[  2] (s)    "test"
sp[  3] (i)    -7
CaFE (   -7) % depth
CaFE (    4) % stack
sp[  0] (i)    1
sp[  1] (f)    2.3
sp[  2] (s)    "test"
sp[  3] (i)    -7
sp[  4] (i)    4
CaFE (    4) % 

「depth」と「times」コマンドを用いると,スタック内のデータの総和を簡単に求めることができます.下の例では5個のデータがあることをdepthで確認し,「その値 - 1」回の足し算を繰り返す例です.

CaFE (empty) % 1 2 3 4 5    
CaFE (    5) % depth 1 - times +
CaFE (   15) % 

最後にプッシュされたデータだけをクリアしたり,再度同じデータをプッシュしたいときにはそれぞれ「pop」「push」を使います.「push」の代わりに「dup」を使うこともできます.「push」と「dup」は全く同じことをします.

CaFE (empty) % 1 2 "test" 4
CaFE (    4) % stack
sp[  0] (i)    1
sp[  1] (i)    2
sp[  2] (s)    "test"
sp[  3] (i)    4
CaFE (    4) % pop
CaFE ("test") % stack
sp[  0] (i)    1
sp[  1] (i)    2
sp[  2] (s)    "test"
CaFE ("test") % push
CaFE ("test") % stack
sp[  0] (i)    1
sp[  1] (i)    2
sp[  2] (s)    "test"
sp[  3] (s)    "test"
CaFE ("test") % 

スタックの最後の2つのデータを入れ替える場合には「swap」を使います.

CaFE (empty) % .1 .2 .3 "last"
CaFE ("last") % cl   
CaFE (empty) % .1 .2 .3 "last" stack
sp[  0] (f)    0.1
sp[  1] (f)    0.2
sp[  2] (f)    0.3
sp[  3] (s)    "last"
CaFE ("last") % swap    
CaFE (0.3) % stack
sp[  0] (f)    0.1
sp[  1] (f)    0.2
sp[  2] (s)    "last"
sp[  3] (f)    0.3
CaFE (0.3) % 

さらにスタックの中身をローテートさせるには「rot」を用います.最後にプッシュされたデータは,スタックの先頭へ持っていかれます.

CaFE (empty) % 0 1 2 3 "last" stack
sp[  0] (i)    0
sp[  1] (i)    1
sp[  2] (i)    2
sp[  3] (i)    3
sp[  4] (s)    "last"
CaFE ("last") % rot 
CaFE (    3) % stack
sp[  0] (s)    "last"
sp[  1] (i)    0
sp[  2] (i)    1
sp[  3] (i)    2
sp[  4] (i)    3
CaFE (    3) % 

「depth」と「times」,さらに「rot」コマンドを用いると,スタック内のデータすべてに対する個々の操作が可能です.下はその例です.

CaFE (empty) % 1 2 3 4 5
CaFE (    5) % depth times '2 * rot'
CaFE (   10) % stack
sp[  0] (i)    2
sp[  1] (i)    4
sp[  2] (i)    6
sp[  3] (i)    8
sp[  4] (i)    10
CaFE (   10) % 

スタック操作にはこのほか,「スタック上の最後のN個をdupする」コマンド「mdup」と,スタックのデータを逆順にするコマンド「reverse」が用意されています.「mdup」と「npush」は同じことを行うコマンドです.

CaFE (empty) % 1 2 3 4 5
CaFE (    5) % 3 mdup stack
sp[  0] (i)    1
sp[  1] (i)    2
sp[  2] (i)    3
sp[  3] (i)    4
sp[  4] (i)    5
sp[  5] (i)    3
sp[  6] (i)    4
sp[  7] (i)    5
CaFE (    5) % 

CaFE (empty) % 1 2 3 4 5 stack
sp[  0] (i)    1
sp[  1] (i)    2
sp[  2] (i)    3
sp[  3] (i)    4
sp[  4] (i)    5
CaFE (    5) % reverse stack
sp[  0] (i)    5
sp[  1] (i)    4
sp[  2] (i)    3
sp[  3] (i)    2
sp[  4] (i)    1
CaFE (    1) % 

###3.3 ファイル,ファイルの操作

####3.3.1 ファイルの読み込み

cafeはファイルを読み込むことができます.
「use」コマンドは,指定されたファイルを読み込み,その内容を実行します.
下の例は"simple_sample.cafe"という名のファイルを読み込んだ例です.ファイルの中身は「1 1 + .」となっていたため,これを実行しています.

CaFE (empty) % "simple_sample.cafe" use
2
CaFE (    2) % 

このような読み込みを行うファイルで,関数の定義をしておくと便利です.また「use」コマンドを起動の度に実行しなくても,ファイルの名前を「.cafe」(ドットで始まるUNIXでの不可視ファイル)として自分のユーザディレクトリに置いておくと起動時に自動的に読み込んでくれます.

通常のシェルで使われるグロブをファイル名指定に使うことができます.
ワイルドカードを使うことにより,一回のコマンド実行で,複数のファイルを読み込むことも可能です.以下の例では「testfile*.cafe」で示せるすべてのファイルを読み込む例です.この場合複数のファイルが読み込まれますが,その順番は保証されません.

CaFE (empty) % "testfile*.cafe" use

次のようにすることで「testfile3.cafe」「testfile4.cafe」「testfile5.cafe」「testfile6.cafe」の4個のファイルだけを読み込むように指定することも可能です.

CaFE (empty) % "testfile[3-6].cafe" use

これらワイルドカードなどの解釈は,通常シェルが行う解釈をそのまま使う事ができるようになっています.

####3.3.2 ファイルの書き出し

読み込みとは逆にファイルへの書き出しも行えます.
書き出しには2種類のコマンドがあり,スタックのデータだけを書き出す「save」と関数ならびにヒストリを保存する「put」が用意されています.

注意 : saveコマンドによってファイルに保存されるデータはテキストに変換されて保存されます.このためデータの精度を保持することはできません.ファイルにテキストとして保存するためのデータの変換は指定された変換に従います(テキスト表示変換の項を参照).

注意 : saveコマンドは,いまのところユーザスタックには対応していません.

####3.3.3 特別なファイル

#####3.3.3.1 「.cafe」ファイル

cafeには特別な扱いを受けるファイルがあります.
「.cafe」と名づけられたファイルが,ユーザのホームディレクトリにある場合,cafeは起動後に自動的にこのファイルを読み込みます.
自分のよく使う設定や関数,定数などをここに書いておくことができます.

#####3.3.3.2 「.cafe.auto_preference」ファイル

「.cafe」の他にもう一つ特別なファイルがあります.これもユーザディレクトリに置かれるファイルで「.cafe.auto_preference」という名のファイルです.cafeは起動時に二つのファイルを読み込みます.まず最初に「.cafe」を次に「.cafe.auto_preference」を読み込みます.

「.cafe.auto_preference」はcafe終了の都度,更新されます.このファイルにはcafe実行中にユーザが定義した関数と,実行したヒストリが記録されます(「".cafe.auto_preference" put」が実行されたのと同等です).これにより,次回cafeを起動した際,前回までの状態を再現することができるようになっています.

前回終了時までのヒストリ履歴はこのファイルの中に「history」コマンドとして記録されます.このコマンドが実行されることでcafe内部にヒストリ・エントリを作り,ユーザの操作に備えることになります.
このコマンドはcafe自身が正常終了時にファイルに書き出すもので,特にユーザが意識する必要のないものです.「history」コマンドはターミナルからも使えるコマンド(この場合は行頭以外でも動作する)ですが,このような使い方は特に意味を持たないでしょう.

####3.3.4 ファイルの扱い

#####3.3.4.1 コメント

cafeではファイルを読み込む場合,「#」からその行の最後までを無視します.つまりcafeの実行に関係のないコメントとして扱ってくれます.

#####3.3.4.2 ファイル内での関数

ファイル内では,より大きい関数の定義をしやすくするため,複数行に渡る関数定義が行えるようになっています.
通常のターミナルから行う関数定義は1行に限られ,関数終了の記号「;」を用いなくても,行末を関数定義の終了として扱います.
ファイルでの関数定義はこれと違い,「:」から「;」までのすべてを一つの関数定義として扱います.

###3.4 例外的な文法を用いるコマンド

先にも少し触れましたが,cafeでは「逆ポーランド」の記法に則っていないコマンドがいくつかあります.これらは関数の定義などの関数操作,変数の操作,条件実行やループの制御さらにprintのコマンド類です.これは単に式を書くとき,読むときの扱いやすさのためにこのような記法の例外を採用しています.

しかし,プログラム的なコードを書く場合には,これらの例外的文法によって制約が出てくると考えられます.
たとえば「関数を定義する際,その名前やボディを変数としてスタックから渡したい」ような場合が考えられます.
このようなときには記号「&」を用います.この「&」はcafeが式を読み込む際に,そのときのスタックの中身によってこの記号を置き換えてくれます.次は関数定義の例です.通常の関数の定義方法と,「&」を使ったバージョンの両方を示しています.

CaFE (empty) % fstack
CaFE (empty) % : function_sample1 sample_body ;
CaFE (empty) % "sample_body"
CaFE ("sample_body") % "function_sample2"
CaFE ("function_sample2") % : & & ;
CaFE (empty) % fstack
function[ 0] : function_sample1 sample_body
function[ 1] : function_sample2 sample_body
CaFE (empty) % 

注意:「&」は一つのトークンとして扱われます.たとえばスタック上に"test sample"のようにスペースを含む文字があり,それに対して「: & ;」を実行した場合には,「sample」は関数のボディにはなりません.この場合には「&」でスタックから取ってきた文字列「test sample」全体が関数の名前になります.
このような関数名の関数は以下のようして実行が可能です.

CaFE (empty) % "test sample"
CaFE ("test sample") % : & print "function sample" ;
CaFE (empty) % test¥ sample
"function sample"
CaFE (empty) % 'test sample'
"function sample"
CaFE (empty) % 

注意:「&」の扱いには注意が必要です.「&」が評価されるとすぐにその文字がスタック上の値に置き換えられます.上の例のように関数定義の中で使われた場合,「&」が出てくる度その場所が順番にスタックの値で置き換えられて行きます.
関数定義時ではなく実行時に置き換えを行いたい場合には「&」ではなく「^」を使います.「^」は関数定義の際には通常の文字として扱われ,そのままの状態で関数のボディに保存されます.そののち関数の実行時に評価されたとき初めてスタック上の値に置き換えが行われます.

###3.5 シェル・コマンド操作

先の節に「シェル・コマンド操作」という言葉が出てきましたが,まだ説明をしていませんでした.

cafeではその内部からシェル・コマンドを実行させることができます.
「!」コマンドはそれに続く文字列を「sh」で実行させるという意味になります.

CaFE (empty) % "pwd" !
/Users/okano
CaFE (empty) % 

このようにシェルで「ちょっと何かをさせたい」時に便利です.「!」を使った場合,その表示のみが行われ,cafeの実行自体には影響を与えません.

さらに「!!」というコマンドが用意されています.このコマンドは「シェルでの実行結果をcafeに取り込む」ことをしてくれます.以下はシェル上で「3 + 5」を実行させて,その結果に7を掛けるという作業をしています.

CaFE (empty) % "expr 3 + 5" !!
CaFE (    8) % 7 *
CaFE (   56) % 

###3.6 文字列の操作

文字列の操作には簡単な文字列連結のコマンドのみが用意されています.
「glue」コマンドがそれで,スタック上の文字列を合わせて一つの文字列とします.

CaFE (empty) % "String" "Sample" stack
sp[  0] (s)    "String"
sp[  1] (s)    "Sample"
CaFE ("Sample") % glue
CaFE ("StringSample") % stack
sp[  0] (s)    "StringSample"

###3.7 文字列の評価

スタック上の文字列を式として使うことができます.
コマンド「eval」はスタック上の文字列をそのまま評価して実行します.以下はその例です.

CaFE (empty) % "1 2 3 4 + + +"
CaFE ("1 2 3 4 + + +") % stack
sp[  0] (s)    "1 2 3 4 + + +"
CaFE ("1 2 3 4 + + +") % eval 
CaFE (   10) % stack
sp[  0] (i)    10

###3.8 プロンプト表示の切り替え

「prompt」コマンドに表示のフォーマットをしていする文字列を与えることで,切り替えが行えます.このフォーマットについての詳細は以下の表の通りです.C言語のprintf変換のように%を用いてその表示内容を指定します.

%b		: この場所以降の文字を太字に
%n		: この場所以降の文字を通常の文字に
%r		: スタックの最上位のデータを表示
%p		: スタックポインタの値(空の時は何も表示しない)
%P		: スタックポインタの値(空の時は「e」を表示)
%s		: 現在対象としているスタックのID
%S		: 現在対象としているスタックのID (%sと同じ)
%%		: 「%」を表示

下に示した例では「() % 」をプロンプトとし,カッコ内のイコール記号の間にスタックID,カギカッコの中にスタックポインタ値,そのあとに現在のスタックの最上位にあるデータの値を表示するようにします.

CaFE (empty) % "( =%s= [%p] %b%r%n ) %% " prompt
( =0= [] empty ) % 

この例では対象としているスタックIDが0(デフォルト・スタック)であり,スタック・ポインタは初期値(スタックに何も無い)を指しており,スタック上には何もデータが無い状態を示しています.
プロンプト表示の指定は「.cafe」ファイル等に指定しておくと便利です.

##4. 関数と変数の詳細

###4.1. 関数

####4.1.1 関数の管理のされかた

定義した関数は「fstack」と呼ばれる専用のスタック上に保存されます.
保存された関数は明示的に消去しない限り,そのスタック上に残ります.fstackはデータ保存目的のスタックとは独立しており,専用のコマンドを用いて操作します.

関数の定義は既に説明したように一対の「:」「;」の記号を用いて行われます.関数が新たに定義されると,その関数はfstackにプッシュされます.プッシュされた関数は名前部とボディ部に分けて文字列として保存されます.関数の呼び出しは一致する名前が検索された後,ボディ部が取り出され,その文字列が評価されるという手順で行われます.

定義しようとしている関数の名が,既にfstackにあると,同名の関数を上書きします.[4.1.2]関数の再定義の項参照.

定義された関数は(後述するパッケージに関係なく)何処からでもアクセスが可能です.

次にfstack操作用のコマンドを解説します.   引数をとるコマンドはすべて後置型のコマンドで,関数の名前を引数として与えます.   この関数の名前の指定には「""」のような引用符は用いません.

「fstack」はfstackを表示します.現在のスタックの状態が示されます.

「forget」は関数を消去するのに使うことができます.forgetはこのコマンドの後に引数をとり,引数として指定された関数と,その後に定義された関数すべてが消去されます.

CaFE (empty) % fstack 
:minute           60
:hour             minute 60 *
:day              hour 24 *
:week             day 7 *
:db               logt 20 *
:abs              if neg '-1 *'
:radtodig         pi / 180 *
CaFE (empty) % forget db
forget function "radtodig".
forget function "abs".
forget function "db".
CaFE (empty) % fstack   
:minute           60
:hour             minute 60 *
:day              hour 24 *
:week             day 7 *
CaFE (empty) % 

「fdelete」は単一の関数の消去に使います. 「forget」「fdelete」はどちらも引数を一つだけ取ります.

「frename」は関数の名前を変更します.二つの引数を用い,変更対象とする関数名,変更後の関数名の順で指定します.

CaFE (empty) % fstack
function[ 0] : sample_name      function body
CaFE (empty) % frename sample_name after_rename
CaFE (empty) % fstack
function[ 0] : after_rename     function body
CaFE (empty) % 

関数のボディ部の変更は同名の関数の再定義で行えます.対話的環境では「fedit」というコマンドが用意されており,指定した関数をコマンドラインに呼び出すことができます.   コマンドラインに呼び出した既に定義されている関数の一部を編集し,リターンキーを押すことで,再定義を簡単に行うことができます.   このコマンドを使うことによって,関数定義文のタイプの手間や,コピーペースとの手間が省けます.

####4.1.2 関数の再定義

関数は再定義可能です.先に定義してある関数と同じ名前の関数を定義すると,後の定義内容で上書きされます.

####4.1.3 コマンドの(関数による)再定義

関数同様にコマンドも再定義可能です.しかしこれはコマンド自体を書き換えるのではなく,コマンドと同名の関数を定義することになります.cafeでは同名の関数とコマンドがあった場合,関数の実行が優先されるため,結果的にコマンドを再定義したように見せかけることができます.

####4.1.4 関数と同名のコマンドを実行する(明示的コマンド実行指定)

関数によってコマンドを再定義したあとで,元のコマンドを実行したくなるときがあります.
たとえば...

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
「undo」コマンドはスタックの状態を一つ前に戻しますが,「undo」実行後に毎回「stack」を実行して内容を確認するのは面倒です.このようなとき「undo」コマンドを一連のコマンドで再定義しておけば便利になるでしょう.
しかし残念ながら以下のような関数定義では「undo」の再帰コールを作ってしまい,望む結果は得られません.

: undo undo stack ;

この問題を回避するため,関数ではなくコマンドを指定するための方法が用意されています.コマンド名の前に「@」をつけ,それに続いてスペースを空けずにコマンド名を記します.

: undo @undo stack ;

これにより,undo関数の実行により,undoコマンドを実行-->stackを実行することができるようになります.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

####4.1.5 関数の再帰コール

前説では関数の再帰コールという言葉が出てきました.

cafeでは関数の再帰コールが可能です.
以下は再帰コールの例です.関数gcdはスタックにおかれた2つの値の最大公約数(GCD)を求めます.関数gcdはその関数内でさらにgcdを,つまり自分自身を呼んでいます.このように自分自身を直接,あるいは間接的に(他の関数を経由して)呼ぶことができます.

: gcd 2 mdup pop ifelse t '% swap gcd' 'pop swap pop' ;

####4.1.6 関数の中での関数の定義

関数の中で関数の定義をすることが可能です.
下の例では関数fooを実行することにより,barが定義されます.

: foo : bar do_something ; ;

この関数の中での関数の定義には注意が必要です.上記の例では関数fooが実行される度に関数barが再定義されます.もし複数回fooが実行されるなら,その実行の都度,関数のオーバーライトが行われます.
さらにもしbarが,fooの実行よりも先にどこかで別の同名の関数として定義されていた場合には,先に定義されていた内容を壊すことになってしまいます.このような問題を起こさないように注意が必要です.

変数の場合には関数内だけで有効な(プライベートな)変数を作ることが可能なので,このような「名前」についてあまり神経質になることはありません.しかし関数は常に何処からでもアクセス可能なものとして登録されるため,意図しない上書きに注意が必要となります.

####4.1.7 当座関数

「if」や「times」のコマンドを用いる場合,それに続く一連のコマンドはシングルクォート「'」でまとめることができると説明をしました.しかし,たとえばより複雑な関数を書くような場合には少し不便です.

シングルクォートは次のシングルクォートまでを一連のまとまりとして扱います.これを「入れ子」にするにはどうしたら良いでしょうか?
たとえば「..___.」のような表示をする関数を定義する場合には,以下のような書き方ができます.

:sample 3 times '3 times ¥'print _¥' print .'

'print _'を3回実行し,さらにこれと'print .'を一緒に3回実行します.ここで'print _'の部分は単純に「'」ではなく「¥'」と書かれています.これは最初の「3 times」の実行の際に一旦シングルクォートで囲まれた部分が評価されます.つまり
「'3 times ¥'print _¥' print .'」としてあった部分が「3 times 'print _' print .」を三回実行されることになります.
このように実行対象の指定をシングルクォートだけを用いると,その"記述の深さ"によって「¥'」「¥¥¥'」「¥¥¥¥¥'」のようにバックスラッシュを多くつけ,それを管理しなければならなくなります.これを回避するために「当座関数」が用意されています.

当座関数は「if」,「times」と組み合わせて使われます.
当座関数はその場で定義され実行される関数です.関数定義の文法ではその開始と終了にそれぞれ違った文字を使うためにシングルクォートを用た場合のような問題はありません.

当座関数は「@」を名前とする関数で,「if」や「times」と組み合わせて使われる時にのみ有効な機能です.cafeの内部では「@」という名の関数が定義されると,その内容を特別な当座関数用のエリアに,実際には空文字列を名前とする関数として一旦登録し,ifやtimeだけが呼び出し可能な特別な方法によって実行されるようになっています.

再び当座関数が定義される際には先に定義されている内容を上書きします.

当座関数をネストすることもできます.当座関数は定義された後,すぐに使用されます.内部的には,当座関数の実行は定義された関数のボディのコピーを作って,その文字列に対して実行されるため,当座関数の実行途中で関数が上書きされても影響はありません.

「if」,「times」と関係なく当座関数を定義することはできますが,上記のようにこの関数は名前の無い関数であるため,後から呼び出す手段がありません.このため当座関数が単独て定義された場合には,内部的な関数定義ルーチンが呼ばれ登録だけは行われますが,実行されずに終わってしまいます.

####4.1.8 その他...関数について

#####4.1.8.1 関数の実行制御

「return」コマンドは関数の実行をそこで停止し,関数の呼び出し元へ制御を戻します.
次の関数はスタックの数値が0であれば関数を終了する例です.

: test_return if z return print AAA ;

#####4.1.8.2 関数のボディを得る

関数のボディをスタックに文字列として得る事が出来ます.関数名の前に「@@」をつけることでスタックにその関数のボディが入ります.
あるいは「fbody」コマンドで@@と同様の結果を得ることができます.

###4.2 変数

複雑な計算を行うために,スタックとは別にデータの保存を行うことのできる「変数」が用意されています.

cafeで使われる変数は大きく分けて2種類あります.一つは「通常変数」で,実行する関数の中でのみ有効な変数です.もう一つは「グローバル変数」で,これは関数をまたいで使うことのできる変数です.

変数に「型」はありません.スタックにプッシュできるデータはすべて変数に保存することが可能です.

####4.2.1 通常変数

#####4.2.1.1 通常変数の使いかた.

「通常変数」は一時的なデータの保管場所として使うことができます.
通常変数を使用する際には,使用前の宣言は必要ありません.必要な箇所で代入を行うと,その変数が自動的に作られます.

この通常変数へのアクセスには演算子「>」と「<」を使います.(これらの演算子も後置型のコマンドです.)
この「>」と「<」の演算子はその右側に置かれる名前の変数に対して操作を行います.

変数への代入は「>」を使います.スタックからポップされたものが変数へ格納されます.反対に変数からの読み出しには「<」が使われます.変数の内容が取り出され,スタックにプッシュされます.

変数には自由に名前をつけることができます.この名前に使用できる文字,文字数には特に制限はありません.

下の例ではスタックに置かれたデータを「variable_name」に保存し,その後それを読み出しています.変数に置かれたデータは読み出しを行っても消えないため,複数回の読み出しを行えば,その回数だけのデータがスタックにプッシュされることになります.

CaFE (empty) % "test string"
CaFE ("test string") % > variable_name  
CaFE (empty) % < variable_name
CaFE ("test string") % < variable_name
CaFE ("test string") % stack
sp[  0] (s)    "test string"
sp[  1] (s)    "test string"
CaFE ("test string") % 

 メモ : 
この記号を矢印とみなして,左側にスタック,右側に変数があるものと考えるとより直感的です.数値の代入を1行で書くと「100 > var」のようになります.

####4.2.1.2 通常変数のスコープ

「変数のスコープ」とは「変数が有効な範囲」のことを言います.通常変数は「いま実行している関数内」のみで有効です.

たとえばある関数からもう一つの関数を呼ぶと,その呼ばれた側の関数から,呼び元の関数で使っていた変数にアクセスすることはできません.
たとえば現在実行中の関数の中で「var」という名の変数を使っていて,その関数からさらに関数を呼ぶとします.呼ばれた側の関数の中でも先ほどと同じ「var」という名の変数があったとしても,これには実行中の(呼ばれた側の)関数の中で独自に変数が用意されるため,関数の呼び元側の変数には影響を及ぼしません.

一旦終了した関数が再び呼ばれた時に変数はどうなるのでしょうか?
関数内でのみ有効な通常変数は関数実行が終了する度に廃棄されてしまいます.再び関数が呼ばれ,通常変数のアクセスがあると,その時にまた新たに作られることになります.
このため通常変数では,その関数の前の状態を記録しておくことはできません.

まだ代入の行われていない変数から読み出しを行うとエラーになります.

CaFE (empty) % 1111 > var
CaFE (empty) % : foo 2222 > var < var . ;
CaFE (empty) % foo 
2222
CaFE ( 1111) % < var .
1111

メモ : 関数が新たに実行されると,その関数独自の変数テーブルが用意されます.実行中の関数はこのテーブルの範囲を超えて通常変数にアクセスすることはできません.

メモ : 関数を再帰的に実行した場合,通常変数のテーブルは関数が呼ばれる度に(実行される都度)新たに用意されるため,同名の通常変数であっても違う別々の変数として扱われます.

メモ : 通常変数はC言語の自動変数「auto変数」と同等の機能を持つと考えて差し支えありません.

CaFE (empty) % : foo 3333 > var < var . ;
CaFE (empty) % foo
3333
CaFE ( 3333) % cl
CaFE (empty) % fedit foo
CaFE (empty) % :foo < var . ;
finction overwriting
CaFE (empty) % foo
error : no variable available for "var"
CaFE (error) % 
CaFE (empty) % 

####4.2.2 グローバル(広域)変数

#####4.2.2.1 グローバル変数の使いかた.

通常変数とは違い,関数のワクを超えて使うことのできる変数がグローバル変数です.グローバル変数の基本的な操作の方法は通常変数と同じですが,使用する前に必ず宣言が必要になります.

変数の宣言には「variable」を使います.スタックから二つの引数をとり,まずスコープ指定の「パッケージ名」,次に変数名指定の文字列を取ります.重複した変数名が同一スコープで指定された場合にはエラーとなり,実行中の処理を停止します.
宣言の例を示します.この例では「a_var」と言う変数を「scope」という名のパッケージ名で有効な変数として宣言しています.

"a_var" "scope" variable

スコープに空文字列「""」を指定した場合には,スコープ指定なしのグローバル変数となり,どこからでもアクセス可能な変数となります.

####4.2.3 変数のアクセス制御

変数の名前の衝突を避けるために,変数へのアクセスの制御を行うことができます.

#####4.2.3.1 通常変数のアクセス範囲変更

通常変数は実行中のその関数内でのみ有効なのですが,まれにその関数の呼び出し元で使われていた変数にアクセスしたい事があります.
これを行うためのコマンドが用意されています.
「[]」で囲まれた範囲内では関数の呼び出し元の変数アクセスが可能になります.
下ののような関数が実行されると,数値222がこの関数の通常変数varに書き込まれます.この後「[」によって呼び出し元関数の持つ通常変数へのアクセスが許可され,呼び出し元のvarが読み出され表示されます.この後「]」によって通常の変数アクセス状態に戻され子の関数の持つvarが読み出されその内容が表示されます.

: callee 222 >var [ <var . ] <var . ;

「[」はネストも可能で,関数の呼び出し元の呼び出し元を参照するような使い方もできます.
このカッコは必ず対になってなければなりません.カッコを閉じずに関数が終わると,その後の挙動は不定となってしまいます.

関数の実行ではなく,対話的に使用している場合でも宣言無しの変数を使うと,その変数は通常変数となります.

#####4.2.3.2 グローバル変数のスコープ指定

グローバル変数は指定したパッケージ内であれば,どこからでもアクセス可能です.パッケージ名を空文字「""」としていすると,パッケージによる制限を受けない,どこからでもアクセス可能な変数となります.
パッケージ名でスコープを指定されたグローバル変数は,そのパッケージに属した関数からのみアクセスが可能になります.
パッケージについては次の節で解説します.

###4.3 パッケージ

####4.3.1 パッケージの指定

特定の関数からのみアクセス可能な変数を実装するため,「パッケージ」をという概念を使います.
たとえばひとつのファイルにプログラムを記述するとき「プログラムは複数の関数から構成されるように作るが,共通の変数を持たせたい」という事態が発生します.
このときcafe全体からアクセスできるグローバル変数を使うのも方法ですが,同時に使うファイルが,既に同名のグローバル変数を持っているときには問題(変数名の衝突)が発生します.
cafeでは同一のグローバル変数が再定義されたときには,warningメッセージが出るのみで,その後のアクセスについては問題としません.このためユーザはこのようなメッセージを発見したら,名前が重複しないように変更を加えなければなりません.これを気にせず使用すると,意図しない変数の値の更新が発生してしまいます.

このような問題の回避手段として,「パッケージ」が用意されています.
「use」コマンドでファイルの読み込みが開始されると,そのファイル名をパッケージ名として読み込み処理が行われます.
このファイルの中で関数が定義されていると,その関数はそのパッケージ名をスコープを持つ変数へのアクセスが可能なものとして定義されます.
もし複数のファイルでひとつのパッケージを構成したいなら,「package」コマンドを使って共通の名前を指定しておく事ができます.
「package」コマンドで指定したパッケージ名は,そのファイルの読み込みが終了するか,あるいは次の「package」コマンドが使われるまで有効で,そのファイル名の代わりに指定された文字列をパッケージ名として使用します.
ファイルの読み込みが終了するとcafeは空文字パッケージ名が指定された状態に戻します.
「package」コマンドはcafeを対話的に使用しているときにも有効です.この機能を使って,アクセス制御をかけた変数への読み書きができます.対話的に使用している際はファイルの読み込みの終了がないため,元の状態に戻すには空文字をパッケージ名に指定しなければなりません.

「関数の管理のされかた」の節で述べた通り,関数には変数のようなアクセス制御機能は用意されていません.このため関数は何処からでもアクセス可能です.パッケージ内でのプライベート化ができないため,名前の重複(衝突)には注意が必要です.
変数,関数に一度に複数のパッケージを設定することはできません.

####4.3.2 変数検索順序

変数に同一の名前が使われている場合,cafeは変数を通常変数,アクセス制御ありグローバル変数,アクセス制御なしグローバル変数の順で検索し最初に見つかった変数にアクセスします.このアクセス順を変えることはできません.

###4.4 変数表示

現在使われている変数とその値を表示させることができます. 「vl」コマンドは通常変数,アクセス制御ありグローバル変数,アクセス制御なしグローバル変数のそれぞれをリスト表示します.
「vl」スタックから一つの文字列を取り出し,その文字列がスコープとして指定されているグローバル変数を表示します.この文字列が空文字列("")であればアクセス制御なしのグローバル変数を表示します.もしスタックが空であれば実行中の関数内で有効な通常変数を表示します.

通常変数を表示されるためにその都度スタックを空にするのは不便なので,「vls」というコマンドが用意されています.このコマンドはスタックの状態に関係なく単独で動作します.

###4.5 変数名の補完,候補表示機能

変数名の補完,候補表示も行えます.変数操作のコマンド「>」「<」にスペースを空けずに続けて名前を入力し,途中でタブキーを押すと変数名の候補が現れます.この変数名の候補リストはcafeの起動後に使われたすべての変数名を候補リストとして持っています.

##5. 文字列の操作,表示の変更

###5.1 文字列の操作のためのコマンドが用意されています.

####5.1.1 文字分解

文字列を扱うためのいくつかのコマンドが用意されています.文字列を単一文字からなる複数の文字列に分解する「s2c」,「s2cn」が用意されています.「s2c」は「string to characters」の略としてこのような名前がついています.以下にに実行例を示します."string"という文字列が文字に分解されてスタックに積まれています.

CaFE (empty) % "string"
CaFE ("string") % s2c
CaFE ("g") % stack
sp[  0] (s)    "s"
sp[  1] (s)    "t"
sp[  2] (s)    "r"
sp[  3] (s)    "i"
sp[  4] (s)    "n"
sp[  5] (s)    "g"
CaFE ("g") % 

分解を行う文字を数値によって指定することもできます.これは「s2cn」コマンドを使います.このコマンドはスタックからポップした整数値によって分解する文字列部分が指定できるようになっています.下の例では最初の5文字を分解,次に最後の3文字を分解する例を示しています.

CaFE (empty) % "abcdefghijklmn"
CaFE ("abcdefghijklmn") % 5 s2cn
CaFE ("fghijklmn") % stack
sp[  0] (s)    "a"
sp[  1] (s)    "b"
sp[  2] (s)    "c"
sp[  3] (s)    "d"
sp[  4] (s)    "e"
sp[  5] (s)    "fghijklmn"
CaFE ("fghijklmn") % cl
CaFE (empty) % "abcdefghijklmn"
CaFE ("abcdefghijklmn") % -3 s2cn
CaFE ("n") % stack
sp[  0] (s)    "abcdefghijk"
sp[  1] (s)    "l"
sp[  2] (s)    "m"
sp[  3] (s)    "n"

####5.1.2 トークン分解

文字分解だけではいささか不便なので文字列をトークンに分解するコマンドも用意されています.
トークン分解は,cafeが持っているトークン解析用のルーチンをそのまま使うので,一旦作成した関数がどのように評価されるのかを確認する目的にも使えます.
「s2w」を使用した例を示します.

CaFE (empty) % ":gcd 2 mdup pop:ifelse t '% swap gcd' 'pop swap pop' ;"
CaFE (":gcd 2 mdup pop:...") % s2w
CaFE (";") % stack
sp[  0] (s)    ":"
sp[  1] (s)    "gcd"
sp[  2] (s)    "2"
sp[  3] (s)    "mdup"
sp[  4] (s)    "pop"
sp[  5] (s)    ":"
sp[  6] (s)    "ifelse"
sp[  7] (s)    "t"
sp[  8] (s)    "% swap gcd"
sp[  9] (s)    "pop swap pop"
sp[ 10] (s)    ";"
CaFE (";") % 

「s2wn」コマンドはs2cn同様に部分を指定して分解することもできます.

CaFE (empty) % ":gcd 2 mdup pop:ifelse t '% swap gcd' 'pop swap pop' ;"
CaFE (":gcd 2 mdup pop:...") % 4 s2wn
CaFE (":ifelse t '% swa...") % stack
sp[  0] (s)    ":"
sp[  1] (s)    "gcd"
sp[  2] (s)    "2"
sp[  3] (s)    "mdup"
sp[  4] (s)    ":ifelse t '% swap gcd' 'pop swap pop' ;"
CaFE (":ifelse t '% swa...") % cl
CaFE (empty) % ":gcd 2 mdup pop:ifelse t '% swap gcd' 'pop swap pop' ;"
CaFE (":gcd 2 mdup pop:...") % -5 s2wn
CaFE (";") % stack
sp[  0] (s)    ":gcd 2 mdup pop:"
sp[  1] (s)    "ifelse"
sp[  2] (s)    "t"
sp[  3] (s)    "% swap gcd"
sp[  4] (s)    "pop swap pop"
sp[  5] (s)    ";"
CaFE (";") % 

####5.1.3 文字列の長さ

文字列に含まれる文字数や,トークンの数を得るコマンドも用意されています.それぞれ「strlen」,「strwc」が対応します.

####5.1.4 その他

文字列を逆に並び替えるコマンドが「strrev」用意されています.
また文字をASCIIコードに従った数値に変換する「c2i」とその逆の変換をする「i2c」も用意されています.

次の例は文字列をASCIIコード列に変換する関数と,その使用例です.

CaFE (empty) % :ascii :gs " " swap glue glue;  strlen >length s2c <length times 'c2i rot' <length 1 - times gs;
CaFE (empty) % "abcdefg" ascii
CaFE ("97 98 99 100 101...") % stack
sp[  0] (s)    "97 98 99 100 101 102 103"
CaFE ("97 98 99 100 101...") % 

次の例は,上の関数が変換したASCIIコード列を文字列に戻す関数の例です.

CaFE (empty) % :ascii_rev strwc >length s2w <length times 'eval i2c rot' <length 1 - times glue;
CaFE ("97 98 99 100 101...") % ascii_rev 
CaFE ("abcdefg") % 

###5.2 表示の変更

先にプロンプト表示の変更についての説明を行いましたがここでは整数や浮動小数点表示の変更方法を解説します.

整数,浮動小数点のそれぞれが画面に表示されたりやファイルに書き出される際の表示形式を変更すすことができます.「format_int」,「format_float」のそれぞれのコマンドがこれにあたります.

「format_int」コマンドは整数を表示する際の表示形式,「format_float」は浮動小数点の表示形式を決定します.これらのコマンドはスタックから文字列を一つポップし,それを表示の際のフォーマットそして使用します.このフォーマット文字列はcafeの内部でprintf関数にそのまま渡されるため,printf変換のそれに倣った記述である必要があります.

下に使用例を示します.

CaFE (empty) % 0xabcd
CaFE (43981) % "0x%X" format_int
CaFE (0xABCD) % "%Xh" format_int
CaFE (ABCDh) % stack
sp[  0] (i)    ABCDh
CaFE (ABCDh) % 10 3 /
CaFE (3.33333) % "%.10lf" format_float 
CaFE (3.3333333333) % stack
sp[  0] (i)    ABCDh
sp[  1] (f)    3.3333333333
CaFE (3.3333333333) % 

どちらのコマンドの場合も,フォーマットとして空文字列を指定することでデフォルトの状態に戻すことができます.

CaFE (3.3333333333) % "" format_float 
CaFE (3.33333) % 

##6. ユーザ・スタック

###6.1 ユーザ・スタック

注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意
この機能が追加されたためundoコマンドを暫定的にサポートしてません.20060602
注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意

通常のスタック(デフォルト・スタック)とは別に,スタックを別に用意することが可能です.
新しいスタックを宣言すると,現在のスタックの上に新しいスタックが作られます.このようにしてスタックの上に子スタック,さらに孫スタックを作っていくことができます.

新しいスタックが作られると,そのスタックには固有のID番号が割り振られます.デフォルトスタックのIDは0となっており,ユーザによって作成されたスタックは1番から順番に数字がつけられていきます.スタックのID番号はユーザの利便性のために設けられたものであり,実際の操作にこの番号を使うことはありません.

「=new」コマンドが新しいスタックを作ります.このスタックはデフォルトスタックと全く同じように使うことができます.

スタック自体を操作するコマンドは,すべて「=」記号で始まります(データの積み重なった「スタック」をイメージしています).
「=new」コマンドを実行した後に現在のスタックを見てみると,「=1=」のようにイコールで挟まれた数字が表示されます.これが新しく作られたスタックです.

メモ :  
	スタックは,一度作られると参照元が存在する限りその内容が保持されます.スタック自体がどこからも参照されなくなった時に消されます.たとえばユーザ・スタックがその親のスタックから消されても変数に保存されていればその内容を保持しています.

メモ :  
	スタックのIDは,cafeの起動後の最初の作成で1が割り振られ,以降作成される都度2,3,4...のように順番に番号がつけられていきます.

新しく作成したスタック上で計算を行うには「=target」コマンドを用いて,対象(ターゲット)とするスタックを指定します.=targetコマンドは現在のスタックの最上位が指しているスタックへ,その対象を切り替えます.いままで使っていたプロンプト表示では,どのスタックを対象としているかがわからないので,ここで切り替えておきましょう.

CaFE (empty) % "( =%s= [%p] %b%r%n ) %% " prompt
( =0= [] empty ) % 

###6.2 スタックの切り替え

上記のプロンプト表示の状態で,先に説明したスタックの作成,切り替えを説明します.

( =0= [] empty ) % 1 2 3
( =0= [2] 3 ) % =new
( =0= [3] =1= ) % stack
sp[  0] (i)    1
sp[  1] (i)    2
sp[  2] (i)    3
sp[  3] (=)    =1=
( =0= [3] =1= ) % =target 
( =1= [] empty ) % stack
( =1= [] empty ) % 

この例では,新しく作られたスタックが現在のスタック上にどのように置かれるかを説明するため,仮に「1」「2」「3」のデータを置いてみています.このあとの=newによって作られたスタックにはID番号1が与えられ,先ほどのスタックのデータの後にプッシュされています.この状態で=targetコマンドを発行すると,計算対象スタックがデフォルト・スタックからID1のスタックに切り替わります.
これはプロンプト表示を設定しておくと,そこで確認できます.新しく作られたスタックは常に空です.ここでもID1のスタックが空なのがわかります.

注意 :   
	スタック上に置かれたユーザスタックは,その実体が置かれているわけではなく「参照先」が指定されているだけです.
	
メモ :  
	ターゲット・スタックとなりえるのは常に一つだけです.これまでに説明したスタック操作のコマンド以外は,すべてこのターゲットとしているスタックに対してのみ有効です.たとえば「cl」はスタックをクリアする目的で用意されたコマンドと説明しましたが.このコマンドもその時点でターゲットとしているスタックに対してのみ有効です.子や孫スタックをターゲットとしている場合にはそのスタックの中身だけがクリアされます.

=newコマンドで作られるスタックは,現在のスタックの「子」となります. =new,=targetコマンドを繰り返すことで,子,孫,曾孫,のようにスタックの上にさらにスタックを作ることが可能です.

子スタックから親スタックを参照するには「=parent」コマンドを使います.=parentコマンドは現在の計算対象としているスタックに「親」スタックを置いてくれます(先の「注意」で述べた通り,実体ではなく参照先として置かれます).ここで=targetを実行すると,親スタックへの切り替えが行われます.もし=targetがスタック以外に実行された場合にはなにも起こりません.

###6.3 スタック上のスタック(参照先としてのデータ)

先にスタック上のスタックは参照先としてのデータであることを述べましたが,もう少し詳しく説明します.
=newコマンドが実行されると新しくスタックが作られます.このスタックは実体が現在のスタックの上に作られるわけではなく,別の場所に実際のデータの置き場所が作られます.作成した元のスタックにはIDが表示されますが,これはあくまでこのスタックを指しているIDに過ぎません.
実験してみましょう.

( =0= [] empty ) % =new 
( =0= [0] =1= ) % dup
( =0= [1] =1= ) % stack
sp[  0] (=)    =1=
sp[  1] (=)    =1=
( =0= [1] =1= ) % 

最初に=newコマンドでスタックを作り,dupコマンドを実行します.dupコマンドによってできるのはID2のスタックではなく,ID1のスタックがコピーされています.ここまでの操作に引き続いて=targetコマンドを使ってID1のスタックを操作してみます.

( =0= [1] =1= ) % =target 
( =1= [] empty ) % 1 2 3
( =1= [2] 3 ) % =parent =target
( =0= [1] =1= ) % stack
sp[  0] (=)    =1=
sp[  1] (=)    =1=
( =0= [1] =1= ) % pop
( =0= [0] =1= ) % stack
sp[  0] (=)    =1=
( =0= [0] =1= ) % =target 
( =1= [2] 3 ) % stack
sp[  0] (i)    1
sp[  1] (i)    2
sp[  2] (i)    3
( =1= [2] 3 ) % 

元のスタックの最上位にあったID1のスタックを計算対象とするため=targetを実行しました. 次にID1のスタックに「1」,「2」,「3」のデータを積んでいます.
その後=parent に続いて =target を実行し,計算対象を親スタックに切り替え元に戻していますます.
stackコマンドを実行すると先ほどと同じように=1=が二つ存在します.このうち先ほどスタックを操作した上位側の方のスタックを現スタックから消去します.
残った側のスタックを計算対象にしてスタックの内容を確認すると,先ほどの内容が残っているのがわかります.つまりID1のスタックは同じものであり,そこへの参照を一つ消しても内容はそのまま保持されていることがわかりました.

スタックの実体は参照されている限り残ります.参照とはたとえばあるスタック上にそのスタックが存在している状態や,変数に保存されている状態にあたります.

###6.4 =targetコマンドの挙動

=target コマンドの実行後のスタックの状態は一定ではありません.

  • 計算対象として子スタックが指定された場合には,親スタックにはその子スタックが残されます.

  • 計算対象として親スタックが指定された場合には,現スタックにはその親スタックへの参照は残されません.

    これは=parentが=targetの直前に実行された場合ばかりでなく,間接的に参照された場合も適用されます.
    

子スタックが親スタックへの参照を持ち,そのスタックへ子スタックの下,つまり孫スタックとして親にアクセスするのは循環参照となるため望ましくありません.このような理由で親スタックへの参照を上記のように実装してあります.
またさらに自身をターゲトにする場合にも,

  • 現スタック自身を明示的に指定した場合には,その参照は残されます.
  • 現スタック自身を明示的に指定しなかった場合には,スタックの値は消されます.

###6.5 スタックのコピー

先ほどのdupコマンドによる操作では,スタックの参照が増えただけでした.実体は同じもののため,片方の内容が変更されるともう一方の内容も変更されてしまいます.
独立したスタックのコピーは「=copy」コマンドで作ることができます.=copyコマンドはスタックから二つの引数を取ります.最初にポップされたデータをコピー先,後からポップされた方をコピー元とします.下に使用例を示します.

( =0= [] empty ) % =new =target
( =1= [] empty ) % 1 2 3 4 5
( =1= [4] 5 ) % =parent =target 
( =0= [0] =1= ) % stack
sp[  0] (=)    =1=
( =0= [0] =1= ) % =new
( =0= [1] =2= ) % stack
sp[  0] (=)    =1=
sp[  1] (=)    =2=
( =0= [1] =2= ) % =target stack
( =2= [] empty ) % =parent =target 
( =0= [1] =2= ) % =copy
( =0= [1] =2= ) % =target stack   
sp[  0] (i)    1
sp[  1] (i)    2
sp[  2] (i)    3
sp[  3] (i)    4
sp[  4] (i)    5
( =2= [4] 5 ) % 

ID1のスタックを作成しその中に「1」「2」「3」「4」「5」をセットしています.
その後,親スタックに戻り新しい子スタックID2を作成.その中身を確認すると空になっています.
=copyを実行します.ID1からID2へスタックの内容がコピーされ,その内容を確認しています.

ID1の内容がすべてID2のスタックにコピーされました.
もし既にID2のスタックにデータが存在した場合には,ID1のデータがその後に追加される形になります.

=copyコマンドは子スタックばかりではなく,親スタックや自分自身をその対象に指定できます.
=targetコマンドと同様に自分自身を参照する場合にはその引数として非スタックデータを指定します.

##7. cafeを制御する

###7.1 パラメータ設定

cafeの挙動を決定するパラメータは,あらかじめ定義されている変数へのアクセスとして制御できます.
パラメータとして定義されている変数はすべて「$$」で始まる名前を持ちます.

たとえばユーザが意識しておかないといけないものに「再帰コールの制限」があります.実行時の不用意なメモリのオーバーフローを防のが目的です.再帰コールの制限は「$$RECURSIVE_LIMIT」変数で設定されます.
デフォルト値は2000です.
再帰コールの制限値を変更するのは自由ですが,プログラムのクラッシュの危険を常に考えておかなくてはなりません.

この値の変更は以下のように行います.

CaFE (empty) % 1000 > $$RECURSIVE_LIMIT
CaFE (empty) %

現在の値をスタックに得るには反対の不等号記号を使います.

CaFE (empty) % < $$RECURSIVE_LIMIT
CaFE (1000) %

実際に$$RECURSIVE_LIMITの値を設定する際には「何回まで式を再帰的に評価可能にするか」を設定することとなるため,この値が直接「再帰コールの限界数」となるわけではありません.
たとえば最大公約数を求める関数「gcd」の例では...

1. ifelseの条件が真であれば'% swap gcd'をトークンとして
   評価する
2. '% swap gcd'には「gcd」が関数として存在するので,そのgcdを
   さらに評価する.

次の再帰コールが行われるまでに式が2回評価されます.このため実際の再帰コールは「1000 > $$RECURSIVE_LIMIT」とした場合には499回までという制限となります(500でないのは最初の式評価も1つめと考えるためです).

操作可能なパラメータ変数の一覧を得ることもできます.この時には「>」または「<」コマンドに続き「$$」(ドル記号2つのみで名前無し)の変数を指定することで表示されます.

CaFE (empty) % < $$
current mode status
  $$INTERACTIVE_MODE             : 1
  $$GET_STDIN                    : 0
  $$USE_PREFERENCE               : 1
  $$USE_HISTORY                  : 1
  $$PUT_HISTORY                  : 1
  $$RECURSIVE_LIMIT              : 2000
  $$ERROR_DETECTED               : 0
  $$N_HISTORY                    : 500
  $$QUIT                         : 0
  $$FUNC_EXE_ABORT               : 0
  $$FORCE_FUNCTION_RTN           : 0
CaFE (empty) % 

たとえば

1 > $$QUIT

とするとcafeは終了します.

メモ : これらのパラメータの名前を毎回正確に書くのは面倒なので,変数名の補完機能を使うと便利です.

###7.2 コマンンドラインから使う

ここまではcafeを対話的に使う方法を説明してきました.この節ではcafeを非対話モードで使用する方法を説明します.

####7.2.1 コマンドライン上で計算を行う.

cafeをコマンドラインで使用する方法を紹介しましょう.cafeコマンドに続いて計算式を入力します.この方法を使う場合には,プロンプトは現れないので,その結果の表示を明示的に指示する必要があります.

alice:‾ % cafe 3 4 5 - + .
2

最後のピリオドは結果を表示させるコマンドです.cafeを非対話モード使う場合にはプロンプトは表示されません.このためでピリオド無しでは何も表示されずに終了してしまいます.(ピリオドはスタックの一番上に置かれている値を表示するコマンドです.)

掛け算を行う際には注意が必要です.このように計算式を書く場合には,シェルがアスタリスクを展開してしまうため,cafeに「」が渡らなくなってしまいます.このような問題を避けるためには「」を「¥*」と書いてやらなくてはなりません.

alice:‾ % cafe 3 4 5 ¥* + .
23

####7.2.2 標準入力を使って,ファイルから入力.

標準入力を使うとファイル入力で計算をさせることができます.cafeを非対話モードで起動するために「-B」オプションを使用します.

alice:‾ % echo '3 4 5 * + .' > test.txt
alice:‾ % cat test.txt
3 4 5 * + .
alice:‾ % cafe -B < test.txt
23

cafeの起動に(オプションスイッチ以外の)2つ以上の引数がを用いると,-Bオプションを用いなくても自動的に非対話モードでの起動となります.[5.2.1]節の例では,cafeに与えた計算式の各文字が引数であり,合計6個の引数があったために-Bが必要なかったのです.

####7.2.3 ファイルを処理する.

「-f」オプションを用いてcafeの起動時にファイルを読み込ませることができます.cafeは起動後,通常動作として「‾/.cafe」と「‾/.cafe.autopreference」を読み込み,その後「-f」オプションで指定されたファイルを読み込みます.対話モードでは起動後に「use」コマンドを使ってファイルを読み込むのと同等です.
「-f」オプションは対話モードと非対話モードのどちらでも使うことができます.非対話モードで使用する際は引数の数に注意してください.-fオプションの後に指定されるファイル名はこのオプションスイッチの引数となり,cafeの引数の数にはカウントされません.このため-fオプションを用いてcafeを非対話モードで起動する時には-Bを使用した方が安全です.

-fオプションは複数使用することができます.-fをファイル名を繰り返し書くことで,複数のファイルを読み込むことができます.読み込む順序は,コマンドラインに書かれた逆順,右から左へとなります.
下は非対話モードで複数のファイルを処理する例です.-Bオプションで非対話モードで起動しています.

alice:‾ % cat function.txt
: process 22 * ;
alice:‾ % cat test.txt
3 4 5 * + . process .
alice:‾ % cafe -B -f test.txt -f function.txt
23
506

この例ではcafeは非対話モードで起動はされているのですが,ファイル処理をの後に自動的に終了してくれません.非対話モードを使っても入力を標準入力から得ることを期待しているため,ファイルの読み込み後,標準入力からEOF(「Control-D」)を入力しなければ,対話モードと同じようにプログラムが待っている状態になります.
-fオプションを使い,標準入力からはなにも入力しない場合にはそれをオプション「-!」によって指定してやらなければなりません.上記のような場合には,cafeを「cafe -! -B -f test.txt -f function.txt」のように起動します.

####7.2.4 入力の組み合わせ処理.

上記で説明したようにcafeを非対話モードで起動する際にはオプションの指定によっていくつかの入力を同時に指定することができます.
a. 引数としての入力
b. 標準入力
c. -fオプションを用いたファイル入力
これらはそれぞれ単独で使用することも組み合わせて使用することもできます.組み合わせて使用する場合には次のような仕組みで動くようになっています.

ステップ1. 起動後に-fオプション指定のファイルを順番に読み込む
ステップ2. ステップ1終了後,引数として指定された式をロードする.
ステップ3. 標準入力を1行づつ読み込み,ステップ2でロードされた式を行末に付け足して計算処理を行う.

alice:‾/coding/CaFE2 % cat average.txt
:avg depth >dp aa <dp /;
alice:‾/coding/CaFE2 % cat multiline.txt
5 5 5 5 5 5 6 5 5 5
32 43 54 42 23 32 23 2 54 12 45 11 53
1 2 3 4 5 6 7 8 9 9 9 9 9 9 9 9 9 9 9 9

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1
2 2 2 3 4 5 5 5 5 5 4 3 3 2 1 2 3 4 4 5 5 6 2
alice:‾/coding/CaFE2 % cafe 'avg . cl' -f average.txt < multiline.txt
5.1
32.7692
7.2

1.04167
3.56522
alice:‾/coding/CaFE2 %                 

##8. デバッガ

###8.1 デバッガの起動

関数の振る舞いを検証するためにデバッガが用意されています.
デバッガ機能の起動には以下の二つの方法があります.

一つは関数を実行する前に「debugger_enable」コマンドを実行しておく方法,もう一つは関数内に「pause」コマンドを用意しておく方法です.これらのコマンドは実質的にどちらも同じですが,「pause」ではデバッガモードに入ったメッセージが表示されます.

ここで例を示して説明します.まず最初に「test」という名の関数を用意し,その関数をステップ実行してみます.

CaFE (empty) % :test 10 times 'print A';
CaFE (empty) % debugger_enable 
CaFE (empty) % test
<< in function ""  [prev:"debugger_enable"]  [next:"test"]  [d/r:1/1] [faf=0] >>
debug > 

最初に「test」関数を定義した後,「debugger_enable」コマンドを実行して,デバッガモードを設定しています.この後「test」を実行すると,デバッグ・プロンプトが表示され,実行が一時停止状態となっています.
このデバッグ・プロンプト表示がされている時に「?」キーを押すとヘルプが表示されます.

CaFE (empty) % test
<< in function ""  [prev:"debugger_enable"]  [next:"test"]  [d/r:1/1] [faf=0] >>
debug > 
	n        : execute next command
	[return] : execute next command
	[space]  : execute next command
	i        : step into function
	o        : step out from function
	g        : go to next breakpoint
	s        : show stack
	q        : abort from debug mode
	e        : evaluate key input
debug > 

このヘルプ表示はデバッグ・プロンプトで使うことのできるデバッグ・コマンドです.
関数の内部をステップ実行するには「n」,リターン,スペースのいずれかのキーを押せば良いことがわかります.
「g」は実行を続行します関数内にpauseのコマンドが存在しなければ関数をすべて実行して終了します.
「s」は現在のスタックの状態を示します.
「q」はデバッグモードの中止,cafeの内部的なエラーを発生させて関数の実行を打ち切ります.
「e」はユーザのコマンド入力を可能にします.

デバッグ・プロンプトの上の行には,実行状態が表示されています.まず「<<」の記号に続いて現在一時停止している関数の名前,次に直前に実行したトークン文字列,その右横には次に実行されるトークン,さらに現在の関数実行レベル,最後に関数の実行中止フラグです.

この中の「関数実行レベル」とは,実際に関数をどのレベルで停止させるかという値と,現在はどのレベルなのかという値の二つの数値から成っています.

cafeでは関数が関数を呼ぶことが許されています.関数から呼び出す関数は他の関数でも自分自身でも構いません.呼び出された関数はさらに関数を呼び出すことができます.ここでは関数からさらに呼ばれる関数をより「深い」関数と呼ぶことにします.
関数実行レベルの値は,この「深さ」を示すものです.実行レベルの値が大きいほど,その関数は深い所で実行されています.

cafeがユーザのコマンドを受け付けてコマンド,あるいは関数の実行が始まったとき,実行レベルは1となります.そこから関数が呼ばれるとその関数は実行レベル2で実行され,さらにその関数の中から呼ばれた関数は実行レベル3で実行されます.
実行レベル3の関数が終了すると,実行は元の関数に戻り実行レベルは2に,その関数の実行も終了すると実行レベルは1に順に戻っていきます.

より深い関数を順次呼び出しながら実行していく仕組みをデバッグする際,すべての深さの関数の実行まで監視するようなことはあまりないでしょう.通常,ある処理を行う場合にはその処理を細かい部品にわけて共通するものを関数化して動作を確認し,その関数をより大きな関数から呼び出して使うようにします.すでに実行に問題ないことが確認されている関数を使うのであれば,その内部の動作までは監視する必要はありません.

デバッグ作業のある時点で,実行停止中に,次に実行する関数の内部まで監視する必要があるか,あるいは単に一つのコマンドのように実行させるかを選択できなければ,関数を積み重ねた処理を検証することは非常に難しくなってしまいます.

このような関数の深さと,実行をどの深さの関数で止めるのかを制御するためにこのような「実行レベル」が作られています.次のような関数の実行を例に見てみましょう.

CaFE (empty) % :a 0 1+;
CaFE (empty) % :b 2+;
CaFE (empty) % :c 3+;
CaFE (empty) % :d 4+;
CaFE (empty) % :abcd a b c d;

この例ではa, b, c, d, abcdの5個の関数が定義されています.ここでデバッガモードをセットしてabcdと,いくつかのコマンドを実行してみます.

CaFE (empty) % debugger_enable 
CaFE (empty) % abcd . print ¥nend¥n
<< in function ""  [prev:"debugger_enable"]  [next:"abcd"]  [d/r:1/1] [faf=0] >>
debug > 

次に実行するコマンド/関数が「abcd」となっている状態でデバッガが待機しています.この状態で実行レベルは「1/1」になっていて,デバッガに指定された実行を停止レベルが1,現在のレベルが1ということを意味しています.この状態で「n」,「return」,「space」のいずれかのキーを押すと,「abcd」はその内部では停止せず実行されてしまいます.abcdが実行された後には,次のトークン「.」を処理する手前で一旦停止します.

<< in function ""  [prev:"+"]  [next:"."]  [d/r:1/1] [faf=0] >>
debug > 

これでは関数abcdの内部の動作を見ることができません.このような時に停止させる深さを一つ進めることでabcdの関数実行の内部を覗くことができるようになります.先ほどの「.」コマンドの実行を待つデバッガの状態から通常状態へ抜けるために,「q」キーを押して実行を破棄,通常のプロンプトに戻りましょう.

メモ : 「q」キーによる実行の破棄は,関数/コマンドの実行を中断し通常のプロンプト状態に戻すだけで,途中まで実行した処理によるスタックや変数,関数定義等の影響はそのまま残されます.

<< in function ""  [prev:"+"]  [next:"."]  [d/r:1/1] [faf=0] >>
debug > error : error condition has been thrown to quit from the debugging mode.

10
CaFE (error) % 

先ほどと同じ処理をデバッガを起動して実行します.

CaFE (empty) % debugger_enable 
CaFE (empty) % abcd . print ¥nend¥n
<< in function ""  [prev:"debugger_enable"]  [next:"abcd"]  [d/r:1/1] [faf=0] >>
debug > 

先の例では「n」,「return」,「space」でステップ実行をさせたところで,その代わりにステップ・イン・コマンドの「i」を押してみます.こうすることで関数を停止させる深さの指定をを一つ進めることになります.この後関数abcdが実行されるわけですが,停止の指定が2,そして実行レベル2であることを認識し,実行は関数に入った所で停止します.
その後ステップ実行を繰り返していくと関数abcdの中で呼び出される関数が実行レベル2で順次実行されていくのが見えます.
関数abcdが終了すると,実行レベル1に戻ってくることが確認できます.

<< in function "abcd"  [prev:"abcd"]  [next:"a"]  [d/r:2/2] [faf=0] >>
debug > 
<< in function "abcd"  [prev:"+"]  [next:"b"]  [d/r:2/2] [faf=0] >>
debug > 
<< in function "abcd"  [prev:"+"]  [next:"c"]  [d/r:2/2] [faf=0] >>
debug > 
<< in function "abcd"  [prev:"+"]  [next:"d"]  [d/r:2/2] [faf=0] >>
debug > 
<< in function ""  [prev:"+"]  [next:"."]  [d/r:1/1] [faf=0] >>
debug > 

関数実行を途中まで監視し,その後その関数の動作を見る必要がなくなった時には「o」キーによるステップ・アウト・コマンドで1段階浅い実行レベルに戻ることができるようになっています.

これから書く






##APPENDIX

###A1. イテレータ

ループ処理の中身をループの外部で定義して使うことができます.
次の関数は文字列中の各文字を数値として操作する関数ですが,その中で使われるループ内の処理を実行時に指定できるようにしてあります.この関数は2つの文字列を取り,1つ目は操作する文字列,2つ目は操作する内容を指定します.

:hal >calculation

strlen >length 
s2c 
<length 
times 
'
	c2i <calculation eval i2c rot
' 

<length 1- 
times glue

;

下はその実行例です.

CaFE (empty) % :hal >calculation strlen >length s2c <length times 'c2i <calculation eval i2c rot' <length 1- times glue;
CaFE (empty) % "IBM"
CaFE ("IBM") % "1 -" hal
CaFE ("HAL") % "1 +" hal
CaFE ("IBM") % 

halの中身を見るとスタック上の最初のデータをcalculationに保存しています.ここにはどのような計算を行うかを指定する文字列が入れられます.その後,その次にスタックに残っている文字列の処理を始めますが,文字列が各文字に分解され処理が行われる際に,calculationの中に保存しておいた文字列を取り出し,その内容に従って操作が行われます.「"IBM" "1 -" hal」とすると,"IBM"の各文字が1文字づつアルファベット順のAの方向にシフトされます.

さらにこれから書くもの






コマンド一覧
大文字,小文字の扱い
割り込み(実行の停止)

グローバル変数とユーザインターフェース実行中の通常変数の違い.
スコープ指定したグローバル変数は関数のなかからのみアクセス可能.

About

An RPN calculator

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages