6802用アセンブラの構文解析部分を、将来の拡張性を見据えて、自前で作成するのではなく字句解析にJFlex、構文解析にCUPを使って作成することにしました。この記事ではそれぞれの導入と、それらを使った構文解析部分のプロトタイプを紹介します。
目次 [非表示]
インストール
以下の環境にインストールするためのメモを記します。
- Windows 10
- Eclipse Version Mars.2 Release 4.5.2
JFlex
ダウンロードページからtar.gzまたはzipファイルをダウンロードし、適当な場所に保存します。2017年2月26日現在、最新版は1.6.1。tar.gzもzipも内容は同じです。
以下ではc:\tools\jflex
に展開したものとします。
C:\tools\jflex\
+--bin\ (start scripts)
+--doc\ (FAQ and manual)
+--examples\
+--byaccj\ (calculator example for BYacc/J)
+--cup\ (calculator example for cup)
+--interpreter\ (interpreter example for cup)
+--java\ (Java lexer specification)
+--simple-maven\ (example scanner built with maven)
+--standalone-maven\ (a simple standalone scanner,
built with maven)
+--zero-reader\ (Readers that return 0 characters)
+--lib\ (precompiled classes, skeleton files)
+--src\
+--main\
+--cup\ (JFlex parser spec)
+--java\
+--java_cup\
+--runtime\ (source code of cup runtime classes)
+--jflex\ (source code of JFlex)
+--gui\ (source code of JFlex UI classes)
+--jflex\ (JFlex scanner spec)
+--resources\ (messages and default skeleton file)
+--test\ (unit tests)
Eclipseだけで開発する場合は不要ですが、DOSプロンプトから実行する場合も想定して、展開してできたフォルダ内のbin/flex.bat (今回の例ではc:\tools\jflex\bin\flex.bat
)を自分の環境に合うように修正します。修正箇所は環境変数の設定部分です。
@echo off
REM Please adjust JFLEX_HOME to suit your needs
REM (please do not add a trailing backslash)
set JAVA_HOME=C:\Program Files\Java\jdk1.8.0_51
set JFLEX_HOME=c:\tools\jflex
java -Xmx128m -jar %JFLEX_HOME%\lib\jflex-1.6.1.jar %1 %2 %3 %4 %5 %6 %7 %8 %9
JAVA_HOME
の行はJDKのあるディレクトリ、JFLEX_HOME
はJFlexを展開したディレクトリに合わせて変更します。
必要に応じて上記のbatファイルの場所を環境変数PATH
に追加してください。
CUP
ダウンロードページからtar.gzファイルをダウンロードし、適当な場所に保存します。2017年2月26日現在、最新版はjava-cup-bin-11b-20160615。
以下ではc:\tools\cup
に展開したものとします。
C:\tools\cup\
java-cup-11b.jar
java-cup-11b-runtime.jar
CUPをインストールして試そうとするとUnsupported major.minor version 52.0
が発生することがありました。CUPはJava 1.8が前提なのかもしれません。ここの原因究明は深入りせず、今回の開発環境はJava 1.8に統一しました。
Eclipseの設定
JFlexとCUPのソースコードとJavaコードの生成先をどこにするか悩み、次のようにしてみました。
src/
AsmScanner.flex # 字句解析のソース
AsmParser.cup # 構文解析のソース
+--- jp
+-- asamomiji
+-- assembler
AsmScanner.java # AsmScanner.flexから生成されたもの
AsmParser.java # AsmParser.cupから生成されたもの
AsmParserSym.java # AsmParser.cupから生成されたもの
自作ソース
Eclipseのワークスペースのソースフォルダ直下に.flexと.cupを配置し、これらを処理して生成される.javaファイルを自作ソースコードのあるパッケージフォルダ(パッケージ名がjp.asamomiji.assembler
なのでjp/asamomiji/assembler
)を指定しています。自作ソースと自動生成ソースが同じ場所に混在するのがちょっと汚いのですが…。
次にAnt用のbuild.xml
を作成します。
<?xml version="1.0" encoding="utf-8" ?>
<project name="Compiler" default ="compile" basedir=".">
<property name="src" location="src" />
<property name="classes" location="bin" />
<property name="jflexlib" location="c:/tools/jflex/lib" />
<property name="cuplib" location="c:/tools/cup" />
<property name="cupruntimelib" location="${cuplib}/java-cup-11b-runtime.jar" />
<taskdef name="jflex" classname="jflex.anttask.JFlexTask" classpath="${jflexlib}/jflex-1.6.1.jar" />
<taskdef name="cup" classname="java_cup.anttask.CUPTask" classpath="${cuplib}/java-cup-11b.jar" />
<target name="generate">
<jflex file="${src}/AsmScanner.flex" destdir="${src}" />
<cup srcfile="${src}/AsmParser.cup" destdir="${src}" parser="AsmParser"
interface="true" locations="false" />
</target>
<target name="compile" depends="generate">
<javac srcdir="${src}" destdir="${classes}" classpath="${cupruntimelib}" includeantruntime="false" encoding="utf-8" />
</target>
<target name="clean">
<delete file="${src}/jp/asamomiji/assembler/AsmParser.java" />
<delete file="${src}/jp/asamomiji/assembler/AsmParserSym.java" />
<delete file="${src}/jp/asamomiji/assembler/AsmScanner.java" />
</target>
</project>
ポイントは、JFlexとCUPから生成されたJavaコードをもとに全体をビルドする必要があるため、compileタスクで明示的にJavaソースコードのコンパイルを実行しているところです。その他、パス名は自分の環境に合わせて修正してください。
さらにJavaのビルドパスに、外部JARとしてCUPのランタイムライブラリ(java-cup-11b-runtime.jar)を追加します。
最後にEclipseにCUP用プラグインを追加します。ダウンロードページの指示にしたがって、Eclipseの「ヘルプ→更新ソフトウエアのインストール」からインストールしてください。
JFlex用のプラグインは無いみたいです。。。一応cup-lex-eclipseというのが見つかってはいるのですが、うまくインストールできませんでした。
サンプル構文解析コード
字句解析 (AsmScanner.flex)
昔ながらのlexやGNUのflexを触ったことのある人なら、書式はそれほど難しくないと思います。JFlexはCUPとの連携をするための構文が用意されているので、CUPを使っておけば楽できそうです。
で、昔ながらのアセンブラの書式だと、数値の表現方法が今どきから見るとちょっと特殊です。
- $1000 ('$'を前置すると16進数)
- 1000H ('H'を後置すると16進数)
- %00101111 ('%'を前置すると2進数)
- @0777 ('@'を前置すると8進数)
これに、"0x"を前置したら16進数、みたいな規則もやはり欲しいので、この辺りの処理に手間取りそうです。
/*
* Scanner for 6802 assembler
*/
package jp.asamomiji.assembler;
import java_cup.runtime.*;
%%
%class AsmScanner
%unicode
%cup
%line
%column
%state HEXNUMBER
%{
private SymbolFactory sf;
public AsmScanner(java.io.Reader in, SymbolFactory sf) {
this(in);
this.sf = sf;
}
private int intValue(String s, int radix) {
try {
return Integer.parseInt(s, radix);
}
catch (NumberFormatException e) {
throw new RuntimeException("exception occured in intValue(): " + s);
}
}
private boolean isByteRange(int value) {
return value <= 255 ? true : false;
}
%}
Identifier = [:jletter:] [:jletterdigit:]*
LineTerminator = \r|\n|\r\n
InputCharacter = [^\r\n]
WhiteSpace = [ \t\f]
Comment = ; {InputCharacter}* {LineTerminator}
HexadecimalPrefix = "0x"
HexadecimalNumber = [0-9a-fA-F]+
%%
<YYINITIAL> {
/* 予約語 */
"ADDA" { return sf.newSymbol("ADDA", AsmParserSym.ADDA); }
"ADDB" { return sf.newSymbol("ADDB", AsmParserSym.ADDB); }
"ABA" { return sf.newSymbol("ABA", AsmParserSym.ABA); }
"X" { return sf.newSymbol("X", AsmParserSym.REG_X); }
/* 記号 */
":" { return sf.newSymbol("COLON", AsmParserSym.COLON); }
"," { return sf.newSymbol("COMMA", AsmParserSym.COMMA); }
"#" { return sf.newSymbol("HASH", AsmParserSym.HASH); }
{LineTerminator} { return sf.newSymbol("NEWLINE", AsmParserSym.NEWLINE); }
{Identifier} { return sf.newSymbol("IDENTIFIER", AsmParserSym.IDENTIFIER, yytext()); }
{HexadecimalPrefix} { yybegin(HEXNUMBER); }
{Comment} { /* ignore */ }
{WhiteSpace} { /* ignore */ }
}
<HEXNUMBER>
{HexadecimalNumber} { yybegin(YYINITIAL);
int value = intValue(yytext(), 16);
return isByteRange(value)
? sf.newSymbol("BYTE_VALUE", AsmParserSym.BYTE_VALUE, value)
: sf.newSymbol("SHORT_VALUE", AsmParserSym.SHORT_VALUE, value); }
<<EOF>> { return sf.newSymbol("<<EOF>>", AsmParserSym.EOF); }
構文解析 (AsmParser.cup)
こちらの方はyaccやbisonとは若干書式が異なりますが、それらを使ったことのある人には特に難しくはないと思います。書式で苦労するよりも、むしろ構文のデバッグの方がはまるでしょう。こんな短い構文でも、シフト・還元衝突の解決に時間を費やしています……。
/*
* Parser for 6802 assembler
*/
package jp.asamomiji.assembler;
import java_cup.runtime.*;
import java.util.Vector;
class AsmParser;
parser code {:
private Vector<Instruction> instructions = new Vector<Instruction>();
:};
/* 終端記号 */
terminal COLON, COMMA, HASH, NEWLINE;
terminal ADDA, ADDB, ABA;
terminal Integer BYTE_VALUE;
terminal Integer SHORT_VALUE;
terminal String IDENTIFIER;
terminal REG_X;
/* 非終端記号 */
nonterminal Vector<Instruction> program;
nonterminal line_list;
nonterminal String label;
nonterminal Instruction line, operation;
/* 文法 */
start with program;
program ::= line_list
{: RESULT = instructions; :}
;
line_list ::= line_list line:inst
{: if (inst != null) {instructions.add(inst);} :}
|
line:inst
{: if (inst != null) {instructions.add(inst);} :}
;
line ::= label:name operation:inst NEWLINE
{: inst.setLabel(new Label(name)); RESULT = inst; :}
|
label:name NEWLINE
{: RESULT =new DummyInstruction(); RESULT.setLabel(new Label(name)); :}
|
operation:inst NEWLINE
{: RESULT = inst; :}
|
NEWLINE
{: /* ignore */ :}
;
label ::= IDENTIFIER:str COLON
{: RESULT = str; :}
;
operation ::= ADDA HASH BYTE_VALUE:value
{: RESULT = new ADDA(Instruction.MODE_IMMEDIATE, value.intValue()); :}
|
ADDA BYTE_VALUE:value
{: RESULT = new ADDA(Instruction.MODE_DIRECT, value.intValue()); :}
|
ADDA BYTE_VALUE:value COMMA REG_X
{: RESULT = new ADDA(Instruction.MODE_INDEXED, value.intValue()); :}
|
ADDA SHORT_VALUE:value
{: RESULT = new ADDA(Instruction.MODE_EXTENDED, value.intValue()); :}
|
ADDB HASH BYTE_VALUE:value
{: RESULT = new ADDB(Instruction.MODE_IMMEDIATE, value.intValue()); :}
|
ADDB BYTE_VALUE:value
{: RESULT = new ADDB(Instruction.MODE_DIRECT, value.intValue()); :}
|
ADDB BYTE_VALUE:value COMMA REG_X
{: RESULT = new ADDB(Instruction.MODE_INDEXED, value.intValue()); :}
|
ADDB SHORT_VALUE:value
{: RESULT = new ADDB(Instruction.MODE_EXTENDED, value.intValue()); :}
|
ABA
{: RESULT = new ABA(); :}
;
次に向けて
次は字句解析・構文解析系を完成させようと思います。もう少し詳細なコードの説明は次回に。
これまでの記録
- 6802用アセンブラを作る(1) はじめに
- 6802用アセンブラを作る(2) JFlexとCUPの導入
- 6802用アセンブラを作る(3) プロトタイプ完成
- 6802用アセンブラを作る(4) JR-100でのテスト
- 6802用アセンブラを作る(5) 一応完成
コメント