プログラムdeタマゴ

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

EclipseCDTにC方言を追加するプラグインを作ろう その2

 さて、前回BNFで拡張構文の規則を作りました。今回からは、その規則に対するJavaの処理を作っていきます。

構文解析の流れ

 Javaの処理を作る前に、CDTがどのような手順で構文を解析しているかについて、ザックリと解説します。

  1. IScannerインターフェースを実装した字句解析器(CPreprocessor)でテキストをITokenに分解
  2. 字句解析器が生成したITokenを、構文解析器で使えるように変換(今回の記事の主内容)
  3. 変換したITokenを構文解析器に渡す
  4. 構文解析器の規則に従ってITokenからIASTNodeを作成する

 

 IScannerの実装であるCPreprocessorは、名前からわかるようにプリプロセッサの処理もしてくれます。このプリプロセッサの処理があるためか、字句解析器を自作することはできないようです。

 さて、CPreprocessorが作ってくれたITokenを構文解析したいのですが、実はこのままでは構文解析器は動きません。例えば、SDCCでは終端記号として「__sfr」などを定義しました。これは終端記号であって、終端文字列ではありません。記号には、IToken.getType()で得られる整数値を用います。同じ終端文字列であっても、CPreprocessorが作るITokenのtypeと、我々が作った構文解析器が受け付けるITokenのtypeが異なっているため、我々が実装した構文解析器は動きません。この差を吸収するためのコンバータが必要になります。

 最終ステップで、構文解析器は定義されている規則にマッチすると、登録されているJavaの処理を起動してくれます。ここでマッチした規則や、ITokenの内容からIASTNodeを作成します。



 だいたいこんな感じです。ここで作られたIASTNodeから関数名のリストを作ったり、色々しているみたいですが、その辺は詳しく知りません。また、IScannerに渡すテキストも、実は全部のテキストじゃなくて、ブロック単位で管理されていたりと、色々処理が入っています。


ITokenを変換するIDOMTokenMapの実装


 先にも説明したように、CPreprocessorのIToken.getType()の値を、我々が扱える数値に変換する必要があります。この変換機能を提供するインターフェースがIDOMTokenMapです。IDOMTokenMapのインスタンスは、前回作ったBNFをコンパイルすることで生成されるParserクラスのコンストラクタに渡す必要があります。

 我々が扱える数値は、grammarファイルをコンパイル(grammerディレクトリでantを実行)すると生成される~~Parsersymインターフェースに、「TK_終端記号名」という名前で定義されています。(参考:SDCCParsersym.java)ちなみに、この~~Parsersymインターフェースで文法エラーが発生してる場合は、grammarファイルが既にバグっています。
 一方、CPreprocessorはITokenに定義されている定数「t~~~」を扱います。

Keywordの定義

 特にやらなくても良いですが、IDOMTokenMapを実装する前に、拡張定義したKeywordを定義するenumを作成しておくと、見通しが良くなります。C99で定義されているものまで、定義する必要はありません。

package nodamushi.cdt.parser.sdcc;

//Parsersymの定数をstatic import
import static nodamushi.internal.cdt.parser.sdcc.SDCCParsersym.*;

public enum SDCCKeyword{
  __data(TK___data),
  __near(TK___near),
  __xdata(TK___xdata),
………略……… 

  // わざわざ構文解析器で解析することはやめた追加キーワード
  __asm__(-1) 
  ;

  //対応する数値を保持しておく

  private final int tokenKind;
  private SDCCKeyword(int tokenKind){
    this.tokenKind = tokenKind;
  }

 


 で、ITokenから対応するenumのtokenKindを、素早く検索する為にMapにしておきます。MapはMap<IToken,SDCCKeyword>やMap<String,SDCCKeyword>ではなく、Map<char[],int>で持つ方が良いみたいです。そのためのMapにCharArrayIntMapというのがあったので、これを使いました。UPCの実装ではCharArrayMap<V>が使われていました。

  private static final int EMPTY_NUMBER = Integer.MIN_VALUE;
  private static final CharArrayIntMap tokenMap;

  static{
    final SDCCKeyword[] values = values();
    final int size = values.length;
    tokenMap = new CharArrayIntMap(size, EMPTY_NUMBER);
    sdccKeywords = new String[size];
    for(int i=0;i<size;i++){
      final SDCCKeyword v = values[i];
      final String name = v.name();
      if(v.tokenKind != -1) // 構文解析器と無関係なものは無視
        tokenMap.put(name.toCharArray(), v.tokenKind);
    }
  }
  
  // OptionalIntを使ってるのはただの趣味
  public static OptionalInt getTokenKind(char[] image){
    if(image == null){
      return OptionalInt.empty();
    }else{
      int v = tokenMap.get(image);
      return v == EMPTY_NUMBER? OptionalInt.empty():OptionalInt.of(v);
    }
  }

 

IDOMTokenMapの実装

 実はIDOMTokenMapの実装のほとんどを、先のKeywordでやっちゃっいました。なので、基本的には以下のコードをSDCCやpackage名だけ直せば、そのままで問題ありません。私もUPCからほとんどコピっただけです。

 我々がASTNode変換処理するときに使うITokenは、このIDOMTokenMapのmapKindを通じて得られたtypeに設定されたITokenになります。

package nodamushi.cdt.parser.sdcc;

import org.eclipse.cdt.core.dom.lrparser.IDOMTokenMap;
import org.eclipse.cdt.core.parser.IToken;
import static org.eclipse.cdt.core.parser.IToken.*;
import static nodamushi.internal.cdt.parser.sdcc.SDCCParsersym.*;

public class DOMToSDCCTokenMap implements IDOMTokenMap{
  @Override public int getEOFTokenKind(){
    return TK_EOF_TOKEN;
  }

  @Override public int getEOCTokenKind(){
    return TK_EndOfCompletion;
  }

