LaTeXのlistings向けに,Arduino言語用のスタイルの設定を作ってみました。
Arduino IDEでの見た目と同じような感じでシンタックスハイライトを行います。
ライセンスはMIT licenseでお願いします。
TeX Live 2016 で動作を確認しました。
ダウンロード: listings-arduino.sty 1.8.5a
上記サンプルのファイル一式(TeX Live 2016で動作確認。Ubuntuではtexlive-fonts-extraも入れて):
listings-arduino-sample
背景:
うちの研究室の卒業論文はソースコードも掲載してもらうことにしています。
Arduinoを使う研究も多いのでそのソースコードも良く載せるのですが,その際listingsではlanguage=Cかlanguage=C++を使っていました。しかしどうせならArduino IDEと同じ見た目の方がカッコ良いので,作ってみることにしました。
どのトークンをシンタックスハイライトするかですが,Arduino IDEのソースコードには,シンタックスハイライトのためのルールファイルのようなものが含まれているようです。
例えば,build/shared/lib/keywords.txt にTSV形式で以下のように書かれています。
# LITERAL1 specifies constants HIGH LITERAL1 Constants RESERVED_WORD_2 LOW LITERAL1 Constants RESERVED_WORD_2 INPUT LITERAL1 Constants RESERVED_WORD_2 INPUT_PULLUP LITERAL1 Constants RESERVED_WORD_2 OUTPUT LITERAL1 Constants RESERVED_WORD_2 … delay KEYWORD2 Delay delayMicroseconds KEYWORD2 DelayMicroseconds digitalWrite KEYWORD2 DigitalWrite digitalRead KEYWORD2 DigitalRead
これを見ると第1コラムが終端記号で,第2コラムがその種類のようです。
これらを機械的に抽出して色付けをしました。
ちなみに keywords.txt というファイルは複数個存在します。
Arduino-1.8.5 時点では以下の個数あるようです。
hardware/arduino/avr/libraries/EEPROM/keywords.txt
hardware/arduino/avr/libraries/SoftwareSerial/keywords.txt
hardware/arduino/avr/libraries/SPI/keywords.txt
hardware/arduino/avr/libraries/Wire/keywords.txt
hardware/arduino/avr/libraries/HID/keywords.txt
build/shared/lib/keywords.txt
libraries/Stepper/keywords.txt
libraries/TFT/src/utility/keywords.txt
libraries/TFT/keywords.txt
libraries/Ethernet/keywords.txt
libraries/GSM/keywords.txt
libraries/WiFi/keywords.txt
これらから想像付くように,ライブラリそれぞれに keywords.txt は存在し,予約語だけではなくライブラリのクラス名にももれなく色を付ける方針のようなので,Arduino IDEがバージョンアップして標準ライブラリが増えるたびにこのlistings-arduino.styも
同期させる必要があります。
将来的にそのような気力が続くかどうかはともかく,混乱しないようにこのスタイルファイルのバージョンもそれが依拠するArduino IDEのバージョンと同じにしておきます。
さらに,Arduino IDEはユーザが後から入れたライブラリにも色をつけてくれます。
これはライブラリ配布者が keywords.txt を同梱すればそれを見に行くというしかけのようです。
例えば私の環境だと,
C:/Users/ユーザ名/AppData/Local/Arduino15/packages
C:/Users/ユーザ名/Documents/Arduino/libraries
にもライブラリがインストールされていますので,Arduino IDEではここの keywords.txt も考慮されて色が付きます。
というわけで,どこにArduino IDEとライブラリとがインストールされているかを指定すれば,そこから keywords.txt を探し出してそこからトークンを拾って listings-arduino.sty を生成する,というスクリプトが以下です(Ruby)。
#!/usr/bin/ruby # coding: utf-8 # generate listings-arduino.sty from Arduino IDE class ArduinoIDEParser def initialize(*ref_dirs) # enum of "keywords.txt"s. # Note: These depend on installed libraries. @keywords_txts = [] ref_dirs.each do |dir| @keywords_txts += Dir.glob(File.join(dir, "**/keywords.txt")).to_a end # table of the tokens(LITERAL1, KEYWORD1, ...) @tokens = Hash.new{|h, k| h[k] = []} @keywords_txts.each do |f| File.readlines(f).map(&:chomp). select{|l| l[0] != "#" && !l.strip.empty? && l.match(/^[a-zA-Z_]/)}. map{|l| l.split(/\s+/)}.each do |l| @tokens[l[1]] << l[0] unless @tokens[l[1]].include?(l[0]) end end @ref_dirs = ref_dirs end def header header_str = <<~'EOS' % Arduino language style for listings package __GENERATED_FROM__ % usage: \lstinputlisting[language=Arduino]{somesketch.ino} % __KEYWORDS_TXTS__ \usepackage[dvipdfmx]{color} \definecolor{lst-arduino-sngq}{rgb}{0.000, 0.592, 0.612} \definecolor{lst-arduino-dblq}{rgb}{0.000, 0.361, 0.373} \definecolor{lst-arduino-literal1}{rgb}{0.000, 0.592, 0.612} % BLUE #00979C \definecolor{lst-arduino-keyword1}{rgb}{0.827, 0.329, 0.000} % ORANGE #D35400 \definecolor{lst-arduino-keyword2}{rgb}{0.827, 0.329, 0.000} % ORANGE #D35400 \definecolor{lst-arduino-keyword3}{rgb}{0.447, 0.557, 0.000} % GREEN #728E00 \definecolor{lst-arduino-directive}{rgb}{0.369, 0.427, 0.012} \definecolor{lst-arduino-commentc}{rgb}{0.584, 0.647, 0.651} \definecolor{lst-arduino-commentcpp}{rgb}{0.263, 0.310, 0.329} \definecolor{lst-arduino-identifier}{rgb}{0.000, 0.000, 0.000} \lstdefinelanguage{Arduino} { showspaces=false, showstringspaces=false, showtabs=false, morestring=[s][\color{lst-arduino-sngq}]{'}{'}, morestring=[s][\color{lst-arduino-dblq}]{"}{"}, keywordstyle=[1]{\color{lst-arduino-literal1}},% LITERAL1(BLUE) keywordstyle=[2]{\color{lst-arduino-keyword1}\bfseries},% KEYWORD1(ORANGE) keywordstyle=[3]{\color{lst-arduino-keyword2}},% KEYWORD2(ORANGE) keywordstyle=[4]{\color{lst-arduino-keyword3}},% KEYWORD3(GREEN) directivestyle={\color{lst-arduino-directive}}, morecomment=[s][\color{lst-arduino-commentc}]{/*}{*/}, morecomment=[l][\color{lst-arduino-commentcpp}]{//}, identifierstyle={\color{lst-arduino-identifier}}, EOS header_str. sub(/__GENERATED_FROM__/, @ref_dirs.map{|x| "% Generated from: #{x}\n"}.join). sub(/__KEYWORDS_TXTS__/, @keywords_txts.map{|x| "% #{x}\n"}.join) end def footer <<~'EOS' moredelim=*[directive]\#, moredirectives={ define,elif,else,endif,error,if,ifdef,ifndef,line,% include,pragma,undef,warning }, sensitive=true } EOS end def middle tex_lines = [] %w(LITERAL1 KEYWORD1 KEYWORD2 KEYWORD3).each.with_index(1) do |k, i| tex_lines << "morekeywords=[#{i}]{" tex_lines << " %%% #{k}" ary = @tokens[k].each_slice(5).to_a ary.take(ary.size-1).map{|a| " " + a.join(",") + ","}.each do |l| tex_lines << l end tex_lines << " " + ary.last.join(",") tex_lines << "}," end tex_lines.map{|s| " #{s}\n"}.join end def to_listings header + middle + footer end end print ArduinoIDEParser.new("C:/Program Files (x86)/Arduino", "C:/Users/foo/AppData/Local/Arduino15/packages", "C:/Users/foo/Documents/Arduino/libraries").to_listings