slap チュートリアル

home > チュートリアル

Hello, world!

お約束なのでいちおうやっておきましょう。 以下の内容を適当なファイル、とりあえずhello.slpとしますが、それに保存してください。 いちいちコピペするのがめんどくさい人は、たぶん配布物のsampleディレクトリ内に同じものが入ってるんじゃないかと思います。

$puts("Hello, world!");

このスクリプトを実行するには、コマンドプロンプト(シェル、とか読み替えてもらってもかまいません)で以下のように入力します。

C:\>slap hello.slp
Hello, world!

ま、雰囲気でわかりますよね。 $putsの先頭の$はグローバル変数/メソッドを意味します。 メソッド呼び出しの()は省略できません。ごめん。 行末を意味する;も省略できません。 ゴルフにはあんまり向いてなさそうな文法です。

すぐにみんな試そうとすると思うので最初に釘を刺しておきますが、slapはスクリプトがUTF-8で書かれていることを仮定します。鬼車使っておいて、しかもWindowsで使うことを強要しておいてそれかよ! とか自分でも思いますが、だってめんどくさいんだもんm17n。 そして、UTF-8で書かれていることを仮定しているくせに、文字列オブジェクトは文字単位でなくバイト単位で機能し、入出力に関しても特にエンコーディングを意識せずに単にバイト列として扱います。 将来はこのあたりは変更されることでしょう。

ここまでのまとめ

簡易grep

続いて、簡易grepでも作ってみましょう。確か昔のrubyのサイトにあったチュートリアルもそうだったし。 というわけで、以下の内容を適当なファイルに保存してください。今回はgrep.slpとしておきます。例によって配布物のsampleディレクトリ内に同じものがたぶん入ってます。

#!slap
/* tiny grep */

var pattern = Regexp.new(ARGV.shift());

ARGV.each() @(file) {
  File.open(file, "r") @(io) {
    var i = 0;
    io.each_line() @(line) {
      i++;
      if (pattern =~ line) {
        $puts(file + ":" + i.to_s() + ": " + line);
      };
    };
  };
};

たぶんrubyがわかる人なら何をやってるのかわかると思います。 え、ruby知らないって? ごめん、諦めて。

1行目はいつものアレですが、slapのターゲットはWindowsなので単なる飾りです。 で、2行目を見るとわかりますが、#による行末までのコメントの他に、/**/のC風複数行コメントが使えます。 ここだけはrubyに勝ったね! なお、今回は出てきていませんが正規表現リテラルはruby同様に/.../なので、正規表現リテラルの先頭に*を書くとコメント開始になります。 でも、正しい正規表現ならありえないからいいよね!

空行を挟んで4行目(行番号がないからわかりにくいっすね)、varはローカル変数を宣言する命令です。 slapではローカル変数は宣言しないと使えません。故意なのでそういうものだと納得してください。 で、宣言されたローカル変数patternに代入されるのが、Regexpのメソッドnewの戻り値なわけですが、Regexpのように大文字で始まる識別子は定数を意味します。 定数Regexpは組み込みの定数で、言わなくてもわかると思いますが正規表現クラスです。 そのメソッドnewは言うまでもなくインスタンス生成です。 C++やその影響を受けた言語と違って、new演算子はslapには存在しません。

Regexp.new()メソッドは引数として文字列を受け取り、それを正規表現に内部でコンパイルします。 というわけで検索パターンとなる文字列を渡したいわけですが、ここでは定数ARGVのメソッドshiftの戻り値を渡しています。 ARGVはやっぱり組み込みの定数で、コマンドライン引数(ただしslap自体が処理した引数は含まない)が配列として格納されています。 配列のメソッドshiftは配列から先頭の要素を取り除き、その取り除かれた要素を返します。 というわけで、コマンドライン引数の先頭のものがRegexp.new()に渡されることになります。

続いて、ARGVの残りの要素をeachメソッドで一つずつ取り出します。 これ読んでるのはrubyを知ってる人に決まってるので、自明なことは省略すると、rubyのブロック引数、つまりdo |...| ... endにおおよそ相当するのが@(...) { ... };の部分です。 ブロック引数を与えた場合も行末に;が必要なのはなんか残念な感じがしますが、たぶん深い理由があるので許してください。