  @Override public int mapKind(IToken token){
    switch(token.getType()) {
      case tIDENTIFIER : 
        return SDCCKeyword.getTokenKind(token.getCharImage()).orElse(TK_identifier);

      //以下はC99の変換(誰が作っても固定だと思う)
      case tINTEGER      : return TK_integer;
      case tCOLON        : return TK_Colon;
      case tSEMI         : return TK_SemiColon;
      case tCOMMA        : return TK_Comma;
      case tQUESTION     : return TK_Question;
      case tLPAREN       : return TK_LeftParen;
      case tRPAREN       : return TK_RightParen;
      case tLBRACKET     : return TK_LeftBracket;
      case tRBRACKET     : return TK_RightBracket;
      case tLBRACE       : return TK_LeftBrace;
      case tRBRACE       : return TK_RightBrace;
      case tPLUSASSIGN   : return TK_PlusAssign;
      case tINCR         : return TK_PlusPlus;
      case tPLUS         : return TK_Plus;
      case tMINUSASSIGN  : return TK_MinusAssign;
      case tDECR         : return TK_MinusMinus;
      case tARROW        : return TK_Arrow;
      case tMINUS        : return TK_Minus;
      case tSTARASSIGN   : return TK_StarAssign;
      case tSTAR         : return TK_Star;
      case tMODASSIGN    : return TK_PercentAssign;
      case tMOD          : return TK_Percent;
      case tXORASSIGN    : return TK_CaretAssign;
      case tXOR          : return TK_Caret;
      case tAMPERASSIGN  : return TK_AndAssign;
      case tAND          : return TK_AndAnd;
      case tAMPER        : return TK_And;
      case tBITORASSIGN  : return TK_OrAssign;
      case tOR           : return TK_OrOr;
      case tBITOR        : return TK_Or;
      case tBITCOMPLEMENT: return TK_Tilde;
      case tNOTEQUAL     : return TK_NE;
      case tNOT          : return TK_Bang;
      case tEQUAL        : return TK_EQ;
      case tASSIGN       : return TK_Assign;
      case tUNKNOWN_CHAR : return TK_Invalid;
      case tSHIFTL       : return TK_LeftShift;
      case tLTEQUAL      : return TK_LE;
      case tLT           : return TK_LT;
      case tSHIFTRASSIGN : return TK_RightShiftAssign;
      case tSHIFTR       : return TK_RightShift;
      case tGTEQUAL      : return TK_GE;
      case tGT           : return TK_GT;
      case tSHIFTLASSIGN : return TK_LeftShiftAssign;
      case tELLIPSIS     : return TK_DotDotDot;
      case tDOT          : return TK_Dot;
      case tDIVASSIGN    : return TK_SlashAssign;
      case tDIV          : return TK_Slash;
      case t_auto        : return TK_auto;
      case t_break       : return TK_break;
      case t_case        : return TK_case;
      case t_char        : return TK_char;
      case t_const       : return TK_const;
      case t_continue    : return TK_continue;
      case t_default     : return TK_default;
      case t_do          : return TK_do;
      case t_double      : return TK_double;
      case t_else        : return TK_else;
      case t_enum        : return TK_enum;
      case t_extern      : return TK_extern;
      case t_float       : return TK_float;
      case t_for         : return TK_for;
      case t_goto        : return TK_goto;
      case t_if          : return TK_if;
      case t_inline      : return TK_inline;
      case t_int         : return TK_int;
      case t_long        : return TK_long;
      case t_register    : return TK_register;
      case t_return      : return TK_return;
      case t_short       : return TK_short;
      case t_sizeof      : return TK_sizeof;
      case t_static      : return TK_static;
      case t_signed      : return TK_signed;
      case t_struct      : return TK_struct;
      case t_switch      : return TK_switch;
      case t_typedef     : return TK_typedef;
      case t_union       : return TK_union;
      case t_unsigned    : return TK_unsigned;
      case t_void        : return TK_void;
      case t_volatile    : return TK_volatile;
      case t_while       : return TK_while;
      case tFLOATINGPT   : return TK_floating;
      case tSTRING       : return TK_stringlit;
      case tLSTRING      : return TK_stringlit;
      case tUTF16STRING  : return TK_stringlit;
      case tUTF32STRING  : return TK_stringlit;
      case tCHAR         : return TK_charconst;
      case tLCHAR        : return TK_charconst;
      case tUTF16CHAR    : return TK_charconst;
      case tUTF32CHAR    : return TK_charconst;
      case t__Bool       : return TK__Bool;
      case t__Complex    : return TK__Complex;
      case t__Imaginary  : return TK__Imaginary;
      case t_restrict    : return TK_restrict;
      case tCOMPLETION   : return TK_Completion;
      case tEOC          : return TK_EndOfCompletion;
      case tEND_OF_INPUT : return TK_EOF_TOKEN;

      default:
        assert false : "token not recognized : " + token.getType();
      return TK_Invalid;
    }
  }
}

 じゃ、次回ついにIASTNodeに変換するぜよ。たぶん。

EclipseCDTにC方言を追加するプラグインを作ろう その1

 はい、誰の得になるのかわからない超ニッチシリーズが再び始まりました。

 組み込みでは、GNU CやらVisual C Compilerなんかは使えず、独特なドマイナーコンパイラを使わざるを得ないことがあります。そのドマイナーコンパイラがC標準とかに準拠してくれてたら良いんだけど、コンパイラの拡張構文とか、そもそも準拠してないとかザラにあります。(以降、標準でない拡張や準拠していない構文をまとめてC方言と書きます)

 で、そのC方言を使ってEclipse CDT上で開発すると、こういう風にエラーが出ちゃうんですよね。

f:id:nodamushi:20160725130413p:plain


 今回から解説するのは、こういう風にエディタ上にエラーを出さない様にするために、CDTに方言に対応したパーサーを追加するプラグインをどうやって作るのかって話です。

 この記事ではSDCCに対応するパーサーを作っていきます。
 成果物はこちら。
github.com

概要

 手順は大きく以下の3つになります。

  1. C方言のパーサー(構文解析器)を作る
  2. 構文解析結果をIASTNodeに変換する
  3. パーサーを提供するILanguageを作る


 少しこの手の話に詳しい方なら、「字句解析器」は作らないのか?と思われるかもしれません。ここでは説明を抜きして結論だけ言うと、字句解析器は作りません。CDTが提供するものを使います。

 構文解析器は0から全て自分が作ってもかまいませんが、C言語の仕様を全て理解し、全ての構文を実装するのはなかなか大変です。できることなら方言で拡張された構文だけを実装することで済ましたいものです。というわけで、org.eclipse.cdt.core.dom.lrparserプラグインで提供されるC99パーサーを拡張することでC方言パーサーを新たに作りたいと思います。


環境を整える

 まずはプラグイン開発に必要なものについて。以下の三つが必要です。

  1. Eclipse CDT、C99 LR Parser
  2. Eclipse CDTのソースコード
  3. LALR Parser Generator

 

Eclipse CDT,C99 LR Parser

 Eclipse CDTの開発をするので当然CDTが必要です。すでにCDTを導入済みという人も、オプションであるC99 LR Parserが入ってない可能性があるので、確認をしてください。
f:id:nodamushi:20160728174345p:plain


Eclipse CDTのソースコード

