プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

GitBucketにorg-mode追加するPlugin作った

 事の発端はこのたけぞうさんのツイートとブログ記事。

 このツイートに反応して、Excelプラグイン使いたいと呟いたついでに、ウッカリこれを呟いたのが間抜けだった。

 

というわけで、作った

 というわけで、本当はVS Codeの俺様用Emacsプラグインを作ろうと思ってたはずだったのに、何故かGitBucketのプラグインを作ってしまっていた。どういうことだってばよ。

gitbucket-org-pandoc-plugin

gitbucket-org-js-plugin

 何故か二つある。どういうことだってばよ。

 片方はpandocを使って変換する。つまり、サーバー側で変換処理を負担する。サーバー側にpandocコマンドが使える様になっていないと使えない点には注意。編集画面作るのが怠かったのでやらなかったが、いつか、pandocのフィルタを管理者が設定できるようにしたい。

 もう一方はmooz様のorg-jsを使って変換する。つまり、変換はブラウザ側が負担する。

 好きな方を選んで下さい。

org-pandoc版

org-js版

なんで二つ作ったの?

 ぶっちゃけ、JavaScript触りたくなかったから。

 あと、プラグインにリソースを含める方法が分からなかったから、pandocなら入れなくて良いなって。

 しかし、実際に作ってみたら、結局CSSを入れる必要があって、四苦八苦して入れた。 だったら、もうorg-js版も作っちゃえと。

 はい、そんな感じです。馬鹿じゃねぇの?タヒねば、と罵られても仕方ないですね。すいませんでした。

 では。

 個人的にはPadToolsプラグインが一番欲しいかな。なおぶろさんに許可とって作ってみようかな。

Eclipse プラグイン開発:TM4Eによるエディタ開発

Eclipse プラグイン開発 目次


 本記事ではEclipse Photon以降のEclipseで、TM4Eを使った場合のシンタックスハイライト、インデントの実装について解説する。 TM4EはEclipse Photonで追加されたRustなどにも使われている。

 本記事ではContents Assist(補完機能)については触れないが、Eclipse Photon以降であれば、LSP4E(Language Server Protocol for Eclipse)を用いるのが良いだろう。

目次

下準備:開発する言語の内容

 今回はサンプルなので、非常に単純な仮想言語HOGEの実装をする。ファイルの拡張子は.hogeである。

 仮想言語HOGEはサンプルなので厳密な定義をする気はないが、こんな感じの構文とする。

    // コメント
    fn name int:value{
      string piyo = "piyopiyo"
      if value == 1 {
        piyo = "moge"
      }else if value == 2{
        piyo = "hoge\!"
      }
      return piyo
    }
    name 10

ざっくりしたルール

  1. {}でブロックを生成する
  2. 複数行にわたる構文はサポートしない
  3. fn [a-z]+[ type:name]*で関数定義
  4. キーワードはif,else,=,,return
  5. 型はintとstringのみ
  6. ""でstring。
  7. 数値は10進数整数のみ
  8. 関数呼び出しは name 引数
  9. if,fnの後は必ず{}によるブロックが必要
  10. //で文末までコメントアウト

TM4E(TextMate support in Eclipse IDE)

 Eclipse Photonからシンタックスハイライト等を実装するのに、TextMate構文(.tmLanguage)を使うことが出来るプラグインTM4Eが、公式で提供される様になった。 .tmLanguageはVisual Studio Codeもサポートしており、汎用的な定義と言える。というか、TM4Eがそもそも、VS Codeのvscode-textmateから作られている。

 Eclipse専用に開発するよりずっとエコで、Eclipseの謎仕様を追いかける精神的苦痛からも解放される。

 現段階ではややバギーではある(執筆時はv0.2)が、おおよそは動くので、TM4Eを使うという選択肢は十分にありだろう。

 デフォルトの状態ではTM4Eはインストールされていないので、開発を開始する前に、インストールをしておく。

 Menu:Help→New Install Software…を開き、公式アップデートサイトを選択し、TextMateをチェックする。検索ボックスにTextMateと打ち込むと、探しやすい。

 選択したら、Nextを押す。次の画面でもNextを押す。

 ライセンスに同意して、インストールを開始する。インストール完了後、再起動をする。

Dependencies

 plugin.xmlを開き、依存関係に、以下を追加する。(※流石にプラグインプロジェクトの作り方等の説明は省略させていただく)

  • org.eclipse.ui
  • org.eclipse.core.runtime
  • org.eclipse.ui.genericeditor
  • org.eclipse.tm4e.core
  • org.eclipse.tm4e.languageconfiguration
  • org.eclipse.tm4e.registory
  • org.eclipse.tm4e.ui

Content Type

 まずは.hogeファイルをEclipseに認識させる為に、Content Typeを定義する必要がある。

 

  1. plugin.xmlのExtensions(拡張)タブを開く
  2. Add(追加)ボタンを押して、org.eclipse.core.contenttype.contentTypesを追加する。
  3. org.eclipse.core.contenttype.contentTypesを右クリックし、ポップアップからNew→content-typeを選択し、content-typeを追加する。
  4. 以下の表の内容を設定する

 

