構文

プログラムの形

プログラムはトークンの列であり、それ以上の構造を持たない。実行後にスタックへ残った値がそのまま結果になる。 通常ホストは結果を 1 個だけ期待するので、0 個なら EmptyExpression、2 個以上なら ResultCountMismatch になる。 ホストが複数の結果を期待している場合はその個数だけ残す (結果型の強制)。

トークン分割

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

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

分類の順序

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

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

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

「数値の開始形」とは、先頭が数字または .、あるいは先頭が + / - で 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 | "_" }

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

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

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

名前は大文字小文字を区別する。行列・クォータニオン関数 (Mmul Qmul など) は先頭が大文字であり、 mmul は 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

int と float がスカラ、float2 から float4 がベクトルである。 行列型・真偽型・文字列型はない。真偽値は float の 1.0 / 0.0 で表す。 行列は連続する N 個の floatN を、クォータニオンは float4 をそれぞれ行列・クォータニオンと見なして扱う (行列・クォータニオン)。

3.8 型昇格

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

左 \ 右 int float floatN
int int float floatN
float float float floatN
floatM floatM floatM M = N なら floatN、そうでなければエラー
  • 次元の異なるベクトル同士は昇格できない (float2 と float3 の加算は 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 化」は int を float に置き換える操作であり、ベクトル型はそのまま。

クォータニオン関数 (Q…) は上記のクラスに属さず、引数型が個別に固定されている (float の位置には int も渡せる)。 行列関数 (M…) はアリティすら固定ではなく、スタック先頭の型から行列の次元を推論する。 いずれも行列・クォータニオンを参照。

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

結果型の強制

ホストは期待する結果型をリストで指定できる (通常は長さ 1)。実行後にスタックへ残った値の個数が リストの長さと一致しなければ ResultCountMismatch。個数が合っていれば各スロットについて、

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

期待型が指定されない場合は、残った値の個数・型がそのまま結果になる (このとき 2 個以上残してもエラーにならない)。 行列を結果として返すには、ホストが N 個の floatN を期待していなければならない。

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

3.11 文法まとめ

program    ::= { token }                       (* 空白区切り。実行後にホストが期待する個数 (通常 1) の値が残ること *)

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

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

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 であり加算ではない。
  • 名前は隠せない: ホストが dup や PI という変数を宣言しても、@ 付きでしか参照されないため 衝突はしないが、識別子側の意味を変えることもできない。