 org.eclipse.cdt.core.dom.lrparserのgrammarをベースにしますので、これをダウンロードしてきます。最初はorg.eclipse.cdt.core.dom.lrparser.sourceプラグインに入ってるかなとか思ったんですけど、肝心のgrammar部分は入っていませんでした。なので、Eclipse CDTのソースコード丸ごと全部ダウンロードしてください。
 私がやったときは、Eclipse CDT/gitのWikiに書いてあるgit://git.eclipse.org:29418/cdt/org.eclipse.cdt.gitからはどうもダウンロードできなかったので、git://git.eclipse.org/gitroot/cdt/org.eclipse.cdt.gitか、GithubのEclipse CDTからcloneしてきてください。

LALR Parser Generator

 次にLALR Parser Generatorをダウンロードしてください。Windowsの人がここからダウンロードすると、2016年現在「lpg-win32_x86_2.0.20」というなぜか拡張子がないものがダウンロードされますので、ファイル名の拡張子として「.exe」をつけてください
 ていうか、このLALR Parser Generatorとか何年も更新されてないし、この元プロジェクトのEclipse IMCってのはもう終わってるみたいだし、大丈夫なのか?って感じですが、少なくともEclipse Neonでは動いたのでまだしばらくは大丈夫そうです。



プラグインの依存関係の設定

 さて、ついにいよいよプラグイン作成に入っていきます。

 Eclipseのプラグインを作る際には、とりあえず、まずはplugin.xmlの依存関係の設定をしてしまいましょう。最低限必要なのは以下の三つ

  1. org.eclipse.core.runtime
  2. org.eclipse.cdt.core
  3. org.eclipse.cdt.core.lrparser

f:id:nodamushi:20160728174346p:plain


UPCのgrammarディレクトリをコピー

 パーサーを作っていきたいとおもいますが、何も例がないところから作っていくのもしんどいです。というわけで、UPC Parserのgrammarを例として拝借します。このUPCもC99をベースに拡張して定義されています。
 cloneしたCDTのディレクトリのトップからupc/org.eclipse.cdt.core.parser.upc/grammarディレクトリをコピーしてきてください。

 grammarディレクトリの中はこうなっています。

  • build.xml: antファイル
  • parserBuild.properties: antの設定ファイル
  • upc: grammarファイルが入ったディレクトリ
    • UPCExpressionParser.g
    • UPCGrammarExtensions.g: 主に編集することになるファイル
    • UPCNoCastExpressionParser.g
    • UPCParser.g
    • UPCSizeofExpressionParser.g

 なお、名前がUPCのままではあれなので、私はSDCCに名称変更しました。

f:id:nodamushi:20160728174347p:plain

parserBuild.propertiesの修正

 parserBuild.propertiesにはLALR Parser Generatorの実行ファイルとテンプレートとなるlrparserのgrammarロケーション設定が3つ書かれています。自分が保存したフォルダと合うように修正してください。



build.xmlの修正

 まずは、build.xmlにあるupc,UPCの文字列を、今から作ろうとしている言語名に変えておいてください。生成されるクラス名にも対応してくるので、きちんと直しておいた方が良いと思います。私の場合は、upcはsdccに、UPCはSDCCに置換しました。

 次に以下の部分を自分が保存したフォルダの場所になるように修正してください。

<import file="../../org.eclipse.cdt.core.lrparser/grammar/generate.xml" />

 また、以下の一文を、先(↑)の修正した場所の前に挿入してください。(引数で渡すのが面倒くさいので、固定で設定ファイルを読ませるようにしただけです)

<property file="parserBuild.properties" />

 次に、次のパラメータを、これから作ろうとするパーサーのパッケージ階層と合うように修正してください。たとえば、私の場合はパッケージをnodamushi.internal.cdt.parser.sdccとしました。

<property name="upc_location" value="../src/org/eclipse/cdt/internal/core/dom/parser/upc" />


 で、locationとか設定したんだからここに結果を出力してくれるかと思ってたのですが、なぜかupcディレクトリにjavaファイルが出力されてしまいます。これを毎回手で移動するのが面倒くさいので、次の移動処理をtarget upcに追加しておいてください。これの後ろについでにclean_l_filesのタスクを呼び出す処理を追加しても良いかも。(中間生成物っぽいlファイルというのは、upc_locationに出力されるんだな、これが)

<move todir ="${upc_location}">
  <fileset dir="./upc"> <!-- ディレクトリ名はそれぞれの環境に合わせてください -->
    <include name="**/*.java"/>
  </fileset>
</move>

 SDCC用に作ったnodamushiの修正ファイルは、こちらを参照してください。build.xml




---Parser.gの修正

 続いては、upcディレクトリ(私の場合はsdccディレクトリ)のファイルを修正します。

 まずは、全てのファイル名の接頭辞UPCを、build.xmlでUPCを置換した文字列に変更します。(私の場合はSDCC)

 UPCGrammarExtensions.g(私の場合はSDCCGrammarExtensions.g)を除く以下のファイルの修正をします。

  • UPCExpressionParser.g
  • UPCNoCastExpressionParser.g
  • UPCParser.g
  • UPCSizeofExpressionParser.g

 これら全ファイルについて、の出力パッケージ名と、UPCGrammarExtensions.gのファイルをインクルードしている部分を修正します。「$Import C99~~~.g $End」となっているところは変更しないでください。

%options package=org.eclipse.cdt.internal.core.dom.parser.upc

↓ 修正後(build.xmlで指定したパッケージ階層と矛盾がないように)

%options package=nodamushi.internal.cdt.parser.sdcc
$Import
    UPCGrammarExtensions.g
$End

↓ 修正後(SDCCの部分は適宜変更してください)

$Import
    SDCCGrammarExtensions.g
$End


なお、UPCSizeofExpressionParser.gの$Importは以下のようになっていますが、$DropRulesの部分は消してください。拡張構文にsizeofに関する内容がある場合は、このように規則を削除するようです。SDCCにはこのような拡張構文はないので、詳しいことは調べていません。。。

$Import
    UPCGrammarExtensions.g
$DropRules

unary_expression
    ::= 'upc_localsizeof' '(' type_id ')'
      | 'upc_blocksizeof' '(' type_id ')'
      | 'upc_elemsizeof'  '(' type_id ')'
$End

↓ 修正後(SDCCの部分は適宜変更してください)

$Import
    SDCCGrammarExtensions.g
$End

GrammarExtensions.gの編集

 SDCCGrammarExtensions.g(UPCGrammarExtensions.g)の中身を編集していきます。ここに拡張構文のパーサーを記述していきます。

 GrammarExtendsions.gは$Defineパートと$Globalsパート、$Terminalsパート、$Rulesパートに分かれています。

  • $Define : テンプレートが使う変数の定義
  • $Globals : Javaのimport文を定義
  • $Terminals : 終端記号を定義
  • $Rules : 構文解析規則を記述

$Define

