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 |