C# WPFで作るJavaScript REPL


# 背景
ChakraCoreという、C#から利用できるJavaScriptエンジンがあります。
WPFでユーザーインターフェースを作って、REPLにしてみましょう。

# 完成イメージ
JavaScriptを入力して`実行(RUN)`ボタンを押すと結果を表示します。
![out.gif](https://qiita-image-store.s3.amazonaws.com/0/15964/4c39fc73-a761-24c6-3647-d30478a83985.gif "out.gif")
[分割代入](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)が動くのが、（今の所）ChakraCoreならではです。
 
# 手順
VisualStudio Community 2015 Update1で新しいWPFプロジェクトを作ります。

## ChakraCoreのdllを作る
[ChakraCoreをビルドする](http://qiita.com/ledsun/items/a3bd7691b86389073c7a)を参考にしてビルドしてください。
[ChakraCoreをC#から呼び出す](http://qiita.com/ledsun/items/f0e28499269c238c62f9)と同様にプロジェクトに追加します。

## NugetでReactivePropertyをインストールする
viewとviewModelをバインドするためにReactivePropertyを使います。
ReactivePropertyを使う理由は[INotifyPropertyChanged実装のありえない面倒くささと、ReactivePropertyの信じられない素晴らしさ](http://qiita.com/ledsun/items/6f4ef754e5ae2507e531)を見てください。
## MainWindowを作成する
コンポーネントを適当に配置します。

```xml
<Window x:Class="REPL.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:REPL"
        mc:Ignorable="d"
        Title="ChakraCore REPL"
        Height="450"
        Width="600" Closing="DisposeChakraRuntime">

    <StackPanel HorizontalAlignment="Left" VerticalAlignment="Top">
        <TextBox
            Height="250" 
            Width="600"
            AcceptsReturn="True"
            TextWrapping="Wrap"
            Text="{Binding Script.Value}"
            VerticalScrollBarVisibility="Auto"
            FontSize="20"
            />
        <Button Click="Button_Click">実行(RUN)</Button>
        <Button Click="ResetContext">初期化(Reset)</Button>
        <TextBlock
            TextWrapping="Wrap"
            Text="{Binding Result.Value}"
            />
    </StackPanel>
</Window>
```

## ViewModelを作る
JavaScript文字列と結果文字列持つビューモデルを作ります。

```csharp
public class ViewModel
{
    public ReactiveProperty<string> Script { get; } = new ReactiveProperty<string>("(()=>{return \'Hello world!\';})()");
    public ReactiveProperty<string> Result { get; } = new ReactiveProperty<string>();
}
```

初期値に`"(()=>{return \'Hello world!\';})()`を入れます。
[公式サンプル](https://github.com/Microsoft/Chakra-Samples/blob/master/ChakraCore%20Samples/Hello%20World/C%23/HelloWorld/HelloWorld.cs#L15)の真似です。

## JavaScriptの実行部
### Hosting
[ChakraCoreをC#から呼び出す](http://qiita.com/ledsun/items/f0e28499269c238c62f9)と同様に[公式サンプル](https://github.com/Microsoft/Chakra-Samples/tree/master/ChakraCore%20Samples/Hello%20World/C%23/HelloWorld)の`Hosting`ディレクトリをコピーします。

### 実行エンジンの初期化
`JavaScriptRuntime.Create`メソッドでランタイムを作ります。
ランタイムの`CreateContext`メソッドで実行コンテキストを作ります。

```csharp
private JavaScriptRuntime runtime;
private ViewModel vm;

public MainWindow()
{
    InitializeComponent();

    vm = new ViewModel();
    DataContext = vm;

    InitChakraRuntime();
    Run(runtime, vm);
}

private void InitChakraRuntime()
{
    runtime = JavaScriptRuntime.Create();
    JavaScriptContext.Current = runtime.CreateContext(); 
}
```

### 実行
事前に設定した`JavaScriptContext`の`RunScript`メソッドに文字列を与えます。
`ConvertToString`メソッドと`ToString`メソッドで結果をC#の文字列に変換します。

```csharp
private static void Run(JavaScriptRuntime runtime, ViewModel vm)
{
    try
    {
        vm.Result.Value = JavaScriptContext.RunScript(vm.Script.Value)
            .ConvertToString()
            .ToString();
    }
    catch (JavaScriptScriptException e)
    {
        vm.Result.Value = e.Error
            .ConvertToString()
            .ToString();
    }
}
```


## イベントハンドラーを作る
ボタンのイベントハンドラーを設定します。

```csharp
private void Button_Click(object sender, RoutedEventArgs e)
{
    Run(runtime, vm);
}
```


## その他の雑用
コンテキストを初期化し直すボタンをつけます。
グローバル変数の定義を消すために使います。

```csharp
private void ResetContext(object sender, RoutedEventArgs e)
{
    JavaScriptContext.Current = runtime.CreateContext();
}
```

Windowを閉じる時にランタイムを消します。

```csharp
private void DisposeChakraRuntime(object sender, System.ComponentModel.CancelEventArgs e)
{
    // Dispose runtime
    JavaScriptContext.Current = JavaScriptContext.Invalid;
    runtime.Dispose();
}
```

# ソースコード
[github](https://github.com/ledsun/ChakraCoreSample/tree/master/REPL)にあります。

# 感想
公式サンプルのHostingの[JavaScriptRuntime.cs](https://github.com/Microsoft/Chakra-Samples/blob/master/ChakraCore%20Samples/Hello%20World/C%23/HelloWorld/Hosting/JavaScriptRuntime.cs)がC#っぽいAPIを提供しています。
これを使うと[公式サンプル](https://github.com/Microsoft/Chakra-Samples/blob/master/ChakraCore%20Samples/Hello%20World/C%23/HelloWorld/HelloWorld.cs)よりスマートに書けて良い感じです。