 $Defineパートでは、PraserActionクラス、ASTNodeFactoryクラス、SecondaryParserFactoryを指定します。といっても、今の段階では何も作っていないので、とりあえず、UPCの文字列を作る言語の名前に変えておけば良いと思います。実態は後で作ります。

$build_action_class /. SDCCParserAction ./
$node_factory_create_expression /. new SDCCASTNodeFactory() ./
$parser_factory_create_expression /. SDCCSecondaryParserFactory.getDefault() ./

 あと、2016年7月現在、このままの$Defineではコンパイルが通りません。
 とりあえず、コンパイルを通すという目的だけで、私は$Buildと$EndBuildという変数を上書き定義しておきました。正しいかどうかはともかく、こうしておけば、ひとまず通ります。

$Define
  $build_action_class /. SDCCParserAction ./
  $node_factory_create_expression /. new SDCCASTNodeFactory() ./
  $parser_factory_create_expression /. SDCCSecondaryParserFactory.getDefault() ./
  $Build /. action. ./
  $EndBuild /. ./
$End

 


$Globals

 次に$Globalsですが、importに何が必要かとか、今の段階ではわかりません。ひとまず空っぽにして、放っておききましょう。実際にSDCCParserActionなどを実装してから、最後に必要なものを足せばOKです。



$Terminals

 LALR Parser GeneratorではBNFを用いて構文を記述していきます。
 $Terminalsには、追加する終端記号を定義します。

 え?BNFって何?終端記号って何それおいしいの?って人は詳しくは、このページ(BNFおよび拡張BNF)Wikipediaなどを読んでください。あ、あと、Hatada's Home Page様の構文解析あたりも、とても有用な情報だと思います。

 ここではザックリと、構文に追加したいキーワードとだけ言っておきます。SDCCでは以下のものを定義しました。

$Terminals
  __data  __near  __xdata  __far  __idata  __pdata
  __code  __bit  __sfr  __sfr16  __sfr32  __sbit
  __at  __banked  __interrupt  __using  __reentrant
  __critical  __naked  __wparam  __shadowregs
  __preserves__regs  __asm  __endasm
$End

 なお、本当のキーワードと同じ名前でなくてかまいません。構文上、「--」はコメントと見なされてしまいますし、Javaの変数名に使えない文字列を使うことはできません。そういった場合は、何か別のわかりやすい名前をつけましょう。(C99のgrammarでは「...」はDotDotDotとか名前がついています。)



$Rulesの構文

 BNFに従って追加構文を記述していきます。BNFに追加されている特殊な構文について説明します。
 なお、BNFなにそれおいしいの?って人はこのページ(BNFおよび拡張BNF)Wikipediaなどを読んでください。


 1. rule ::= A | B | C というのは以下のように、ばらして書くことができるっぽいです。

rule ::= A
rule ::= B
rule ::= C

 2. 文法にマッチしたときに行うJavaの処理を、/. ./で括って定義することができます。(定義しなくても良いです)

rule 
  ::= 'HOGEHOGE'
   /.  System.out.println("終端記号HOGEHOGEにマッチした");  ./
    | 'MOGEMOGE'
   /.  System.out.println("終端記号MOGEMOGEにマッチした");  ./
    | rule1 rule2
   /. System.out.println("rule1の処理をした後、rule2の処理をし、この文字列がでる"); ./

 3. 文法的には単にrule ::= rule1 rule2なんだけど、rule1とrule2の間に特別にJavaの処理を挟みたい場合があります。そういう場合には、空にマッチする$emptyを使うことができます。このテクニックは<openscope-ast>で使います。

rule ::= rule1 rule1_2 rule2

rule1_2 ::= $empty
         /. System.out.println("1と2の狭間");

 

$Rulesに追加規則を定義する

 さて、ついにBNFで文法を追加していきましょう。Javaでの処理定義は、ひとまずBNFを作った後に考えれば良いです。

 追加する規則は、文法のルートとなる規則からたどれる必要があります。まぁ、基本的にはC99Grammar.gに定義されている規則の、どれかに追加すれば大丈夫です。

 じゃぁ、C99Grammar.gで定義されている規則は何があって、どこに追加すれば良いのか………それは………根性でC99Grammar.gを解析する以外ありませぇえん!!!!( ゚д゚)

 ま、まぁ、たぶん大丈夫ですよ。0からC言語の構文全部BNFで書き上げるよりは、ずっと解析するだけの方が簡単ですよ。実際、数時間でなんとか私はなったんですから、大丈夫です、きっと。UPCの例や私の作ったSDCCの例を見ながら、なんとなくできますよ。たぶん。

 後は試行錯誤です。

 一応、私がSDCCの拡張構文を追加するときに使った規則は以下です。

  • type_qualifier : 型の修飾関連の規則
  • simple_type_sqecifier_token : intやdoubleといった組み込み型の規則
  • function_direct_declarator : 関数名と引数の規則
  • statememt : 基本的にはブロックとかスコープが絡むようなものっぽい

 あと、C99で何が終端記号として宣言されているかは確認しておいた方が良いでしょう。私は、integer(整数)とidentifierの終端記号を利用しています。


 SDCCのために追加したBNF。(Javaの処理は省く)

-----------------------------------------------------------------------------------
-- Declarations
-----------------------------------------------------------------------------------
type_qualifier
    ::= address_space_name_qualifier
      | define_address
      | sdcc_type_qualifier


-- 以下二つを分けてる理由は特にない
address_space_name_qualifier
    ::= '__data'
      | '__near'
      | '__xdata'
      | '__far'
      | '__idata'
      | '__pdata'
      | '__code'
      | '__sfr'
      | '__sfr16'
      | '__sfr32'

sdcc_type_qualifier
   ::= '__banked'

define_address
    ::= '__at' '(' absolute_address ')'
      | '__at' absolute_address

absolute_address
    ::= 'integer'



simple_type_specifier_token
    ::= '__sbit'
      | '__bit'

-----------------------------------------------------------------------------------
-- Function
-----------------------------------------------------------------------------------

-- ↓C99Grammar.gからfunction_direct_declaratorをコピー
original_function_direct_declarator
    ::= basic_direct_declarator '(' <openscope-ast> parameter_type_list ')'
          /. $Build  consumeDirectDeclaratorFunctionDeclarator(true, true);  $EndBuild ./
      | basic_direct_declarator '(' ')'
          /. $Build  consumeDirectDeclaratorFunctionDeclarator(true, false);  $EndBuild ./


function_direct_declarator
  ::= original_function_direct_declarator sdcc_function_attributes


sdcc_function_attributes
 ::= sdcc_function_attribute
    | sdcc_function_attributes sdcc_function_attribute
 
sdcc_function_attribute
  ::= address_function_attribute
    | '__critical'
    | '__reentrant'
    | '__naked'
    | '__shadowregs'
    | '__wparam'
    | preserves_regs_attribute


address_function_attribute
  ::= '__interrupt' '(' absolute_address ')'
    | '__interrupt' absolute_address
    | '__using' '(' absolute_address ')'
    | '__using' absolute_address
    | '__interrupt'
    | '__using'

preserves_regs_attribute
    ::= '__preserves__regs' '(' ')'
      | '__preserves__regs' '(' preserves_regs_args ')'

preserves_regs_args 
    ::= preserves_regs_arg
    | preserves_regs_args ',' preserves_regs_arg

preserves_regs_arg
    ::= 'identifier'

-----------------------------------------------------------------------------------
-- Statements
-----------------------------------------------------------------------------------

statement
     ::= critical_statement
       | oldasm_satement

critical_statement
   ::= '__critical' compound_statement

oldasm_satement
   ::= '__asm' oldasm_contents '__endasm' ';'
     | '__asm' '__endasm' ';'


oldasm_contents
   ::= oldasm_content
     | oldasm_contents oldasm_content

oldasm_content
   ::= oldasm_item

-- __endasm以外全部
oldasm_item  ;;= 省略




