構文

プログラムの形

プログラムはトークンの列であり、それ以上の構造を持たない。実行後にスタックへちょうど 1 個の値が 残らなければならない。0 個なら EmptyExpression、2 個以上なら ExtraOperands になる。

トークン分割

  1. 空白 (char.IsWhiteSpace) は区切り文字であり読み飛ばされる。
  2. ; からその行の末尾 (\n) まではコメント。
  3. それ以外の位置から、空白または ; が現れるまでを 1 トークンとして切り出す。
  4. 切り出した文字列を 3.3 節の順序で分類する。

つまり トークンの区切りは空白 (とコメント) だけである。1+2 は 3 トークンではなく 1+2 という 1 個の不正トークンになる。1;comment1 の直後にコメントが来た扱いになる。

分類の順序

切り出した文字列 t は次の順に判定される。先にマッチしたものが勝つ。

条件 種別
1 t が記号表に完全一致 Symbol
2 t[0] == '#' Swizzle (本体が不正なら InvalidSwizzle)
3 t[0] == '@' Variable (本体が識別子でなければ InvalidToken)
4 t が数値の開始形 数値リテラル (解析失敗なら InvalidNumberLiteral)
5 t が識別子 Identifier
6 それ以外 InvalidToken

記号表: + - * / // % ... > < >= <= == != !

「数値の開始形」とは、先頭が数字または .、あるいは先頭が + / - で 2 文字目が数字または . であること。 したがって -3 は整数リテラル -3 であり、減算演算子ではない。減算は空白で区切られた単独の - である。

数値リテラル

number ::= [ "+" | "-" ] ( digit {digit} [ "." {digit} ] | "." digit {digit} )
           [ ("e"|"E") ["+"|"-"] digit {digit} ]
           [ "f" | "F" ]
  • . e E のいずれかを含むか、末尾に f / F が付いていれば float、そうでなければ int
  • 例: 3 は int、3.0 3f 3e0 は float、.5 は float 0.5、1.5e-3 は float。
  • 16 進数・アンダースコア区切り・NaN / Infinity 表記は受け付けない (INF 定数は存在する)。
  • 解析は CultureInfo.InvariantCulture 固定なので、小数点は常に .

識別子と名前解決

identifier ::= ( letter | "_" ) { letter | digit | "_" }

letterchar.IsLetter 準拠なので ASCII 以外の文字も使える (ホストが宣言する変数名次第)。

識別子トークンは次の順に解決される。この探索順は固定で、後から名前を隠すことはできない

  1. unpack (... と同義)
  2. スタック操作語 dup drop swap over rot
  3. 組み込み定数 (PI など)
  4. 組み込み関数 (sin など)
  5. 見つからなければ UnknownName

変数参照

variable ::= "@" identifier

ホストが名前と型の組で宣言した変数だけを参照できる。式の側で変数を定義・代入することはできない。 宣言表にない名前は UnknownVariable。同じ変数を何度参照しても命令は毎回出力される (値はキャッシュされない)。

型と値

表記 次元 バイト数 備考
ZaxValueType.Int int 1 4 32bit 符号付き整数
Float float 1 4 IEEE 754 単精度
Float2 float2 2 8
Float3 float3 3 12
Float4 float4 4 16

intfloatスカラfloat2 から float4ベクトルである。 行列型・真偽型・文字列型はない。真偽値は float の 1.0 / 0.0 で表す。

3.8 型昇格

2 項以上の演算では引数型を次の規則で 1 つに昇格する。

左 \ 右 int float floatN
int int float floatN
float float float floatN
floatM floatM floatM M = N なら floatN、そうでなければエラー
  • 次元の異なるベクトル同士は昇格できない (float2float3 の加算は TypeMismatch)。
  • スカラとベクトルの混在はスカラ側が全成分へブロードキャストされる。
  • ベクトルの暗黙の幅拡張 (float3 から float4) は起こらない。

シグネチャ規則

すべての演算子・関数は次の 6 クラスのいずれかに属し、引数型と結果型の推論規則が決まる。 4 章・5 章の表の「クラス」列はこれを指す。

クラス 引数の要求 実引数型 結果型
Ei (elementwise, int 保存) 昇格可能であること 昇格結果 昇格結果
Ef (elementwise, float 化) 昇格可能であること 昇格結果を float 化 同左
Io (整数専用) 全引数が厳密に int int int
Rs (スカラへ縮約) 昇格可能であること 昇格結果を float 化 float
F3 (float3 専用) 全引数が厳密に float3 float3 float3
Cn (構築) 全引数がスカラ float アリティ次元のベクトル

「float 化」は intfloat に置き換える操作であり、ベクトル型はそのまま。

int が保たれるのは Ei クラスに int だけを与えたときのみである。2 3 + は int 5、 2 3.0 + は float 5.0、2 3 / は float 0.666… になる (/ は Ef のため)。

結果型の強制

ホストは期待する結果型を指定できる。式の生成型がそれと異なる場合、

  • int から float への暗黙変換のみが許され、末尾に Convert 命令が追加される。
  • それ以外は ResultTypeMismatchfloat3 を要求する箇所に float4 を書くことはできない。

期待型が指定されない場合は生成型がそのまま結果型になる。

言語側に float から int への変換手段はない。int 値は int リテラル、int 型のホスト変数、 および Ei / Io 演算の結果としてのみ生じる。

3.11 文法まとめ

program    ::= { token }                       (* 空白区切り。実行後に値がちょうど 1 個残ること *)

token      ::= symbol | swizzle | variable | number | identifier

symbol     ::= "+" | "-" | "*" | "/" | "//" | "%" | "..."
             | ">" | "<" | ">=" | "<=" | "==" | "!=" | "!"

swizzle    ::= "#" component_seq
component_seq ::= xyzw{1,4} | rgba{1,4} | uvst{1,4}   (* セットの混在は不可 *)

variable   ::= "@" identifier
identifier ::= ( letter | "_" ) { letter | digit | "_" }

number     ::= [ "+" | "-" ] ( digit {digit} [ "." {digit} ] | "." digit {digit} )
               [ ("e" | "E") [ "+" | "-" ] digit {digit} ] [ "f" | "F" ]

comment    ::= ";" { newline 以外の文字 }

字句上の落とし穴

  • 空白必須: 1 2+ は不正トークン。すべてのトークンを空白で区切る。
  • -3 は負のリテラル: 5 -3 - は 5 と -3 を push してから減算するので 8 になる。 減算のつもりなら 5 3 -。同様に +5 はリテラル 5 であり加算ではない。
  • 名前は隠せない: ホストが dupPI という変数を宣言しても、@ 付きでしか参照されないため 衝突はしないが、識別子側の意味を変えることもできない。