項目 内容 説明
id editor.sample.hoge ID. 全てのプラグインを含めて、ユニークな名前である必要がある。
name HOGE Language File 人が読める名前
base-type org.eclipse.core.runtime.text このファイル情報の継承元。特にない場合はorg.eclipse.core.runtime.textにする
file-extensions hoge ファイル拡張子

 この段階で、このプラグインを導入したEclipseを起動し(図の赤く囲ったボタンで起動可能)、.hogeファイルを適当に生成し、プロパティを見てみよう。 TypeのところがFile (HOGE Language File)となっていて、上記の設定が認識されている。

Generic Editor

 次に、.hogeを開いた時に使用するエディタを設定する。  かつては自分でエディタを作成し、エディタを登録し、そのエディタを使う様に設定するという面倒くさい作業が必要だったが、 TM4Eを使う場合は単にGenericEditorでよい。素晴らしい。

 

  1. plugin.xmlのExtensions(拡張)タブを開く
  2. Add(追加)ボタンを押してorg.eclipse.ui.editorsを追加する。
  3. org.eclipse.ui.editorsを右クリックし、ポップアップからNew→editorContentTypeBindingを選択し、追加する。
  4. 以下の表の内容を設定する。書くのが面倒なら、右のBrowse…から検索すると良い。

 

項目 内容 説明
contentTypeId editor.sample.hoge Content Typeで設定したidを指定する。
editorId org.eclipse.ui.genericeditor.GenericEditor どのエディタで開くか指定する。

.tmLanguageを作成

 .tmLanguageを実際に作成していく。

 最初に、プラグインを配布する際のバイナリに、構文ファイルが追加される様にしておこう。

  1. grammar/hoge.tmLanguage.jsonを作成する
  2. build.propertiesを開く
  3. 「Binary Build」に「grammar」ディレクトリにチェックを入れる

 grammarディレクトリが不都合なら、別なディレクトリ名にしても良い。 なお、今回はjsonにしたが、tmLanguage形式が良い場合やexsdで書きたい場合は、それに変更する。 どの形式が良いのかは正直よく分からないが、jsonにしておけば、何処でも使えそうな雰囲気はある。後ブログでシンタックスハイライトしやすい。

 

 次に、.hogeファイルと、grammar/hoge.tmLanguage.jsonを結びつける設定をする。

  1. plugin.xmlを開き、Extensionsタブに移動する
  2. Addボタンを押し、org.eclipse.tm4e.registry.grammarsを追加する
  3. org.eclipse.tm4e.registry.grammarsを右クリックし、ポップアップメニューからNew→grammarを選択し、追加する。
  4. 同じくポップアップメニューからNew→scopeNameContentTypeを選択し、追加する。
  5. grammarとscopeNameContentTypeを以下の内容で設定する

 

grammar:

項目 内容 説明
scopeName source.hoge スコープ名。source.言語名にしとけばいいのかな
path grammar/hoge.tmLanguage.json 構文ファイルのパス

scopeNameContentType:

項目 内容 説明
scopeName source.hoge grammarのscopeNameを指定する
contentTypeId editor.sample.hoge Content Typeを指定する。

 これでgrammar/hoge.tmLanguage.jsonがhogeファイルを開いた時に取りこまれる様になる。

 最後に、TM4Eが提供するPresentation Reconcilerを.hogeファイルに登録すればお終いだ。 Presentation Reconcilerはドキュメントが変更された時の領域分割や色の設定をするクラスで、EclipseのAPIを叩いてプログラムするとかなり面倒くさい。泣きたくなる。

 

  1. plugin.xmlを開き、Extensionsタブに移動する
  2. Addボタンを押し、org.eclipse.ui.genericeditor.presentationReconcilersを追加する
  3. presentationReconcilerを以下の内容で設定する。

 

項目 内容 説明
class org.eclipse.tm4e.ui.text.TMPresentationReconciler IPresentationReconcilerの実装
contentType editor.sample.hoge Content Type

tmLanguageの書き方はTextMate Language Grammarsを参照せよ。JSONで書く場合は、単にJSONに直すだけである。

{ 
  "scopeName" : "source.hoge",
  "name" : "HOGE Language",
  "fileTypes":[".hoge"],
  "uuid" : "a75f98cf-a3d0-4606-b1e5-1067590c0618",

  "patterns" : [
    {
      "name"  : "string.quoted.double.hoge",
      "begin" : "\"",
      "end"   : "\"",
      "patterns" : [
        { 
          "name" : "constant.character.escape.hoge",
          "match" : "\\."
        }
      ]
    },

    {
      "name" : "storage.type.hoge",
      "match" : "\\b(int|string)\\b"
    },
    {
      "name" : "keyword.control.hoge",
      "match" : "\\b(return|if|else|fn)\\b"
    },
    {
      "name" : "comment.line.double-slash.hoge",
      "begin" : "//",
      "end" : "(?=$)"
    },
    {
      "name": "keyword.operator.hoge",
      "match" : "==|!="
    },
    {
      "name" : "constant.numeric.hoge",
      "match" : "-?[0-9]+"
    }
  ]
}

 UUIDはWELLHATで生成した。

 これでsample.hogeに冒頭のサンプルコードを打ち込んでみよう。(起動時にファイルを開いてしまっている場合は、一旦閉じてから、再度開く)

