最新言語「P#」の環境を作る

  • 52
    いいね
  • 0
    コメント

はじめに

P#とは、Microsoftが開発中のプログラミング言語です。

非同期&イベント駆動型という特徴を持ち、非同期処理のテストを容易にすることに重点を置いているようです。
Machine(オートマトン)、State(状態)、Event(状態遷移トリガ)を記述することによりプログラムを構築するのですが、注目すべきはこの言語、文法がC#の拡張であり、.NET Framework上で動作するのです。
つまり、「C#コードはP#コードである」という論理が成り立ちます。
夢がひろがりんぐですね。

環境構築

公式Githubはこちら。

https://github.com/p-org/PSharp

執筆時点でのP#のバージョンは1.2.4です。
公式Githubには色々と文書があるので、環境構築は簡単かなーと思いきや、文書は古い内容なのか、それ通りにやってハマったのでメモします。

PSharpの入手

公式から任意のフォルダにクローンします。

git
> git clone https://github.com/p-org/PSharp.git

これでPSharpフォルダ以下にソリューションが生成されます。

ビルド

公式にはdotnet restoreを使えとか、build.ps1でビルドしろとか書かれていますが、うまく行きませんでした。
しかし、Visual Studio Community 2017 でPSharp.slnを開き、単純にビルドすれば通ります。
これで、PSharp/bin/net46/以下にDLLやコンパイラなどが生成されます。

P#プロジェクトの準備

P#をコンパイルするにはプロジェクトが必要です。
C#コンソールアプリのプロジェクトを作りましょう。
このとき、ターゲットフレームワークは「.NET Framework 4.6」を指定してください。

プロジェクトを作成したら、参照設定が必要です。
PSharp/bin/net46以下のこのDLLを参照してください。

参照の追加
Microsoft.PSharp.dll

そして、以下の2ファイルを用意します。

Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.PSharp;

namespace PSharpSample
{
    class Program
    {
        static void Main(string[] args)
        {
            PSharpRuntime.Create().CreateMachine(typeof(Server));
            Console.ReadLine();
        }
    }
}

Machines.psharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PSharpSample
{
    event Ping; // Client sends this event to the Server
    event Pong; // Server sends this event to the Client
    event Unit; // Event used for local transitions

    // Event used for configuration, can take a payload
    event Config (target: machine);

    machine Server
    {
        machine client;

        start state Init
        {
            entry
            {
                // Instantiates the Client
                this.client = create(Client);
                // Sends event to client to configure it
                send(this.client, Config, this);
                raise(Unit); // Sends an event to itself
            }

            on Unit goto Active; // Performs a state transition
        }

        state Active
        {
            on Ping do async
            {
                // Sends a Pong event to the Client
                Console.WriteLine("Receive Ping");
                await Task.Delay(1000);
                Console.WriteLine("Send Pong");
                send(this.client, Pong);
            }
        }
    }

    machine Client
    {
        machine server;

        start state Init
        {
            on Config do Configure; // Handles the event
            on Unit goto Active; // Performs a state transition
        }

        void Configure()
        {
            // Receives reference to Server
            this.server = (trigger as Config).target;
            raise(Unit); // Sends an event to itself
        }

        state Active
        {
            entry
            {
                Console.WriteLine("Send Ping");
                send(this.server, Ping);
            }

            on Pong do SendPing;
        }

        async Task SendPing()
        {
            // Sends a Ping event to the Server
            Console.WriteLine("Receive Pong");
            await Task.Delay(1000);
            Console.WriteLine("Send Ping");
            send(this.server, Ping);
        }
    }
}

以上の2ファイルが用意できたら、Machines.psharpのプロパティをいじりましょう。

Machines.psharp -> プロパティ -> ビルドアクション -> コンパイル

に設定します。
これをしないと次のコンパイルが通りません。

以上ができたら、Visual Studioのウィンドウを一旦閉じます。

コンパイル

Developer Command Prompt for VS 2017を開いてください。
そうしたら、PSharp/bin/net46へ移動しましょう。
そして、以下のコマンドを実行します。

コンパイル
> PSharpCompiler.exe /s:${SOLUTION_PATH}

上記の${SOLUTION_PATH}には、先程準備したプロジェクトのソリューションを指定してください。
これでコンパイルが通るはずです。
EXEができているので、実行してみてください。

出力
Send Ping
Receive Ping
Send Pong
Receive Pong
Send Ping
Receive Ping
...

Visual Studio上でビルド

Visual Studio上でビルドを行なうには、まずテキストエディタを使ってプロジェクトの.csprojファイルを開きます。
以下のようなXMLファイルですね。

PSharpSample.csproj
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
  ...

これに以下のImportタグを追加します。

PSharpSample.csproj
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  ↓これを追加
  <Import Project="$(PSharpBinaries)\PSharp.targets" />
  <PropertyGroup>
  ...

上記の$(PSharpBinaries)には、PSharp\bin\net46の絶対パスを記述します。

次に、PSharp\bin\net46以下にある、PSharp.targetを編集します。
テキストエディタで開いてください。
以下のようになってると思います。

Psharp.targets
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <AvailableItemName Include="PSharp" />
  </ItemGroup>
  <PropertyGroup>
    <CoreCompileDependsOn>$(CoreCompileDependsOn);GenerateToolOutput</CoreCompileDependsOn>
  </PropertyGroup>
  <UsingTask TaskName="Microsoft.PSharp.Rewriter" AssemblyFile=".\PSharpSyntaxRewriter.exe" />
  ↓これ
  <Target Name="GenerateToolOutput">
    <Rewriter
        InputFiles="@(PSharp)"
        OutputFiles="@(PSharp->'$(IntermediateOutputPath)%(FileName)%(Extension).cs')">
      <Output TaskParameter="OutputFiles" ItemName="Compile" />
    </Rewriter>
  </Target>
</Project>

Targetタグに属性を追加してください。

Psharp.targets
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <AvailableItemName Include="PSharp" />
  </ItemGroup>
  <PropertyGroup>
    <CoreCompileDependsOn>$(CoreCompileDependsOn);GenerateToolOutput</CoreCompileDependsOn>
  </PropertyGroup>
  <UsingTask TaskName="Microsoft.PSharp.Rewriter" AssemblyFile=".\PSharpSyntaxRewriter.exe" />
  ↓こうする
  <Target Name="GenerateToolOutput" BeforeTargets="CoreCompile">
    <Rewriter
        InputFiles="@(PSharp)"
        OutputFiles="@(PSharp->'$(IntermediateOutputPath)%(FileName)%(Extension).cs')">
      <Output TaskParameter="OutputFiles" ItemName="Compile" />
    </Rewriter>
  </Target>
</Project>

これができたら、再度プロジェクトを開いてください。
もう一度ビルドアクションの設定をします。
今度は、

Machines.psharp -> プロパティ -> ビルドアクション -> PSharp

が選択できるようになっているので、これに設定します。
あとはVisual Studioによるビルドが通るようになっているので、開発が容易になります。

おわりに

公式サイトには、IntelliSenseなどの記述もあるのですが、私の環境ではどうもうまくいきませんでした。

ここで紹介させていただいた内容はすぐに変更される恐れがありますので、ご注意願います。

ともあれ、これでP#の開発が可能になりました。
Git内にはサンプルも含まれていますので、是非試してみてください。