概要
DSL(Domain Specific Language) 〜 N進数対応表の出力DSLを作ってみる
詳細
DSL(Domain Specific Language)=ドメイン特化言語。
Ruby詳しくなくても、対象ドメインに詳しければ動作させることができるような
ミニ言語=ドメイン特化言語を作成する。
代表例としてはRakeのRakefileやBundlerのGemfileなど。
(どちらもRuby自身で構文解析するのでRubyの内部DSL)
サンプル仕様
10進数とそれに対応する2進数、8進数、16進数の一覧を表示するためのDSLを作成します。
DSLのファイル名は「Digittabledefine」
DSLのパーサーは「digit_table_define_dsl.rb」
とします。
用意する文法は以下。
また、数値の一覧表示自体は tbpgr_utils gem に実装済みなのでそちらを呼び出します。
文法 | 設定内容 |
---|---|
radix :radix_type | 出力対象の基数を数値/String/Symbolのどれかで設定する。設定値は 2,8,16,:all |
from :from | 出力範囲の指定。開始値を整数で指定 |
to :to | 出力範囲の指定。終了値を整数で指定 |
from,toのデフォルトはそれぞれ1と10にします。
※DSLの記述をしなかったら1,10が設定される。
サンプル
digit_table_define_dsl.rb
require 'tbpgr_utils' class DigitTableDsl attr_reader :radix_type, :from, :to module ERROR_MESSAGES RADIX_TYPE_ARGUMENT_ERROR = 'invalid Object for radix_type. The Object must implement to_i' RADIX_TYPE_INVALID_RANGE = 'invalid Object for radix_type. The Object can use only 2/6/8/16. your input value is %d' end VALID_RADIX_TYPE_NUMBERS = [2, 4, 8, 16] def initialize @from = 1 @to = 10 end def execute(dsl) instance_eval dsl puts table end def radix(radix_type = :all) check_radix_type(radix_type) end def from(from = 1) @from = Integer(from) end def to(to = 10) @to = Integer(to) end private def check_radix_type(radix_type) if ['all', :all].include? radix_type @radix_type = :all return end fail ArgumentError, ERROR_MESSAGES::RADIX_TYPE_ARGUMENT_ERROR unless radix_type.respond_to? :to_i @radix_type = Integer(radix_type) fail ArgumentError, format(ERROR_MESSAGES::RADIX_TYPE_INVALID_RANGE, @radix_type) unless VALID_RADIX_TYPE_NUMBERS.include? @radix_type end def check_from_or_to(from_or_to) fail ArgumentError, ERROR_MESSAGES::RADIX_TYPE_ARGUMENT_ERROR unless VALID_RADIX_TYPE_NUMBERS.ciradix_type.respond_to? :to_i end def table case @radix_type when 2 Fixnum.to_binary_table @from, @to when 8 Fixnum.to_oct_table @from, @to when 16 Fixnum.to_hex_table @from, @to when :all Fixnum.to_digit_table @from, @to end end end dsl = File.read('./Digittabledefine') DigitTableDsl.new.execute dsl
実行例1
全基数出力。from/toはデフォルトの設定(1-10)を利用。
Digittabledefine
radix :all
出力
|10digit| 2digit|8digit|16digit| | 1|00000001| 01| 0001| | 2|00000010| 02| 0002| | 3|00000011| 03| 0003| | 4|00000100| 04| 0004| | 5|00000101| 05| 0005| | 6|00000110| 06| 0006| | 7|00000111| 07| 0007| | 8|00001000| 10| 0008| | 9|00001001| 11| 0009| | 10|00001010| 12| 000a|
実行例2
2進数のみ。1から16。
Digittabledefine
radix 2 from 1 to 16
出力
|10digit| 2digit| | 1|00000001| | 2|00000010| | 3|00000011| | 4|00000100| | 5|00000101| | 6|00000110| | 7|00000111| | 8|00001000| | 9|00001001| | 10|00001010| | 11|00001011| | 12|00001100| | 13|00001101| | 14|00001110| | 15|00001111| | 16|00010000|
radix, from, to のすべては Kernel#Integer で整数に変換しています。
例えば整数型のInteger(Fixnum/Bignum)以外にも数値文字列(各基数表記に対応)、浮動小数(Float)に対応していて、
それぞれ整数に変換されて設定されます。
これにより、Integerのみを許容する場合に比べて様々な指定方法が利用できて柔軟なIFになります。
これは書籍 Confident Ruby から学んだ知識とテクニック。
例えば以下のような Digittabledefine でも戦術と同じ結果が出力されます。
Digittabledefine (10進文字列、2進文字列、8進文字列)で指定
radix '2' from '0b01' to '020'
Digittabledefine (Fixnum、Float、16進文字列)で指定
radix 2 from 1.9 to '0x10'
実行例3
8進数のみ。8から32。
Digittabledefine
radix 8 from 8 to 32
出力
$ ruby digit_table_dsl.rb |10digit|8digit| | 8| 10| | 9| 11| | 10| 12| | 11| 13| | 12| 14| | 13| 15| | 14| 16| | 15| 17| | 16| 20| | 17| 21| | 18| 22| | 19| 23| | 20| 24| | 21| 25| | 22| 26| | 23| 27| | 24| 30| | 25| 31| | 26| 32| | 27| 33| | 28| 34| | 29| 35| | 30| 36| | 31| 37| | 32| 40|
実行例4
16進数のみ。16から32。
Digittabledefine
radix 16 from 16 to 32
出力
|10digit|16digit| | 16| 0010| | 17| 0011| | 18| 0012| | 19| 0013| | 20| 0014| | 21| 0015| | 22| 0016| | 23| 0017| | 24| 0018| | 25| 0019| | 26| 001a| | 27| 001b| | 28| 001c| | 29| 001d| | 30| 001e| | 31| 001f| | 32| 0020|
実行例5
不整値いろいろ。
対応していない奇数の場合
Digittabledefine
radix 3 from 16 to 32
出力
digit_table_dsl.rb:17:in `instance_eval': invalid Object for radix_type. The Object can use only 2/6/8/16. your input value is 3 (ArgumentError) from digit_table_dsl.rb:22:in `radix' from (eval):1:in `execute' from digit_table_dsl.rb:17:in `instance_eval' from digit_table_dsl.rb:17:in `execute' from digit_table_dsl.rb:63:in `<main>'
Kernel#Integerに対応していない値を指定した場合
Digittabledefine
radix 2 from 'invlalid' to 32
出力
digit_table_dsl.rb:17:in `instance_eval': invalid value for Integer(): "invlalid" (ArgumentError) from digit_table_dsl.rb:26:in `from' from (eval):2:in `execute' from digit_table_dsl.rb:17:in `instance_eval' from digit_table_dsl.rb:17:in `execute' from digit_table_dsl.rb:63:in `<main>'