f:id:nodamushi:20181008200728p:plain

 簡単にシンタックスハイライトをすることができた。

 ところで、たまに何故か「// コメント」が文字化けするという現象にぶち当たった。

f:id:nodamushi:20181008223652p:plain

 これ、毎回じゃなくて、なったりならなかったりだ。しかも、// aコメントとかすると直る。(aを外すと化ける)。 どうやら、フォントのエラーっぽい。Eclipseでコメントだけ文字化けした場合は、フォントを確認しよう。

language-configuration.json

 VS CodeのLanguage Configuration(の一部)を使うことが出来る。 これによって、{を書くと自動的に閉じたり、インデントをさせたりといったことが可能になる。 随分楽だ。

language-configurations/language-configuration.jsonに以下を記述する。

{
   "comments": {
     "lineComment": "//"
   },
   "brackets": [
     ["{", "}"]
   ],
   "autoClosingPairs": [
     { "open": "{", "close": "}", "notIn": ["string"]  },
     { "open": "\"", "close": "\"", "notIn": ["string"] }
   ],
   "surroundingPairs": [
       ["{", "}"],
       ["\"", "\""]
   ]
}

 

 括弧{}のインデントと、{や"を入力すると自動で閉じる、対応するペアを選択する機能が、これだけで実装された。

 後は、これをTM4Eが認識できるようにする。

  1. build.propertiesを開く
  2. Binary Buildのlanguage-configurationsにチェックを入れる
  3. plugin.xmlを開き、Extensionsタブに移動する
  4. Addボタンを押し、org.eclipse.tm4e.languageconfiguration.languageConfigurationsを追加する
  5. languageConfigurationを以下の内容で設定する。

 

項目 内容 説明
path language-configurations/language-configuration.json 設定ファイルのpath
contentTypeId editor.sample.hoge Content Type

 

 たしかにこれで、何か良い感じには動くのだが、割とBuggyである。 例えば、文字列の閉じ"で、次の文字列の開始"が選択されたり、文字列の中でも"や{の自動クローズされたり、 インデントが揃ってないところで{の後に改行すると}の位置が変だったりする。(インデント計算がn*4でしか発生してないっぽい)

f:id:nodamushi:20181008211527p:plain

 まぁ、執筆時はv0.2だし、まだまだこれから。たぶん。

まとめ

 Eclipseのエディタ開発をするのは、かなりの重労働だった。しかし、TM4Eを用いれば、非常に簡単にハイライト機能、インデント機能などを実装することが出来る。  今回は全くJavaによるプログラミングをせずに済んだ。

 LSP4Eと組み合わせれば、VS Code対応すれば、Eclipse対応も大してコストがかからなくなる。いずれ、実装が面倒くさいというEclipseの欠点は、間違いなく解消されるだろう。

 でも、まだまだBuggyである点は注意する必要がある。

参考

Eclipse プラグイン開発:拡張ポイントの実装

Eclipse プラグイン開発 目次


 さて、前回に引き続き、Eclipse Extension  Point(拡張ポイント)の実装を行っていこう。

 ………え?前回が1年半前?

 さぁて、何のことかな?

 あ、今回から環境がJava10、Eclipse Photonになっております。

拡張ポイントを取得する

 プラグイン開発において、拡張ポイントはIExtensionPointによって表現される。 各プラグインがplugin.xmlに記述する拡張ポイントの宣言は、IExtensionで表現される。

インターフェース 対応するファイル 取得方法
IExtensionPoint sample.core.books.exsd IExtensionRegistry.getExtensionPoint
IExtension plugin.xml IExtensionPoint.getExtension

 

 拡張ポイントは、IExtensionRegistryによって管理される。 IExtensionRegistryはPlatformから取得可能だ。

// import org.eclipse.core.runtime.IExtensionRegistry;
IExtensionRegistry registry=Platform.getExtensionRegistry();

 拡張ポイントの取得はIExtensionRegistoryからgetExtensionPoint(String)により取得する。

// import org.eclipse.core.runtime.IExtensionPoint;
IExtensionPoint point = registry.getExtensionPoint("sample.core.books");

 拡張ポイントの宣言情報はIExtensionPointからgetExtensionで取得する。  拡張ポイントの宣言は拡張ポイントそのものと違って、複数回定義されうるものなので、配列で返される。

// import org.eclipse.core.runtime.IExtension;
IExtension[] extensions = point.getExtensions();

要素を処理する

 IExtensionを取得出来たら、ようやくbook要素やauthor要素といった情報を解析する処理が始まる。  XMLを直接解析する必要はなく、DOMの様なデータ構造であるIConfigurationElementを処理する。

 IExtenstion直下のIConfigurationElementを取得するには、getConfigurationElementsを使う。

// import org.eclipse.core.runtime.IConfigurationElement;
IConfigurationElement[] elements = extension.getConfigurationElements();

 IConfigurationElementで扱うデータは、以下の物を理解しておけば十分だ。

データ 取得方法
子要素 getChildren
要素名 getName
属性 getAttribute
属性(kind = java) createExecutableExtension
名前空間 getNamespaceIdentifier

 

 ただし、kindがjavaである属性の取得だけは注意が必要だ。

 というのも、各バンドル(プラグイン)ごとにClassLoaderが異なる為、Class.forNameでクラスを見つけることが出来ないからだ。 createExecutableExtensionはその辺を解決して、インスタンスを生成してくれる。

 (単にクラス名だけが必要なら普通にgetAttributeで取得しても良い。)

リソースの取得

 前回の例で定義している、author.imageなど、リソースを取得する必要がある場合もある。

 リソースは、拡張を定義した各プラグイン内のリソースを取得する必要がある為、Bundleを使って取得する必要がある。(Eclipseプラグイン開発:  バンドルリソース関連を参照)

 BundleはIExtensionやIConfigurationElementのgetNamespaceIdentifierで得られる名前空間名から取得可能だ。

Bundle bundle = Platform.getBundle(extension.getNamespaceIdentifier());

 以下の様な関数を定義しておけば、利用する際に便利だろう。

public static URL findResource(Bundle bundle,String path){
  return path == null || path.isEmpty()?null:FileLocator.find(bundle,new Path(path));
}
public static ImageDescriptor findImage(Bundle bundle,String path){
  return path == null || path.isEmpty()?null:AbstractUIPlugin.imageDescriptorFromPlugin(bundle.getSymbolicName(),path);
}

Expression

 前回、最後に導入したenablementの要素はorg.eclipse.core.expressionsパッケージが処理を行う。

 処理の流れを以下に示す。

  1. IConfigurationElementをExpressionに変換
  2. ExpressionContextを用意し、式を評価(evaluate)し、ExpressionResultを取得
  3. 扱いやすい様にbooleanに変換

 2の処理を行うタイミングは、式の内容による。  一度計算すれば二度と変化しないなら、最初に計算してしまえば良い。  一方で、設定などに依存する場合はその都度計算する必要があるだろう。

boolean enable = true;

IConfigurationElement[] enablement=book.getChildren("enablement");
if(enablement.length==1){
  Expression exp= ExpressionConverter.getDefault().perform(enablement[0]);

  EvaluationContext ctx = new EvaluationContext(null,"");
  ctx.addVariable("hoge","HOGE"); // hogeという変数を定義

  EvaluationResult r = exp.evaluate(ctx);
  enable = r == EvaluationResult.TRUE;
}

拡張ポイントを処理するタイミング

 拡張を定義するplugin.xmlは起動時に読み込まれる為、基本的に変化しない。  従って、Bundleがアクティブになった後、必要になった時に1回だけ読み込む様にするのが良いだろう。

(無論、メモリを圧迫するなどの理由で毎回読み込んでも良いが)

 一応、Bundleが破棄されたら、それらの情報も破棄した方が良い。

 

拡張ポイントの読み込みサンプルプログラム

 Github:extension_point_sampleに前回定義したcore.sample.booksを読み込むサンプルプログラムを用意した。

 このプログラムはsample.internal.core.Extensionsクラスで拡張ポイントを処理してBookクラスにコンバートする。  コンバートした結果はActivatorのstartで取りこまれ、stopで破棄される。

C#ぼっち勉強会 リスト

 Javaを離れて(といいつつ毎週何回かはEclipseが立ち上がる)、思うのだが

.Netのドキュメントが読みづらいことこの上ない。

 なんで、戻り値の型とか、一々関数の説明見に行かないと見えないんだ?  ローカル変数の型はvarで省略出来るとか関係ない。  ドキュメントを読んでるとき、そこからリンクを辿って色々クラスを見ることが結構重要だ。  だが、.Netでそれをするのは非常にしんどい。サイトが開くのが遅い上に、何度もジャンプしないといけないとか勘弁してくれ。

 JavaDocに慣れてるからじゃなくて、圧倒的にJavaDocの方が優れているのは間違いないと思う。  Doxygenで書いた方がいいんじゃないかってレベル。

 とりあえず、ブログじゃねぇんだから3カラムは耐えられない。一先ずは、ユーザースタイルシートで誤魔化したけど。

 というわけで、今日はC#のリスト

C#のリスト

 C#でリストを使うには以下を宣言しておく。

using System.Collections.Generic;

 ソースコードはここ.Net  Frameworkはソースコード探しやすいのに、coreのはないのかなぁ。

IList

 JavaでいうList

JavaのList C#のIList
add(value) Add(value)
clear() Clear()
contains(value) Contains(value)
containsAll(collection)
get(index) list[index]
indexOf(value) IndexOf(value)
insert(index,value) Insert(index,value)
isEmpty()
remove(index) RemoveAt(index)
remove(value) Remove(value)
size() Count
subList(from,to)
set(index,value) list[index]=value
toArray()

 toArrayとかはList<T>とかで実装されてるけど、IListとしてはないっぽい。

 というか、subListがないのね。Spanが使えるかと思ったけど、使えないみたい。

 一応、GetRangeというメソッドがsubListに似た動作をするけど、コピーを作ってしまう

 conatinsAllは実装されていないが、Linqを使えばいけるっぽい

// using System.Linqが必要
var list1 = new List<int>{1,2,3,4,5,6,7,8,9};
var list2 = new List<int>{1,3,5,7,9};
var list3 = new List<int>{1,3,-5,7,9};
Console.WriteLine($"list1 contains list2 ={  !list2.Except(list1).Any()  }");//true
Console.WriteLine($"list1 contains list3 ={  !list3.Except(list1).Any()  }");//false

 相手の要素から自分の要素を全部削除してみて、何も残ってなければcontainsAll………ってメンドウクサ

List<T>

 ソースコードList.cs

 JavaでいうArrayList<T>のこと。

 C++のvectorライクな初期化方法は出来ず、非常になんか、中途半端なコレクション初期化子で初期化する。

// List<int> list{1,2,3}; これ駄目らしい
var list = new List<int>{1,2,3,4};

 ildasm.exeでデアセンブルしたところ、new List<int>(new int[]{1,2,3,4})ではなく、以下の様になる。

var list = new List<int>();
list.Add(1);  list.Add(2);
list.Add(3);  list.Add(4);

 う~ん。まぁ、きっと最適化されて大したことはないのでしょう。

 Javaと違い、Sort関数、Reverse関数などが実装されている。(Java8からはsortは追加されたけど)

LinkedList<T>

 JavaのLinkedList。ソースコードはこれなんかな?

 Addを実装してない為、コレクション初期化子を使うとエラーになる。  ………え、Addがないってどういうこと。AddLastをAddにしちゃあかんかったん?うーん、わからん。

//var list = new LinkedList<int>{1,2,3}; エラー
var list = new LinkedList<int>(new int[]{1,2,3});

 JavaのLinkedListと違い、いわゆるPairデータ(LinkedListNode)を直に弄る。  JavaよりLispに近いデータ操作方法で、抽象化が低い。list.CDDDARとか書かせて欲しい。

var llist = new LinkedList<string>(new string[]{"a","b","c","d"});
var third = llist.First.Next.Next;
Console.WriteLine($"3rd = {third.Value}");

 しかし、なんでこんなインターフェースなんかなぁ。Javaの方が優秀だと思うんだよなぁ。  リンクリスト形式のデータ構造を作る時に便利かなとか思ったけど、ぶっちゃけ、自前のクラス内でリンク構造作った方が楽なんだよな。

 特に実装しなくてもforeachやLinqを使う場合に便利だったりとか、そんな感じ?う~ん…

StackとQueue

 後入れ先出し(LIFO)と先入れ先出し(FIFO)のデータ構造。ソースコードはStackはこれQueueはこれかな?

 一応LinkedListでもStackとQueueは作れるけど、これらのクラスは配列でデータを保持しているから、効率が多分良い。

 当然、Add関数はないのでコレクション初期化子は使えな………って、いい加減にしろよ。C++を見習え!

 Stack

Java Stack C# Stack
size() Count
pop() Pop()
push(item) Push(item)

 Queue

Java Queue C# Queue
size() Count
element() Peek()
peek() 一応tryPeek(out result)がそれっぽい
poll() Dequeue()
add(item) Enqueue(item)

 tryPeekめんどい。

Immutable

 ImmutableなリストはSystem.Collections.Immutableで提供されている。ソースコードはこれかな?

 利用するにはnugetでインストールする必要がある。(以下のコマンド)

dotnet add package System.Collections.Immutable

 リストとしてはImmutableArray構造体とImmutableListクラスがある。

 どちらもコンストラクタはないので、Createを使うか、Builderを使う。

var array = ImmutableArray.Create(1,2,3,4,5);

var builder = ImmutableList.CreateBuilder<string>();
builder.Add("a");builder.Add("b");builder.Add("c"); 
var list =builder.ToImmutableList();                   

 実装を読むと、ImmutableArrayは中身は配列で、ImmutableListは2分木構造になっている模様。なので、ImmutableArrayはアクセスがO(1)、ImmutableListはO(logn)。

 基本的にはImmutableArrayで良いかなって思う。 Immutableなリストって基本的に最初に作ったらアクセスしかないし。foreachとかはメモ化して高速化してるみたいだけど、それでもやっぱりImmutableArrayの方が単純で速いハズ。

 Addなどの操作メソッドに関しては、ImmutableArrayもImmutableListも変更の代わりに、新たなリストを返す。この辺はJavaのPaguroと同じだね。でも、やっぱりサブリストはない。不変なんだから、サブリストあっても良いんじゃないんですかねぇ。

 ところで、注意点はImmutableArrayって 構造体 だよね。

 つまり、値型なので、引数として受け取る場合にはinで受け取らないと無駄なコピーが出来る。 でも、ImmutableArrayの中身って配列(参照型)しかないっぽいから、実はコピーの方が早かったりするんだろうか?その辺りどうなんだろう?

 ImmutableStackとImmutableQueueについては、中身は配列だった。後はだいたい同様。

 次はDictionary(Map)とSetかな。

C#ぼっち勉強会 配列

 はい、随分期間が空きましたが、別に勉強サボってた訳じゃ無くて、単にブログ書くのが面倒くさかっただけです。

 チマチマとC#で作る様になってきてJavaと比較して使いにくい点もままあるけど(特にInterfaceがデフォルト実装持てないのと、String.Spritが使いにくい、インデント幅4は長すぎる)、まぁ、良い言語かなと思います。

 

 文法は、JavaとC++をやっていれば、微妙な違いはありますが、だいたいどちらかの言語にある概念を持ってくれば理解できます。
 というわけで、文法はすっ飛ばして、データ型とその処理について書いていこうと思う。言語を使いこなせるかどうかって、結局、基本データ型(コレクション、ファイル、時間、Thread)の操作を使いこなせるかどうかが結構大きいからね。

 最初は配列からだ。

配列


 配列の型宣言は型[]だけが許されるっぽい。参照型で全てArrayクラスの継承クラス。各要素の初期値は0かnullである。

int[] array = {1,2,3,4};
// int array[] = {1,2,3,4}はだめ


 配列はおおよそ以下の種類がある。

  • 1次元配列
  • 多次元配列
  • ジャグ配列
  • スタック上の配列(Span)

 多次元配列はJavaにはない配列で、中身は1次元配列だけど、添え字で多次元的にアクセス出来る配列。
 以下の例の様に、array2dの添え字 [y,x]は、arrayの [x + y*X] と等価だ。(Xは1次元の長さ) 次元はRankに格納されていて、各次元の長さはGetLength(n)から取得出来る。

int[] array = {1,2,3 ,  4,5,6};
int[,] array2d = {{1,2,3}, {4,5,6}};

Console.Write(
    "[1,2] => array[1*3+2]({0}),  array2d[1,2]=({1})",
   array[2 + 1*3 ], array2d[1,2]);

 なお、多次元配列はforeachの前には無意味だ。

//どちらも同じ。
foreach( var i : array) Console.WriteLine(i);
foreach( var i : array2d)   Console.WriteLine(i);

 
 次に、ジャグ配列はJavaの配列と同じで、配列の要素に別の配列を入れるタイプの配列だ。各要素ごとに長さが違う配列を入れることが出来る。

int[][] array2dJ = new int[2][];
array2dJ[0] = new int[5];
array2dJ[1] = new int[3];

 
 最後に、なんとC#はnewせずに直接スタック上に配列が確保出来る。スゲぇ。Javaにもくれ。

// newではなく、スタック上に確保(解放コスト無し)
Span<int> array = stackalloc int[10];
array[0] = 100;

 なお、C#7.2以上じゃないと駄目なので、program.csprojのPropertyGroup.LangVersionをlatestとかにしておくこと。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <DebugType>Full</DebugType>
    <LangVersion>latest</LangVersion>
  </PropertyGroup>
</Project>

配列の各要素を結合した文字列を作成

 String.Joinで各要素を結合した文字列を作ることが出来る。

int[] array = { 1, 2, 3, 4, 5, 6 };
Console.WriteLine("array={0}",String.Join(",",array));

 ただし、多次元配列だと駄目。面倒くさい。

部分配列(Span)

 Spanを使うことで部分配列を取り出すことが出来る。
 ただし、やっぱり多次元配列だと駄目。(DangerousCreateで出来る?)
 多次元配列いらなくない?[x + y * width]って書くのJavaで慣れてしまったし、正直、デメリットの方が大きいわ。
 Cの多次元配列と同じ構造なら、多次元から1次元にポインタ変換出来そうな気がするんだけど、手段無いのかな?

int[] array = { 1, 2, 3, 4, 5, 6 };
var span = array.AsSpan(2,3);// new Span<int>(array,2,3);
span[0] = 100;
span[1] = 101;
span[2] = 102;
//span[3] = 103; これはエラー
// {1,2,100,101,102,6}
Console.WriteLine("array={0}",String.Join(",",array));


特定のデータで埋める(JavaのArrays.fill)

 JavaのArrays.fillはArray.Clear,Array.Fillそれに当たる。0やnull初期化したいならArray.Clearが引数が無くて楽かも知れない。

int[] array = { 1, 2, 3, 4, 5, 6 };

// {1,0,0,0,5,6}
Array.Clear(array,1,3);
// {1,0,0,100,100,6}
Array.Fill(array,100,3,2);

検索や反転、コピー

 バイナリサーチ、反転、コピーなどのJavaではArraysクラスにstaticメソッドで提供されている関数は、C#ではArrayに一通り揃っている。


 ちなみに、int[]の配列では無く、Spanにしておくと、メソッドとして使えるっぽい。Spanで扱えるところは基本的にSpanで扱った方が楽そうだ。

int[] array={1,2,3,4,5,6};
Span<int> span = array;
Array.Reverse(array);
span.Reverse();

map,filter

 JavaでのmapやfilterはC#ではLINQで実現される。Javaと違って、Streamにしなくてもそのまま使える。

 だが、これがぶっちゃけ言って全然慣れない!!
 なんで、mapがSelectなん?SQLぽさなんていらんわ。Selectって響き的にfilterっぽいじゃん。で、filterがなんでWhereなん?英語的には条件式ってIf,Whenもありうるじゃん。Aggregate?英語音痴の私、この単語知らない。(集計)
 正直この名前付けには不満しかないわ。flattenないし。何でmap→flattenを一つにしてSelectManyにしたわけ?

 逆に、JavaのStreamでは削除されたzipがあるのは素晴らしい。

Java C#
map Select
filter Where
reduce Aggregate
sort OrderBy
sum,max,min,distinct Sum,Max,Min,Distinct
first FirstOrDefault
なし Zip,Concat,Join
flatten SelectMany(_=>_)
toArray ToArray
collect(toList()) ToList
groupingBy GroupBy
parallel AsParallel
IntStream.range Enumerable.Range


 Javaに無いJoinは、最初よく分からんかったけど、こういうことらしい。SQLの内部結合と同じ物を表現する為にあるらしい。SQLとか触ったことしかないからなぁ。

array.Join(array2, func1 , func2 , func3);
// ↓
array.Zip(array2,(x,y) => (x,y))
     .When(x => func1(x.x) == func2(x.y))
     .Select(x => func3(x.x,x.y))


 以上、次はコレクションかな。

C++でタスクトレイに格納するアプリケーションを作る

やりたいこと

 別に立派なGUIが作りたいんじゃない。作りたいのは、コンソールアプリで十分だ。

 しかし、そのコンソールアプリが常に何かを出力し続けていたら?ちょっとモード変更したいとか、そのための入力処理を作るのも億劫だし、そもそも流れ続けるコンソールに入力したくもない。だが、コンソールアプリで十分なのに、画面なんて作りたくない。

 これって、タスクトレイにボタン一つ置ければ、話は簡単じゃないか。

 しかし、そんなことをC++でやろうとすると、途端に非常に面倒くさいことが要求される。Windowsならメッセージを受け取る為の見えないウィンドウを作り、Shell_NotifyIconでNOTIFYICONDATAを登録し…。

 違う。違うんだ、私がやりたいのはそんなことじゃないんだ。ボタン押したら対応するイベントハンドラが発火して、後はチェックボックスぐらいがあれば、それで良いんだ。

 

zserge/tray


 WINAPIを叩いたとしても、まぁ、数百行あればかけるっちゃかけるだろう。
 でも私はWin APIを叩きたくないのだ。何故なら私はGUIを作りたい訳じゃないからだ。そこは本質ではないのだ。情けないことを言えば、ぶっちゃけWinAPIは得意ではないのだ。

 かといって、コンソールアプリを作ってるのに、Qtの出る幕は無い。


 というわけで、今回紹介するzserge/trayを使います。

github.com

 C++でタスクトレイに格納したアプリケーションを、ヘッダファイル1つをインクルードするだけで簡単に作れてしまうライブラリ。リンクは不要。しかもクラスプラットフォームだ。
 私はC++で使ったが、Cでも使える。(というか、C用だから、C++用も欲しいな)

 

サンプルコード

 永遠と「Hoge Hoge」と言い続けるが、タスクトレイから「Piyo Piyo」モードに変更も出来て、終了も出来るプログラムの例を示す。これだけで、もう何となく使うことが出来ると思う。

f:id:nodamushi:20180917020301p:plain

//↓ OS設定。LinuxならTRAY_APPINDICATOR、MacならTRAY_APPKIT。Makefileに書いとくといい。
#define TRAY_WINAPI 1
#include "tray.h"
#include <iostream>
#include <thread>
#include <chrono>

using namespace std;
using namespace std::chrono;

volatile bool piyoMode;    //!< Piyo Piyoとコンソールに出力するモード
volatile bool processDone; //!< 処理完了フラグ
void piyo(tray_menu *);    //!< Piyoモードのトグル
void finish(tray_menu *);  //!< 終了処理
void _main();              //!< Hoge Hogeと出力するメイン処理
tray tray ={               //!< タスクトレイの設定
  (char*)"icon.png", // タスクトレイのアイコン画像。別途用意しておく。
  (tray_menu[]){
    {(char*)"piyoMode",0,0,piyo}, //サブメニューを指定すれば、階層化も可能
    {(char*)"-"},                 // セパレータ
    {(char*)"exit",0,0,finish},   //終了
    {nullptr}                     //最後の要素。必須。
  }
};


int main(int argc, char *argv[]){
  piyoMode=processDone=false;
  thread t(_main);// 表示処理は別スレッド化
  tray_init(&tray); //タスクトレイに登録
  while(tray_loop(1) == 0); //タスクトレイが表示されている間ループ
  t.join();
  return 0;
}

void piyo(tray_menu * item){
  bool b =!item->checked;
  piyoMode = b;
  item->checked = b; //チェックボックスの表示非表示変更
  tray_update(&tray); //更新
}

void finish(tray_menu *){
  processDone=true;
  tray_exit();  // タスクトレイから削除
}

void _main(){
  while(!processDone){
    cout << (piyoMode? "Piyo Piyo" : "Hoge Hoge") << endl; // piyoかhogeを出力
    this_thread::sleep_for(seconds(2)); 
  }
  cout << "Done"<<endl;
}

tray構造体

 タスクトレイの情報。メニューとアイコンを設定する。Windowsでもアイコンってpngでいけるんやね。

static tray tray ={
  (char*)"icon.png",
  (tray_menu[]){
    {nullptr}      //最後の要素。必須。
  }
};

tray_menu構造体

 タスクトレイのメニュー。入れ子構造に出来たり、Check、Disable/Enableも設定出来る。textが"-"だとセパレータになる。

要素 説明
char* text メニュー名。"-"だとセパレータ
int disabled 有効無効の設定
int checked チェックの設定
void (*cb)(struct tray_menu*) コールバック関数。
void *context お好きな様にお使い下さい。
tray_menu* submenu サブメニュー

関数

  • tray_init(tray*): 初期化する。1が返ると失敗
  • tray_update(tray*): disabledとか、checkedとかを変更した時に呼び出して、画面を更新
  • tray_loop(int) : UIのループ。引数はブロッキングするかどうか。tray_exitが呼ばれると1が返る。
  • tray_exit() : UIループを終了する

Utubntu 18.04にTestLinkを入れてみた

 外のサービスはあまり使わせてくれないのに、仮想サーバーはポンポンくれるので、サーバーにTestLinkを入れてみた。お金ないの。

 で、自宅でもTestLink試してみるって事でVirtual Boxに入れてみた。その手順。

Virtual BoxにUbuntu 18.04をインストール

 いつものことなのでチャッチャと。まぁ、初Ubuntu Server 18.04なんだけど。
 もう面倒くさいので全部デフォルトだ。

f:id:nodamushi:20180910231609p:plain:w320

f:id:nodamushi:20180910231628p:plain:w320

f:id:nodamushi:20180910231647p:plain:w320

f:id:nodamushi:20180910231706p:plain:w320

f:id:nodamushi:20180910231725p:plain:w320

f:id:nodamushi:20180910231743p:plain:w320

f:id:nodamushi:20180910231825p:plain:w320

f:id:nodamushi:20180910231843p:plain:w320

f:id:nodamushi:20180910231859p:plain:w320

f:id:nodamushi:20180910231915p:plain:w320

f:id:nodamushi:20180910231947p:plain:w320

 全部「testlink」。セキュリティ?( ゚д゚)

f:id:nodamushi:20180910232033p:plain:w320
f:id:nodamushi:20180910232115p:plain:w320
 どうやらDockerを始めから入れておけるっぽいので、入れておいた。Enterを押すと「*」とチェックが入るので、その後一番下のclose


f:id:nodamushi:20180910232209p:plain:w320
 絶賛放置ング。終わったらリブート。


f:id:nodamushi:20180910232425p:plain:w320
 Enter

f:id:nodamushi:20180910232721p:plain:w320
 リブート終わって、とりあえずログインしたら、もうVirtual Boxは放置。

 Tera Termでログイン。なお、色々ぶつかるので、、仮想サーバーのポートフォワーディングは適当に設定してある。
f:id:nodamushi:20180910233117p:plain:w320
f:id:nodamushi:20180910233054p:plain:w320



Docker-Composeを入れる

 Docker自動で入れてくれるなら、Docker-Composeもデフォルトで入れておいて欲しいな!
 TestLinkのallin-oneをbitnami様々が公開してくれているのですが、docker-composeが必要なので入れます。

 インストールはここを参照して下さい。私は以下(version 1.22.0)で入れました。

sudo curl -L https://github.com/docker/compose/releases/download/1.22.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x  /usr/local/bin/docker-compose

 で、bitnamiのtestlinkを入れる。

mkdir testlink
cd testlink
curl -sSL https://raw.githubusercontent.com/bitnami/bitnami-docker-testlink/master/docker-compose.yml > docker-compose.yml


 docker-compose.ymlの設定を適当にします。私はとりあえず、TESTLINK_USERNAME,TESTLINK_PASSWORD,TESTLINK_LANGUAGEを追加で設定しておいたぐらいです。

 設定したら起動。

sudo docker-compose up -d

 で、ホストOS(Windows)からlocalhostにアクセスしてみる。(仮想OSの設定でポートフォワーディングしてあるので、localhost:2080)

f:id:nodamushi:20180911003953p:plain

 わーい、クッソ楽( ゚д゚)

 ちなみに、私が与えられた仮想サーバーはCPUが64bitなのに、OSがUbuntu14 32bitというイジメを受けた為、Dockerが使えず、手動で全部入れて、結構面倒だった。
 特に、MySQL5.7に手間取ったぜ…(TestLink 1.9.17ってMySQL5.6以上って書いてあるのに、動かなかったんだけどーーー!?)