 次回はJavaで何をどう処理するのかの部分を実装していきます。

Eclipse CDTにSDCCの方言を追加するプラグインを作った

 ScalaやらGoやらclojureやらKotlinといった華々しいモダン言語の世界とは真逆で、組み込み屋の言語発達は遅いです。アセンブリです。良くてCです。それもC88だったりすますデス(遠い目)。
 また、政治的理由によりドマイナーなマイコン使わされると、なんだか聞いたこともないようなドマイナーなコンパイラーを使わされることになります。
 このときに困るのが、エディタがそのマイナーコンパイラーの「方言」に対応していないことです。まぁ、個人的にはEmacsで良くね?と思いますが、そこも政治的いろいろな理由が絡むことがあります。


 そのマイナーなコンパイラの一つに、SDCCというC88のコンパイラーがあります。
 このSDCCの方言に対応したEclipse CDTの言語パーサー作ったのでご報告。

インストール

 新規ソフトウェアのインストールからサイト「https://nodamushi.github.io/nodamushi_sdcc/」を入力し、インストールしてください。たぶん、必須ライブラリも勝手に入ると思います。


設定方法

 プロジェクトの設定画面の「C/C++ General」→「Language Mappings」でContent typeが「C Source File」と「C Header File」に対してLanguage「SDCC」を割り当ててください。
 SDCCしか使わない、という人はプロジェクトごとに設定するのではなく、ワークスペースの設定のLanguage Mappingsで設定することもできます。

f:id:nodamushi:20160725130416p:plainf:id:nodamushi:20160725130415p:plain




使い方と注意点


 SDCCの方言をざっと挙げると、以下のようなものがあります。

//変数の配置場所を明示できる
__xdata __at(0xF800) unsigned char hoge;

//割り込み指示とかを書ける
void test() __interrupt __reentrant;

void test() __interrupt(1) __reentrant{
  //インラインアセンブラ(旧式)
	__asm
	nop
	nop
	nop
	__endasm;

	// critical block
	__critical{
	}
}


 この方言を単にEclipse CDT上で書こうものなら、以下のようにエラー警告地獄になります。

f:id:nodamushi:20160725130413p:plain


 今回私が作ったプラグインをLanguageに設定しておくと、こうなります。

f:id:nodamushi:20160725130414p:plain


 やったね!エラーが消えたよ!


 ただし、いくつかの注意点があります。

  1. エディタ上でエラーが出てなくても、コンパイルは通らないかもしれない
  2. __sbitなどはint型と解釈されてしまう
  3. __asm~__endasmの間は何も解析していない
  4. シンタックスハイライトの機能ではないので、__asm~~__endasmの中のコメント等も通常のCと同じように表示される
  5. __interruptなどの指示をした後に改行するとインデントが変


 1については、まぁ、一番重要なのは構文エラーじゃないところでエラーが出ることを解消することだったんで。。。特にC99を元にしてパーサーが作られているので、変数宣言をどこでやってもエラーにならないなど、実際のSDCCとは異なっています。ビルドしてからじゃないと問題がわかりません。SDCCにsyntax-onlyみたいなオプションがあれば、エラーチェックはそっちに任せられるんだけどなぁ。
 2については、現状諦めた。__sbitと__bitをBuiltin型に指定しようと努力はしたんだけど、良くわからんかった。3,4については、まぁ警告の黄色い線が出なくなっただけでもましと思ってください。