あとはだいたい見ての通りです。 File.openslap自体がバグってなければたぶんちゃんと自動的にファイルをクローズしてくれます。 ご覧の通り、Rubyと違ってi++があるのでせっかくなので使っています。 ちなみに++iもありますよ。 文字列の結合をちまちま+でやってるのも、文字列埋め込み式(っていうんだっけ?)とかフォーマット指定(rubyの%みたいなの)を実装する気力がなかったせいなので、今回はこれでご勘弁を。

言い忘れたことは……おっと、この例にはif文があったんだった。 例にはないけどelifでもelsifでもelseifでもなくelse ifです。 C言語などと同様に、条件式部の()は必須です。 なお、slapではnull(nilじゃないよ)のみが偽であり、他のすべての値が真です。 trueとかfalseとかいうものも存在しません。

で、このgrep.slpを実行するには、以下のように入力します。

C:\>slap grep.slp "TODO" base/*.c
(結果は省略)

おお、生意気にもコマンドプロンプトでのワイルドカード展開をサポートしているのか! と思う人もいるかもしれませんが、ごめんなさい、Microsoftさんのランタイムにやらせてます。 で、それはともかく、この例だと、baseディレクトリ内の拡張子.cなファイル群の中から、文字列TODOを含む行を探して、ファイル名と行番号付きで表示してくれます。

ここまでのまとめ

おまけ

ふう、いっぱい書いて、いい加減疲れたのでこの辺で。 ……と思ったんですが、クラス定義とメソッド定義の説明くらいは必要か。 あと、プロパティなんてもんも一応あるんですよねえ。 以下に幾つか例を示しておきます。手抜きでごめん。

# クラス定義
class SomeClass : Object { # 親クラス省略時はObjectを継承するのでこれは無駄な例
  # メソッド定義はdef文で。
  # 引数リストが空の時も()は省略できないので注意。
  def foo(a) {
    $p(a);
    $p(this);    # rubyのselfはslapではthis
    return null; # 当然returnはできる。書かなくても最後の式の値が返るが。
                 # で、ご覧の通りnilじゃなくてnull。
  } # def文の {} は ; 不要。ブロック引数とは違うのです。

  # _init メソッドは初期化子(というのかなあ? rubyのinitializeに相当)。
  # なお、当然親クラスにも _init があるわけだが、slapではメソッドの
  # オーバーライド時にはoverride指定が必須となる。
  def override _init() {
    # _で始まる識別子はprivateインスタンス変数/メソッド。
    # ただし、slapの「private」はあんまり「private」じゃない。
    # どっちかっつーと世間の「protected」なので、子クラスからは
    # 親クラスのprivateインスタンス変数/メソッドが普通に見えちゃう。

    _val = null;    # インスタンス変数は代入で宣言。
    this.bar = 1;   # お勧めしないけどpublicなインスタンス変数も作れる。
                    # ただしローカル変数と区別できないのでthis.を付けてね。

    $p(bar);        # 作ってしまえばthis.なしでも参照可能。
  }

  # 以下はプロパティの宣言例。
  # メソッド宣言とだいたい同じだが、getterの宣言はget文、setterの宣言は
  # set文で行い、また引数リストを指定することはできない。
  get val {
    $puts("getter called!");
    _val;           # privateインスタンス変数 _val の値を返す。
                    # この例でのプロパティ名 val と _val との名前の類似は
                    # 単なる慣例なので、もちろん好きな名前をつけていい。
                    # 当然、インスタンス変数なんか一切アクセスしなくても
                    # 問題ない。
  }

  set val {
    $puts("setter called!");
    _val = value;   # valueというローカル変数にsetterの値が入ってくる。
  }
}

var obj;
obj = SomeClass.new();  # => 1
obj.foo("hello!");      # => "hello!" / #<SomeClass:0x....>
$p(obj.val);            # => getter called! / null
obj.val = -1;           # => setter called!
$p(obj.val);            # => getter called! / -1

最後のまとめ