| Specript Language Reference rev.0.7.0 | www.specript.org |
Specriptでは、言語組み込みのタイプ(integer、decimal、real、boolean、string、 date、timestamp、record、list、map)をベースとして、それらから派生させて新たな タイプをユーザー定義することができます。また、定義済みのユーザー定義タイプを ベースとして、さらに派生させたタイプをユーザー定義することも可能です。このように 新たなタイプを派生的にユーザー定義することを「spec定義」と称します。
なお、Specriptでは、用語「spec」を、言語組み込みのタイプとユーザー定義された タイプの両者を総称するものとして用いています。
spec定義は、
spec名 : 定義するspecの名称 specベース部/ベースspec : 定義するspecがベースとするspec(言語組み込みのタイプ、または他の定義済みspecの名称) spec派生部 : ベースとするspecから派生的に定義する内容
の3点から成ります。
「specベース部(もしくは、「ベースspec」)」には、プリミティブタイプを含む
言語組み込みのタイプのいずれか、もしくは、他の定義済みspecを指定できます。
ベースspecの記述は省略することができます。その場合"record"を
指定したものと認識されます。
3点目の「spec派生部」に定義できる内容は大きく二つあります。一つは要素定義、 もう一つは制約定義です。要素定義、制約定義のそれぞれで、要素property定義、 要素function定義、制約property定義、制約function定義が可能です。
public" | "private" )?
"spec"
<spec名>
":" ( <specベース部> )?
"{" <spec派生部> "}"
property"
<property名>
( ":" ( "not" "null" )? <propertyのspec> )?
( "=" <初期化式> )?
( "#" <制約違反式> )?
";"
function"
<function名>
( "(" <引数定義リスト> ")" )?
( ":" <functionの返り値のspec> )?
"=" ( <ローカルproperty定義リスト> )? <本体式>
";"
constraint"
<制約property定義>
constraint"
<制約function定義>
constraint" "{"
(
<制約property定義> |
<制約function定義>
)*
"}"
property"
<property名>
( ":" <propertyのspec> )?
( "=" <初期化式> )?
( "#" <制約違反式> )?
";"
function"
<function名>
( ":" "boolean" )?
"=" ( <ローカルproperty定義リスト> )? <本体式>
(
( "#" <制約違反式> ) |
( "##" <制約違反式参照名> )
)?
";"
// 要素propertyがあります。
spec 期間仕様 : record {
property 開始日:date;
property 終了日:date;
}
// 要素propertyと要素functionがあります。
spec 名簿仕様 : { // ベースspecが省略されているので"record"と認識されます。
property 名前:string;
property 誕生日:date;
function 年齢:integer =
((Today.Month < 誕生日.Month) || (Today.Month == 誕生日.Month && Today.Day < 誕生日.Day))
? Today.Year - 誕生日.Year - 1
: Today.Year - 誕生日.Year
;
}
// 要素functionがあります。
spec 郵便番号仕様 : string {
function 前3桁:string = this.substring(0, 3);
function 後4桁:string = this.substring(3);
}
spec 期間仕様 : record {
property 開始日:date;
property 終了日:date;
constraint function 開始日は終了日前 =
(開始日 != null && 終了日 != null) ? 開始日 < 終了日 : true;
}
spec 郵便番号仕様 : string {
function 前3桁:string = this.substring(0, 3);
function 後4桁:string = this.substring(3);
constraint function 長さ7文字 = this.length == 7;
constraint function 数字のみ = this =~ "[0-9]*";
}
spec 金額仕様 : integer {
constraint property 上限金額:integer;
constraint function 上限金額以内 = 上限金額 != null ? this <= 上限金額 : true;
}
specの要素としてpropertyやfunctionを追加的に定義することができます。これはクラスに インスタンス変数やメソッドを定義することに似ています。
recordから派生して定義されるspecにおいてのみ、spec要素としてproperty定義が可能です。 要素property定義とはrecordの要素を具体的に定めるものです。
要素property定義の書式は、spec要素でない一般のproperty定義と同じです。ただし、 初期化式を省略することができます。
初期化式のない要素propertyは、その要素propertyが定義されるspecを使用した
property定義等において、値が外部的に設定されることを想定したものです。初期化式がなく、
property定義等において値が外部的に指定されることもなかった場合、要素propertyの値は
"null"となります。
初期化式のある要素propertyへは、その要素propertyが定義されるspecを使用した property定義等において、値を新たに設定することはできません。
// ベースspecが省略されているのでspec "S"はrecordからの派生
spec S : {
property e1:integer; // 初期化式がありません。
property e2:integer = 2; // 初期化式があります。
}
// "S"を使用したproperty定義にて、初期化式のない要素property "e1"への値の設定が
// あります。
property p:S = {e1 = 1}; // p == {e1 = 1, e2 = 2} となります。
// "S"を使用したproperty定義にて、初期化式のない要素property "e1"への値の設定が
// ありません。
property q:S = {}; // q == {e1 = null, e2 = 2} となります。
// "S"を使用したproperty定義にて、初期化式のある要素property "e2"への値の設定が
// あるのでエラーとなります。
property r:S = {e1 = 1, e2 = 2}; // コンパイル時エラー
また、初期化式のない要素propertyに対して設定しようとする値に関するspecが、 要素propertyのspecに対して互換性がないとき、コンパイル時エラーまたは実行時エラーと なります。
// spec "S"はrecordからの派生
spec S : {
property e1:integer; // 初期化式がありません。
property e2:integer = 2; // 初期化式があります。
}
// "S"を使用したproperty定義にて、要素property "e1"へ設定される値のspecが定義と
// 合致しません。
property p:S = {e1 = "a"}; // コンパイル時エラー
定義されたspecに存在しない要素propertyを設定しようとすると、コンパイル時エラーまたは 実行時エラーとなります。
// spec "S"はrecordからの派生
spec S : {
property e1:integer; // 初期化式がありません。
property e2:integer = 2; // 初期化式があります。
}
// "S"を使用したproperty定義にて、要素property "e3"は定義に存在しません。
property p:S = {e3 = 3}; // コンパイル時エラー
要素propertyの初期化式には、一般のproperty定義同様、任意の式を記述できます。
要素propertyのspecは、一般のproperty定義同様、省略することができます。この場合、 要素propertyのspecは暗黙に初期化式の評価結果が表すspecであるとされます。
要素functionは、プリミティブタイプを含む全てのタイプから派生して定義されるspecに おいて定義可能です。要素functionはクラスのメソッドとほぼ同じ位置付けです。
// integerから派生定義したspec
spec X : integer {
function distance(x:integer):integer = abs(x - this);
}
// その使用
property p:X = 8;
property q:integer = p.distance(10); // q == 2 となります。
// stringから派生定義したspec
spec 郵便番号仕様 : string {
function 前3桁:string = this.substring(0, 3);
function 後4桁:string = this.substring(3);
}
// その使用
property zip:郵便番号仕様 = "1234567";
property zip3:string = zip.前3桁; // zip3 == "123" となります。
property zip4:string = zip.後4桁; // zip4 == "4567" となります。
// recordから派生定義したspec
spec 名簿仕様 : record {
property 名前:string;
property 誕生日:date;
function 年齢:integer =
((Today.Month < 誕生日.Month) || (Today.Month == 誕生日.Month && Today.Day < 誕生日.Day))
? Today.Year - 誕生日.Year - 1
: Today.Year - 誕生日.Year
;
}
要素function定義の書式は、spec要素でない一般のfunction定義同様です。引数のspec、 引数のデフォルト値式、引数なしの場合の扱いなど、一般のfunctionと同じです。
要素propertyと引数なしの要素functionでは、見た目や扱いがほとんど同一となります。 ただし、Java側とデータ交換が行われるとき、propertyとして定義された要素はJava側と 交換されますが、functionとして定義された要素はJava側と交換されません。
要素propertyの初期化式内、要素functionの本体式内では、以下のproperty、functionが 参照、呼び出し可能です。
// 要素function "年齢"の本体式では、自spec定義内の他の要素property "誕生日"を
// 参照しています。
spec 名簿仕様 : record {
property 名前:string;
property 誕生日:date;
function 年齢:integer =
((Today.Month < 誕生日.Month) || (Today.Month == 誕生日.Month && Today.Day < 誕生日.Day))
? Today.Year - 誕生日.Year - 1
: Today.Year - 誕生日.Year
;
}
[X.spec]namespace A; property p:string = "xyz";[Y.spec]namespace B; public function f(a:string):string = ...;[Z.spec]namespace A::N; using A; using B; // 要素property "e"の初期化式内では、namespace "A"に定義されるproperty "p"を参照し、 // namespace "B"に定義されるfunction "f"を呼び出しています。 spec S : { property e:string = f(p); }
要素propertyの初期化式内、要素functionの本体式内では、自身を指し示す予約語"this"を
使用できます。
// 自spec定義内の他の要素propertyや要素functionを指し示すとき、"this"に対する
// 要素アクセスとして記述できます。(この場合の"this"は省略可能です。)
spec 名簿仕様 : record {
property 名前:string;
property 誕生日:date;
function 年齢:integer =
((Today.Month < this.誕生日.Month) || (Today.Month == this.誕生日.Month && Today.Day < this.誕生日.Day))
? Today.Year - this.誕生日.Year - 1
: Today.Year - this.誕生日.Year
;
}
// プリミティブタイプから派生定義したspecでは、"this"はそれ自身の値をも表します。
spec 郵便番号仕様 : string {
function 前3桁:string = this.substring(0, 3);
function 後4桁:string = this.substring(3);
}
自spec定義内の他の要素propertyや要素functionの名称が、spec要素でないpropertyや functionと同一だった場合、要素propertyや要素functionの参照、呼び出しが優先されます。
namespace A;
property p = ...;
function f(a:string):string = ...;
// 要素function "e"の本体式で参照、呼び出ししている"f"と"p"は、
// いずれも自spec定義内の要素である"f"や"p"を参照、呼び出しすることとなります。
spec S : {
property p:string;
function f(a:string):string = ...;
function e:string = f(p);
}
上記例において、namespace "A"に定義されるproperty、functionの参照、呼出しを 行いたい場合は、下記のようにnamespace付き‘フルネーム’とすることで可能となります。
namespace A;
property p = ...;
function f(a:string):string = ...;
spec S : {
property p:string;
function f(a:string):string = ...;
function e:string = A::f(A::p);
}
制約とは、データのとりうる値の範囲について制限、条件を加えるものです。例えば、 stringのデータに対して最大長や受け入れ可能な文字種を定めたり、integerのデータに 対して最小値、最大値を定めたり、recordのデータに対して各要素間で値が充足すべき条件を 定めたりすることができます。
制約functionには、このような充足すべき条件を表す式を記述します。制約functionは、
「引数なしでbooleanを返すfunction」として定義します。制約functionが"true"を
返したときその制約functionが表す制約を満たしたと理解されます。"false"を
返したときは制約を満たしていないと解釈されます。
制約functionの返り値のspecは常に"boolean"でなければなりません。この
返り値のspecは、明記しても省略しても構いません。省略した場合でも、本体式の表すspecは
"boolean"でなければなりません。
制約function定義の本体式では、自spec定義内の要素propertyや要素functionを参照、 呼び出ししつつ、その制約functionが表すべき固有の条件判断式を構築します。
// 制約function "長さ7文字"と"数字のみ"が定義されています。
// spec "郵便番号仕様"であるデータは、これらの制約functionが"true"を返すような
// 値のみ取ることができます。
spec 郵便番号仕様 : string {
function 前3桁:string = this.substring(0, 3);
function 後4桁:string = this.substring(3);
// "this"の長さが"7"であると"true"を返すような式が制約functionの本体式と
// なっています。
constraint function 長さ7文字 = this.length == 7;
// "this"が正規表現"[0-9]*"にマッチすると"true"を返すような式が制約functionの
// 本体式となっています。
constraint function 数字のみ = this =~ "[0-9]*";
}
// 制約function "開始日は終了日前"が定義されています。
// spec "期間仕様"であるデータは、この制約functionが"true"を返すような値のみ
// 取ることができます。
spec 期間仕様 : record {
property 開始日:date;
property 終了日:date;
// "開始日"、"終了日"がともに"null"でないとき、"開始日"が"終了日"より
// 小さい場合に限り"true"を返すような式が制約functionの本体式となっています。
constraint function 開始日は終了日前 =
(開始日 != null && 終了日 != null) ? 開始日 < 終了日 : true;
}
実行時、定義された制約functionを満たさない値が設定された場合、「制約違反」が 発生します。全ての定義された制約functionを満たした場合のみ「制約違反」が発生しません。
制約functionの本体式内では、要素function同様、以下のproperty、functionが参照、 呼び出し可能です。
ただし、他の制約functionを呼び出すことはできません。
制約functionでは、データの充足すべき条件を常に式として記述しなければなりません。 しかし、条件式は同一だがその条件式に対するパラメータ値だけが異なるというケースが ままあります。
// 制約functionで値の上限値を定めています。
spec 金額仕様(上限10万) : integer {
constraint function 上限金額以内 = this <= 100000;
}
// 別の上限値を定めるとき、別途specを定義し、異なる上限値による制約functionを
// 定義しています。
spec 金額仕様(上限100万) : integer {
constraint function 上限金額以内 = this <= 1000000;
}
上記例のような場合、制約propertyを用いることで、spec定義を一本化することができます。
spec 金額仕様 : integer {
// 制約propertyを定義します。
constraint property 上限金額:integer;
// 制約functionの本体式は、制約property "上限金額"を参照するかたちになっています。
constraint function 上限金額以内 = 上限金額 != null ? this <= 上限金額 : true;
}
// "金額仕様"を派生した匿名specで、制約propertyに初期化式を追加しています。
property 金額(上限10万):金額仕様 {
constraint property 上限金額:integer = 100000;
} = ... ;
//
// "金額仕様"を派生した匿名specで、制約propertyに異なる初期化式を追加しています。
property 金額(上限100万):金額仕様 {
constraint property 上限金額:integer = 1000000;
} = ... ;
制約propertyはただ定義されるだけで、それ自体に機能はありません。制約propertyと ペアで、その制約propertyを参照するような条件式を本体式として持つ制約functionを 定義します。派生specにおいて制約propertyに初期化式を付与することで、実質的に 制約functionへパラメータを引き渡すことができるようになります。
制約propertyを用いる場合も、「制約違反」が発生するのは、あくまでもその制約propertyを 参照するような条件式を本体式として持つ制約functionにおいてです。
要素propertyを用いると、そのspecを使用したproperty定義等において値が外部的に設定 される可能性があります。制約propertyへは派生specで初期化式を追加する以外に値を 設定することがでず、specを使用したproperty定義等において外部的に設定されるおそれが ありません。
spec定義では、言語組み込みのタイプをベースとする以外に、他の定義済みspecをベースとして、 さらに派生定義することができます。
// "B"はrecordをベースとするspecです。
spec B : {
property e1:integer;
property e2:integer = 20;
function f1(x:integer):integer = e2 * x;
constraint function c1 = e1 < e2;
}
// "D"は"B"をベースとするspecです。
// 新たな要素property、要素function、制約functionを追加的に定義しています。
spec D : B {
property e3:integer;
function f2:integer = e1 + e3;
constraint function c2 = e2 < e3;
}
上記例において、spec "D"には、下記の要素、制約が全て存在する
こととなります。
property e1:integer;property e2:integer = 20;property e3:integer;function f1(x:integer):integer = e2 * x;function f2:integer = e1 + e3;constraint function c1 = e1 < e2;constraint function c2 = e2 < e3;
特に、spec "D"では、ベースspecで定義される制約function "c1"と
spec "D"の定義で新たに定義された制約function "c2"の
両方を満たす必要があります。
specの派生定義では下記のことが可能です。
not null"制約を追加する。一方、下記を行うことはできません。
not null"制約を除去する。
// "B"はrecordをベースとするspecです。
spec B : {
property e1:integer;
property e2:integer = 20;
property e3:string;
function f1(x:integer):integer = e2 * x;
constraint property upper_limit:integer;
constraint property lower_limit:integer = 0;
constraint function c1 = upper_limit != null ? e1 <= upper_limit : true;
constraint function c2 = lower_limit != null ? e1 >= lower_limit : true;
}
// "D"は"B"をベースとするspecです。
spec D : B {
// 初期化式の無い要素propertyに初期化式を追加することができます。
property e1:integer = 10;
// 初期化式のある要素propertyを、別の初期化式で再定義することはできません。
property e2:integer = 200; // コンパイル時エラー
// 要素functionの本体式を再定義することはできません。
function f1(x:integer):integer = e2 / x; // コンパイル時エラー
// 初期化式の無い制約propertyに初期化式を追加することができます。
constraint property upper_limit:integer = 100;
// 初期化式のある制約propertyを、別の初期化式で再定義することはできません。
constraint property lower_limit:integer = -100; // コンパイル時エラー
// 制約functionの本体式を再定義することはできません。
constraint function c1 = upper_limit != null ? e2 <= upper_limit : true; // コンパイル時エラー
// 初期化式の無い要素propertyのspecを派生specへ再定義することができます。
// "C"はstringをベースとして別途定義されたspecです。
property e3:C;
}
// spec "C"の定義です。制約functionが追加されています。
spec C : string {
constraint function c = this =~ "[0-9]*";
}
下記に示す、
など、specを指定する箇所では、言語組み込みのタイプおよびユーザー定義された任意の specを指定できますが、さらに‘ad-hoc’に構成される「匿名spec」を指定することが できます。
匿名specとは、spec定義におけるspecベース部とspec派生部を、spec指定する箇所に 直接記述する記法です。
匿名specのspec派生部においては、明示的なspec定義におけるspec派生部にて定義可能な 内容が全て定義可能です。
// これはspecが"string"であるproperty "p"の定義です。
property p:string = "123";
// これは、specが「制約function "c"を持つ、stringをベースとした匿名の派生spec」
// であるproperty "q"の定義です。
property q:string {
constraint function c = this ~= "[0-9]*";
} = "123";
// recordをベースとした匿名specも可能です。
property r:{
property e1:integer;
property e2:integer;
constraint function c = (e1 != null && e2 != null) ? e1 < e2 : true;
} = { e1 = 3, e2 = 4 };
匿名specは、匿名specを構成して指定した箇所でのみ使用できるspecとなります。 その匿名specを他のproperty、引数、返り値に対して使用することはできません。
匿名specは、specの要素propertyや要素functionの定義においても使用することができます。
// spec定義時、要素の定義で匿名specを使用することができます。
spec S : {
// 要素property "e1"のspecが、stringから派生した匿名specとなっています。
property e1:string {
constraint function c = this ~= "[0-9]*";
};
}
// 匿名specの要素の定義で匿名specを使用することができます。
property p:{
// 要素property "e1"のspecが、stringから派生した匿名specとなっています。
property e1:string {
constraint function c = this ~= "[0-9]*";
};
property e2:string;
} = { e1 = "1", e2 = "a" };
ネストした匿名spec内では、定義上の親specの要素を参照することができます。
// property "p"のspecは匿名specです。
property p:{
// 要素property "e1"が定義されます。
property e1:integer;
// 要素property "e2"が定義されます。"e2"はintegerから派生した匿名specと
// なっています。
property e2:integer {
// "e2"のintegerから派生した匿名specには要素function "f"が定義され、
// その本体式ではproperty "p"の匿名specの要素property "e1"を参照しています。
function f(a) = a * e1;
};
}
specの互換性とは、ある値もしくは評価結果が、指定されるspecに対して適合するか否かを 示す概念です。例えば、property定義における初期化式の評価結果が、指定されるspecに 対して互換性が‘無い’場合、そのproperty定義はコンパイル時エラーまたは実行時エラーと なります。
また、is演算子では、互換性の有無をエラーを起こさずにチェックすることができます。
以下に、「互換性が無い」評価される条件を一覧します。一覧のいずれか一つを満たすとき、 specの互換性が無いと評価されます。
条件 条件を満たさなかったときに発生する実行時エラー (数値系タイプ同士の場合を除いて、)ルートタイプが異なる T.B.D. specに定義される制約を満たさない T.B.D. recordの派生specにおいて、定義に存在しない要素propertyがある T.B.D. recordの派生specにおいて、要素propertyがこの一覧に示される条件に 引っかかる T.B.D.
| (c)2007-2008, Specript Development Team | Last Updated: 2008-03-06 |