ChakraCore implement 2
長くなるので分けました。
使用する関数は1つ前の記事を参照と言うことで。
エラーハンドリング
開発で一番大切なところ、エラー表示です。
組み込む以上、nodeJs開発環境みたいな気の利いたデバッガは存在しません。でもせめて、エラー箇所の特定ぐらいの情報は欲しいのですが、残念ながらサンプルにはその破片も書かれておりません。
探したところ、ChakraCoreソリューションにある ch プロジェクトのソースがヒントになります。WScriptJsrt.cpp の PrintException がそのものズバリです。
使用箇所を抜粋すれば
... runScript = ChakraRTInterface::JsRun(scriptSource, sourceContext, fname, JsParseScriptAttributeNone, nullptr /*result*/ ); if(runScript != JsNoError){ WScriptJsrt::PrintException(fileName, runScript); }else{ ...
RunScriptでエラーが出たらそのまま収集を開始しています。特にcontextやruntimeなどの情報は要らないようです。
PrintExceptionの中身はgithubでご覧ください。
一目見てうっ!って感じですが、大まかな流れはシンプルです。
bool WScriptJsrt::PrintException(LPCSTR fileName, JsErrorCode jsErrorCode) { LPCWSTR errorTypeString = ConvertErrorCodeToMessage(jsErrorCode); ChakraRTInterface::JsGetAndClearException(&exception); if (exception != nullptr) { if (jsErrorCode == JsErrorCode::JsErrorScriptCompile || jsErrorCode == JsErrorCode::JsErrorScriptException) { if (jsErrorCode == JsErrorCode::JsErrorScriptCompile) { } else { } } else { fwprintf(stderr, _u("Error : %ls\n"), errorTypeString); } return true; } else { fwprintf(stderr, _u("Error : %ls\n"), errorTypeString); } return false; }
errorによって大きく3つにわかれます。コンパイルエラー、実行時エラー、それ以外のエラー。
詳しく調べられるのは、コンパイルと実行時の2つだけ。その他は基本的に JsErrorCode の列挙名を文字列にして返すだけみたいです。
エラーは JsGetAndClearException で得られる exception が詳しい内容を保持しています。exception もJsValueRefであることから、propertyをもった単なるjavascript上のオブジェクトです。(たぶんjavascript上からも値を取得してゴニョゴニョ出来るでしょう)
と思って JsGetValueType するとJsError型と帰ってきました。一応型としては分かれているみたいです。
Exceptionの中身
エラー情報を取得する jsGetAndClearException には以下の説明が載っています。
JsGetAndClearException 関数 現在のコンテキストのランタイムを例外状態にした例外を返し、そのランタイムの例外状態をリセットします。 現在のコンテキストのランタイムが例外状態でない場合、この API は JsErrorInvalidArgument を返します。ランタイムが無効である場合は、スクリプトが終了したことを示す例外を返しますが、例外はクリアしません (JsEnableRuntimeExecution を使用してランタイムを再度有効化すると、例外はクリアされます)。
プログラムが複雑になってくると JsEnableRuntimeExecution なども使用することになってきそうです。
取得出来る exception にどんな property があるのか知りたかったのですがちょっと自力で調べないといけないようです。
エラー型もオブジェクト型みたいなものなので、普通のオブジェクトと同じように中身を出力してみると。
・JsErrorScriptCompile { message : Expected '(' line : 10 column : 4 length : 2 source : for in c of{ url : sample01.js } ・JsErrorScriptExceprion { message : 'o' is not defined description : 'o' is not defined number : -2146823279 stack : ReferenceError: 'o' is not defined \r\n at callback_test (sample01.js:12:5) }
期待したほど入っていませんでした。ほぼ上記のサンプルで情報は出し切っているみたいです。
エラー出力
exception の内容はスッキリしてるので、ぶっちゃけ、エラー表示はオブジェクト内容をそのまま出力すれば良いだけなような気がしてきました。
というわけで、オブジェクト内容を表示するコードさえ書けば以下のような感じに。
exception の型がJsError型であることに注意してください。
// print object property function PrintObject(obj:JsValueRef; space:string=''):string; function inToString(ref:JsValueRef):string; var t : JsValueType; strref : JsValueRef; begin result := ''; JsGetValueType(ref,t); if t = JsString then begin result := GetString(ref); end else begin if jsConvertValueToString(ref,strref) = JsNoError then result := GetString(strref); end; end; var names : JsValueRef; namecount : integer; ref,idref : JsValueRef; t : JsValueType; i,n : integer; name,value : string; begin // is object or error? JsGetValueType(obj,t); if (t <> JsObject)and(t <> jsError) then begin result := inToString(obj); exit; end; result := ''; JsGetOwnPropertyNames(obj,names); // get propert name array object : ["aaa","bbb","cccc"] ref := GetProperty(names,'length',JsNumber); // get array count if ref = nil then exit; JsNumberToInt(ref,namecount); for i:=0 to namecount-1 do begin ref := GetArray(names,i,JsString); if ref = nil then continue; name := GetString(ref); if JsGetPropertyIdFromName(pwchar_t(name),idref) <> JsNoError then continue; if JsGetProperty(obj,idref,ref) <> JsNoError then continue; value := inToString(ref); value := space + name + ' : ' + value; if result = '' then result := result + value else result := result + _nn + value; end; end; // error handling function ScriptErrorHandling(error:JsErrorCode; const filename:string=''):string; var exception : JsValueRef; begin if error = JsNoError then exit; result := 'filename : ' + filename; // file , url , function name result := result + _nn + 'error : ' + jsErrorToString(error); // JsErrorCode(int) -> string //exception object if jsGetAndClearException(exception) = JsNoError then begin result := result + _nn + PrintObject(exception,''); end; result := '----------' + _nn + result + _nn + '----------'; end;
こんなにスッキリとしたエラーハンドルになります。
ついでに exception.toString() もやってみたんですが、出力される内容が異なりました。
具体的には行数などのnumber型が抜かれちゃったので、やっぱり全propertyを調べ上げて全出力するのが良さそうです。
関数実行のエラーハンドリング
JsRunScript の他にもエラーハンドリングが必要な箇所があります。関数実行 JsCallFunction です。
この2つの関数は引数も似ていれば、大本(c++)の内部も似ています。
なので、luaでもそうでしたが、同じようにエラーハンドリング出来るかな?と思ったら出来ました。
// Run the script. error := JsRunScript(pwchar_t(script), @currentSourceContext, pwchar_t(filename), resultValue); // error result := ScriptErrorHandling(error,filename); ... //call function error := jsCallFunction(ref,PJsValueRef(params),paramcount,retvalue); // error result := ScriptErrorHandling(error,'*function:'+name);
エラーをわざと発生させると
・JsErrorScriptCompile ---------- filename : sample01.js error : SyntaxError message : Expected ';' line : 10 column : 23 length : 9 source : dsnfkjssnfkjsmdljkdss()dfslkdlas; url : sample01.js ---------- ・JsErrorScriptException ---------- filename : *function:callback_test error : ScriptError message : 'o' is not defined description : 'o' is not defined number : -2146823279 stack : ReferenceError: 'o' is not defined at callback_test (sample01.js:14:5) ----------
やったー!
おまけ グローバルオブジェクトの中身
中身です。最後の imp_??? はプログラム側から追加した関数です。
NaN : NaN Infinity : Infinity undefined : undefined eval : function eval() { [native code] } parseInt : function parseInt() { [native code] } parseFloat : function parseFloat() { [native code] } isNaN : function isNaN() { [native code] } isFinite : function isFinite() { [native code] } decodeURI : function decodeURI() { [native code] } decodeURIComponent : function decodeURIComponent() { [native code] } encodeURI : function encodeURI() { [native code] } encodeURIComponent : function encodeURIComponent() { [native code] } escape : function escape() { [native code] } unescape : function unescape() { [native code] } CollectGarbage : function CollectGarbage() { [native code] } Object : function Object() { [native code] } Array : function Array() { [native code] } Boolean : function Boolean() { [native code] } Symbol : function Symbol() { [native code] } Proxy : function Proxy() { [native code] } Reflect : [object Object] Promise : function Promise() { [native code] } Date : function Date() { [native code] } Function : function Function() { [native code] } Math : [object Math] Number : function Number() { [native code] } String : function String() { [native code] } RegExp : function RegExp() { [native code] } ArrayBuffer : function ArrayBuffer() { [native code] } DataView : function DataView() { [native code] } Int8Array : function Int8Array() { [native code] } Uint8Array : function Uint8Array() { [native code] } Uint8ClampedArray : function Uint8ClampedArray() { [native code] } Int16Array : function Int16Array() { [native code] } Uint16Array : function Uint16Array() { [native code] } Int32Array : function Int32Array() { [native code] } Uint32Array : function Uint32Array() { [native code] } Float32Array : function Float32Array() { [native code] } Float64Array : function Float64Array() { [native code] } SharedArrayBuffer : function SharedArrayBuffer() { [native code] } Atomics : [object Atomics] JSON : [object JSON] Intl : Map : function Map() { [native code] } Set : function Set() { [native code] } WeakMap : function WeakMap() { [native code] } WeakSet : function WeakSet() { [native code] } Error : function Error() { [native code] } EvalError : function EvalError() { [native code] } RangeError : function RangeError() { [native code] } ReferenceError : function ReferenceError() { [native code] } SyntaxError : function SyntaxError() { [native code] } TypeError : function TypeError() { [native code] } URIError : function URIError() { [native code] } WebAssembly : [object WebAssembly] imp_print : function () { [native code] } imp_function : function () { [native code] }