 5について何ですが、関数定義の後に、__interruptなどの指示をつけ、改行してから{を書くと以下のようになってしまいます。

void main()
{//<- ok
}

void test() __interrupt(1) __reentrant{
}//<= indent ok


void test1() __reentrant
		{ // <-  (´・д・`)
	int test;
		} // <-  (´・д・`)


 改行せずに{を書くと変にはならないので、改行しない書き方を標準としてください。






手動ダウンロード

手動でインストールしたい人はここからダウンロードしてください。
github.com

手動インストール方法:

  1. 依存ライブラリのC99 LR Parserを導入する
  2. 上記のダウンロードのところからzipを落としてきて、展開したものをdropinに突っ込む

1. C99 LR Parserのインストール

 まずは、C99 LR Parserが入っているかどうか確認してください。「help」→「Installation Ditails」の「Plug-ins」タブを選択し、「lrparser」で検索してください。インストールされている場合は、こんな感じになります。

f:id:nodamushi:20160725130417p:plain



 インストールがされていない場合は、「Help」→「Install New Software」でCDTのアップデートサイトを選択し、「CDT Optional Features」の中の「C/C++ C99 LR Parser」をインストールしてください。
f:id:nodamushi:20160725130418p:plain




2. SDCCパーサーのインストール

 ダウンロードしたzipを展開したものをEclipseが保存されているディレクトリにある「dropin」に入れる。

f:id:nodamushi:20160725134136p:plain

プログラマのためのVerilog入門

 ここのところしばらくVerilogをやっていました。というわけで、まとめていこうと思います。

 といっても、基本的な文法の話ではなく、プログラミングをこれまで基本としてやってた人がハードウェア記述言語を触るときに気をつけなくてはならないことを中心に記載していこうと思います。(というか、記事書けるほど文法詳しくない。)


記事の内容:



メインは最後の状態変数の話です。では興味のある方はどうぞ。



論理合成可能なVerilogとテストベンチのVerilogは違う

 C++開発でテストするなら、Assert文やCppUnitTestとかあたりを使いますかね。JavaならJUnitとか使いますよね。C++で開発したものをC++でテストする。Javaで開発したものをJavaでテストするということは特に違和感ないかとおもいます。

 Verilogでも同様で、Verilogで開発した(論理合成可能)ものをVerilogでテストします。

 我々ソフト畑の人間からすれば、C++,Javaで開発したものとテストコードにおいて、その文法的な書き方に区別はつけてないとおもいます。
(テストコードは開発コードに比べて雑とか、利用してるテストライブラリに併せて若干変則的な書き方をするなどの変化はあるでしょうが。)

 が、Verilogの場合、「論理合成可能」、つまり回路に変換可能という縛りが開発コードにはある一方、テストベンチ用の検証コードはその縛りがないので、文法レベルの書き方が増加します。まずはそのことを理解しておきましょう。


regとwireとlogicと

 Verilogが糞だなと思う理由の一つが「reg」「wire」「logic」という型の存在。方々でも言われておりますが、regとwireという型は単にシミュレータの実装上のもので、物理的な意味(配線型、メモリ型など)はありません

 たぶんですが、最初に論理回路シミュレータを作った人が、そういう二つの構造体を定義してデータ管理する実装をしたんでしょう。こんなイメージ↓の実装だったのではないかと。

typedef struct{
  char*  value;
  size_t size;
}wire;

typedef struct{
  char* current_value;
  char* next_value;
  size_t size;
}reg;

wire* wire_list;
reg*  reg_list;

void update()
{
  //always等のプログラム的な処理の実行
  calcReg();
  //assignの一発で決まる処理の実行
  calcWire();
  // regのnext_valueをcurrent_valueに代入
  updateReg();
}




 で、作ったシミュレータを動かすための文法作るときに、二つの構造体のどっちに割り振るべきか判断する処理を考えるのが面倒くさかったのでしょう。もしくは単に、C言語では型を書かないといけないので、何も考えずに癖でそれを持ってきただけかもしれません。あぁ、なんかこう考えると、親近感がすごくわいてきませんか?私はとても親近感がわきました。

 ということで、我々プログラム畑の人間からすれば、この背景を理解さえしていればregとwireに関しては、さほど躓いたり不思議に思うことはないと思います。単にシミュレーターというライブラリが宣言している構造体を宣言しているだけですから。



 で、文法上も分けてる理由がないとようやくSystemVerilogでregとwireがlogicという型でまとめられたわけです。





 これは単なる文句なんですが、もうこのlogicを作ろうという判断がまた糞ナンセンスですよね。logicとかいう型を作った人は「あくまでVerilogはシミュレータ用言語である」としか考えてないんじゃないでしょうか。だったらまだregとwireの方が愛嬌があります。

 作るべきだった型は「logic」ではなく、フリップフロップ型、ラッチ型、組み合わせ回路型などの物理型と検証型(今のlogic型)に分けるべきだと思います。なんか申し訳程度に「always_ff」と「always_comb」がありますが、なんで型じゃなくて記述方法で制限しようとすっかね?


 むろん、論理合成時の最適化や、エンジニアのコードの書き方が悪くて実際にフリップフロップに変換することができないとか、回路上にラッチができる可能性があるから型で制限しにくい!という意見もあるかもしれない。けど、それこそエンジニアの意向と異なるわけだからWarning、Error事項だよね?もし、最適化の結果それらが許される、とエンジニアが判断するなら、それ用の型に変更する(もしくはアノテーション的なものの付加で良いかもしれない)べきです。






状態遷移の考え方を変数中心にする

 特に最近はclojureやらなんやらイミュータブルな実装が流行で、私に限らず、「変」数ではなく、引数など「定」数でプログラムすることが多くなっていると思います。むろん、変数の値で状態遷移するプログラミングを全くしないわけじゃないですけど。

 しかし、Verilogを書く際には全く逆で、変数を中心に状態遷移するようにしないと、綺麗な実装ができません。しかもその状態遷移は、普段我々が全く意識していないレベルの状態遷移でも変数中心で状態遷移させます。一度理解すればなんてことはないんですが、私はこの発想が最初なかったんですよね。

 

 「処理A,Bをしたあと、処理Cをし、前の結果が真なら処理Dを、偽なら処理Eをする。」という内容を考えましょう。

 状態遷移はこうなっていますよね。


f:id:nodamushi:20160625022550p:plain



 まぁ、ここまでは特に問題ありません。言われりゃその通りだ。

 さて、ここで問題になるのは、AからB、BからCへのε遷移は実際に誰が遷移させるのかと言うことです。

 たとえば我々プログラマはこう書きますよね。

void main()
{
  A();
  B();
  if(C())
    D();
  else
    E();
}


 このとき、A(),B(),if(C())への遷移という概念はおそらく考えていないと思います。なぜなら、プログラムは上から下に実行されるものだからです。一方CからD,Eは、遷移を意識していると思います。(それが状態遷移と思っていなくても、分岐という形で意識していると思います。)

 この上から下に実行されるという時間の概念がε遷移の正体になります。


 しかし、Verilog(論理回路)ではこの上から下へと言う時間の概念を使うことはできないのです。

 むろん、回路Aの信号を回路Bに伝え、回路Bの信号を回路Cに伝え………っていうような逐次処理ができるなら、単に組み合わせ回路で表現できます。ですが、この間メモリ(フリップフロップ)の更新はできません。だって組み合わせ回路だから。

 途中、メモリの更新をどうしても挟まなくてはならない場合、どうしてもクロックの単位で処理する回路を区切ることになります。あぁ、残念ながらすでにこの時点で上から下へという時間の概念がぶった切られました。なぜならそれぞれの区切られた回路は並列に動くからです。

 従って、どの回路が有効なのかを決定する状態変数を管理する必要があります。擬似コードとしてはこんな感じでしょう。

int state;
void main()
{
  while(state!=5)//5で終了とする
  {
    // A,B,C,D,Eの関数それぞれが実際には一つ一つの回路になる
    // stateの値で有効な回路を選択する
    // C言語ライクに書くとA~Eのどれか一つしか起動しないが、実際には全部同時に並列実行されます。
    switch(state)
    {
      case 0:   A();   break;
      case 1:   B();   break;
      case 2:   C();   break;
      case 3:   D();   break;
      case 4:   E();   break;
    }
  }
}

 ん~、だいぶ普通のプログラミングから離れてきましたが、まだこれくらいは書くことあります。ユーザーからの入力による状態遷移が複雑すぎる場合はこうやって管理すると見通しがよくなったりします。昔作ったTeXの処理系の中心部分はこんな設計でした。

 ところで、stateはだれが変更するんでしょうかね?

 いや、そりゃま、こうでしょ?と私が最初に普通に考えたものは以下のような内容でした。

int state;
//実際には各回路(関数)はそれぞれの処理をしますが、
//めんどうなので今はstate以外は省略しています。
void A(){state = 1;}
void B(){state = 2;}
void C(){state = 何らかの条件 ? 3 : 4;}
void D(){state = 5;}
void E(){state = 5;}

 はい、残念ながら、これを回路としては作れないんですよね。一つのフリップフロップが5つの回路から入力されるわけですから。例えるなら一つのUSB端子に複数のUSBデバイスを挿すことはできないことと同じです。

 複数のUSBデバイスを挿せないならハブを挟めば良いじゃない。というわけで、私が次に考えたのがこうです。

int state;
int state_a,state_b,state_c,state_d,state_e;
void state_update() //毎クロック勝手に動く
{
  state = state == 0? state_a://状態A
          state == 1? state_b://状態B
          state == 2? state_c://状態C
          state == 3? state_d://状態D
                      state_e;//状態E
}
void A(){state_a = 1;}
void B(){state_b = 2;}
void C(){state_c = 何らかの条件 ? 3 : 4;}
void D(){state_d = 5;}
void E(){state_e = 5;}

 あくまで間違いを選択する程度の能力。もちろん、これ動くんすよ。各変数を更新する回路(関数)は一つしかないので問題ありません。が、決してスマートではありませんし、回路規模もでかくなります。

 さて、なぜ私があくまで間違い続けるのか?それは、生まれながらの馬鹿で死ぬべきだからというもっともな答えには目をつむるとして、各関数間の遷移、特にCからD,Eへの遷移の責任が関数Cの戻り値にあるという発想から抜けられなかったからです。あと、こうしておくと、特定の関数からの状態遷移を後から変えるのが楽ですよね?


 ここで一気に、状態遷移に関して各関数が主体の構成から、状態変数が主体の構成に切り替えるパラダイムシフトが必要になります。つまりこうです。

int state;
int c_value;
void state_update() //毎クロック勝手に動く
{
  state += state==3||(state == 2 & c_value) ? 2: 1;// CからEに移動する場合とDでは2を加算
}
void A(){}
void B(){}
//* c_valueはCからD,Eどちらに移動するのかの条件となる値。
//  組み合わせ回路で出力します
void C(){c_value = 何らかの条件 ? 0: 1;}
void D(){}
void E(){}

 このように基本的には各関数状態変数の更新には何も関与せず、state_updateが常に全ての状態遷移の管理をします。

 まれにCの様に遷移条件を組み合わせ回路ではき出す奴もいますが、あくまで遷移条件だけです。遷移状態ははき出したりしません。


 このようにstate_updateが全て受け持つことで、回路が単純化されます。この例ではどの信号(変数)を選択するかを決めてたセレクタがなくなり、単純な加算機一つになりました。state ==3 || (state ==2 & cvalue)?の部分がセレクタに見えますが、その後ろにあるのは定数なので、ただの論理式です。

 まぁ、一方で、状態遷移の変更要求にたいする柔軟性は若干失われますが。(仕様変更で一つの状態遷移を追加するために、下手すると全部変更する羽目になったりする。たとえば上の例でAとBの間にXを挟んでくれという仕様が来たら………?Xを1とし、B以降の状態値を1増やすのか、それともA:0→X:5→B:1という遷移条件を増やすのか………)


 状態遷移………なかなか味わい深いですね。こんな本を買って今積んでいるところです。みなさんも興味があったら是非。

組込みエンジニアのための状態遷移設計手法―現場で使える状態遷移図・状態遷移表の記述テクニック― (MBD Lab Series)

組込みエンジニアのための状態遷移設計手法―現場で使える状態遷移図・状態遷移表の記述テクニック― (MBD Lab Series)

Firefox44.0で拡大時にIMEのポップアップ位置がずれる

自動でFirefox44.0に更新されたところ、以下のように、拡大しているときのテキストエリア入力でIMEのポップアップ位置がずれるという現象が発生しました。

Firefox47で修正されたことを確認しました。



環境依存のバグかはわかりませんが、プロファイルを作り直して、新しいプロファイルで実行しても同様に発生したので、アドオンが原因ではなさそうです。

(私が利用しているIMEはATOKです。32bit版、64bit版同様に発生しました。)

非拡大時にはこの現象は起こらないので、拡大時のIME表示位置計算が何かミスっているんだと思います。

f:id:nodamushi:20160129005153j:plain


Firefox43.0.4に戻したところ、この現象は消えたので、もし、同様の現象が発生し、困っている方がおられましたら、直るまでは前バージョンに戻しておくと良いと思います。(自動更新を切ることも忘れずに)

Firefox43.0.4(Win32)

Firefox43.0.4(Win64)


f:id:nodamushi:20160129005152p:plain


拡大機能なんてあまり誰も使わないのかねぇ?
目が悪い私は拡大しまくってるんだけど。



ところで、Firefoxの悪手のせいでKeysnailの寿命が近いという話は本当なんですかね……?


追記

会社のPCでも試したところ同様の現象が発生することを確認しました。

会社のIMEはマイクロソフト純正です

MarkdownビューワーをJavaFXで作ってみた

 Markdownを書くのに一番いい方法って何なんでしょうね?
 私はEclipse + GMF viewerを主に使っていたんですけど、このGMF viewerってディレクトリにhtmlファイル出力しちゃうのがすっごい気にくわないんだよねぇ。
 かといって、Firefoxとかでやるとしても、たとえKeySnailを入れてるとしても、EclipseとかEmacsのテキスト編集機能にはさすがにかなわないのよね。



 で、GitBucket作者のたけぞうさんがGitBucket用のMarkdownプロセッサ(markedj)をJava作って公開したという記事を見つけた。

 ほう、Javaとな。

 しかも、会社のGitサーバーに入れたのはGitBucketなので、私の利用環境との相性も良いじゃん。

 よし、markedjのビューワー作るか!JavaFXで!

というわけで、できた

以下からダウンロードできます。
Java8u40以上のJavaにちゃんとパス通ってればmarkedjviewer.jarをダブルクリックで起動するはず。
なお、Windows以外で動くかはわからんっ。前に作ったJarファイルの位置の検索がちょっとトリッキーなことしてるので、テストしたWindows以外で動くか謎。
markedjviewer


f:id:nodamushi:20151108202326p:plain

ソースコードはこちら
Git repository




使い方

 まぁ、ぶっちゃけほとんど機能なんてないので、説明するようなこともないんですが。。。

 起動するとこんな感じの画面です。
f:id:nodamushi:20151108204635p:plain

 上のバーのファイルボタンからMarkdownを開くなり、ドラッグアンドドロップをするなりで開くことができます。

 開いたら、………あとはスクロールするぐらいしか操作することはないですけどね。

 一応、現在の表示内容のHTMLを保存する機能(Ctrl+S)、画像等の変更を反映させるためのリロード(F5かCtrl+R)があります。
 メニューバーにあるコンフィグ(歯車のボタン)機能は張りぼてです。GUI作るのが面倒くさくなりました。




Emacsと組み合わせる

 このビューワーはMarkdownファイルを監視しているので、ファイル内容が変更されると、自動的に表示を更新してくれます
 本当は画像ファイルとかも監視したかったんだけど、WebViewが今何開いてるのかどうやって取得するのかわからなかった。まぁ、今後の課題と言うことで。

 さて、Emacsにはauto-save-buffers-enhanced.elという、ファイルを編集したら、自動的に保存を行ってくれる便利なプラグインがあります。


 ファイル更新監視 + 自動保存………もう、私が言いたいことはもうおわかりですね?
 つまり、Emacs+auto-save-buffers-enhanced.el+markedjviewerで結果を即座に確認しながら、書くことができるわけです!

 んほおおおおお、きもちいいいいのぉおおおおおお!!!

f:id:nodamushi:20151108202925p:plain

 あ、そうそう、表示領域を広くするために、ウィンドウがフォーカス持っていない場合はツールバーが消えるという地味な機能がついています。(上の画像ではビューワーにフォーカスがないので、黒いツールバーが表示されていない)




 というわけで、私にとっては結構便利なものがなんかできました。
 これで快適なMarkdown生活が送れます。



 

Javaでコマンドラインから受け取った引数処理するクラス作ってみた

 コマンドラインから受け取った引数を簡単に処理したいという話。
 
 結論から言えば、すでに作られているライブラリ使うといいよ!!!
 参考:Javaでコマンドライン引数を処理する


 
 まぁ~、車輪の再々再発明なんだけど、MITライセンスのライセンスすら嫌じゃん。URLとか、ライセンス書かないといけないし。ぶっちぇけここしか外部ライブラリ使ってないし、そのためにいちいちライブラリ情報書くのもなー。
 というわけで、著作権放棄のライセンス(CC0 1.0 Universal)で作ってみた。よっぽどライセンスが緩すぎるものしか使いたくねぇ!って人は選択肢になるかもしれないよ。


 nodamushi/NArguments · GitHub

 Java6対象。(ワークステーションのLinuxがJava6だったのよっ。)
 ただし、Pathを使ってるから、コンパイルするときは、JDK7以上にするか、Path関連全部削除するか。

 基本的な思想はたぶん、arg4jと同じ。(使ったことないけど)
 簡単な使い方はDemo.javaを見ればわかるかなーってくらい、単純。ドキュメントなんていらない。
 私しか使わねぇから、ドキュメントも用意するの面倒くさいとか、そういう理由では断じてない。私は嘘はつかないっ(`・д´・ )



public static void main(String[] args){
    final NArgument<Args> nArgument = new NArgument<Args>(new Args());
    nArgument.setIgnoreConvertException(false);
    try {
      nArgument.addArguments(args);
    } catch (final ConvertException e) {
      System.out.println("Parse error:"+e.getMessage());
      System.out.println(nArgument.getDescription());
      return;
    } catch (final ReflectionException e) {
      e.printStackTrace();
      return;
    }

    final Args a = nArgument.get();
    System.out.println(a);
    if(a.help){
      System.out.println();
      System.out.println(nArgument.getDescription());
    }

  }


 Argsってのは、自分で定義するデータ格納クラスです。こんな(↓)感じで定義。
 使える型はString以外に、intなどのprimitiveと、Path,File,Charsetが使えます。
 Argumentsで正規表現で受け取る受けとらないを選択できるのが地味に便利かなぁと思っています。


@Description(
    "demo arguments.\n"
    + "[-a String]* [(-b|--bbb|--bbbb)] [-c] [-d] [(-e|--encoding) File Encoding] "
    + "[-i Integer] [(-h|--help)] [-n Number] [InputFiles]+")
class Args{
  @Option(value="-a",description="multi string option")
  private List<String> strs;

  @Option(value="-b",alias={"--bbb","--bbbb"},description="flag option")
  private boolean flagB;

  @Option("-c")
  private boolean flagC;

  @Option("-d")
  private boolean flagD;

  @Option(value="-e",alias="--encoding",description="text file charset")
  private Charset charset;

  @Option(value="-i")
  private String integer;
  //setterがある場合はsetterを優先的に使う。たとえprivateであっても。
  //従って、integerはStringであるが、setterがintになっているので、int型のOptionと判定される
  private void setInteger(final int i){
    integer = "input integer value is '"+String.valueOf(i)+"'";
  }

  //@Optionはmethodにも付加可能
  public boolean help;
  @Option(value="-h",alias="--help",description="show this help")
  private void setHelp(final boolean b){
    help = b;
  }

  @Option("-n")
  private double number;

  @Arguments(match="\\.txt$")
  private List<Path> inputTextFiles;

  @Arguments
  private List<File> inputFiles;


  @Override
  public String toString(){
    final StringBuilder sb=new StringBuilder();
    sb.append("Options\n");
    sb.append("-a = ").append(strs).append("\n");
    sb.append("-b = ").append(flagB).append("\n");
    sb.append("-c = ").append(flagC).append("\n");
    sb.append("-d = ").append(flagD).append("\n");
    sb.append("-e = ").append(charset).append("\n");
    sb.append("-i = ").append(integer).append("\n");
    sb.append("-h = ").append(help).append("\n");
    sb.append("-n = ").append(number).append("\n\n");
    sb.append("Arguments\n");
    sb.append("text files = ").append(inputTextFiles).append("\n");
    sb.append("else files = ").append(inputFiles);
    return sb.toString();
  }
}


 Demo.javaに次のような引数を与えて実行するとこんな感じになる。

-a a1 -a a2 -a a3 --bbb -cd --encoding shift-jis -i 200 --help -n 20.1 test/test1.txt img/img.jpg test/test2.txt

f:id:nodamushi:20151103211142p:plain

Options
-a = [a1, a2, a3]
-b = true
-c = true
-d = true
-e = Shift_JIS
-i = input integer value is '200'
-h = true
-n = 20.1

Arguments
text files = [test\test1.txt, test\test2.txt]
else files = [img\img.jpg]

demo arguments.
[-a String]* [(-b|--bbb|--bbbb)] [-c] [-d] [(-e|--encoding) File Encoding] [-i Integer] [(-h|--help)] [-n Number] [InputFiles]+

   Options:
      -a        :multi string option

      -b
      --bbb
      --bbbb    :flag option

      -c        :

      -d        :

      -e
      --encoding:text file charset

      -h
      --help    :show this help

      -i        :

      -n        :