ANTLR の C言語 で、 Lexer Parser を作るノウハウ的なものが、
Googleしてもでて来なかったので、記録を残して起きます。
で、なおかつ、Emscriptenを利用します。 なので、ブラウザーで動作します。 スゴ!!
ANTLR は、Parser を作るツール
ANTLRって何それ? 的な人向けに、軽めの解説です。
ANTLRは、LexerやParserを生成するツールです。 プログラム言語の開発とかで使えます。
ざっくりいうと、「文章を読み込んで、木構造にして返す。」これが、「Lexer->Parser」の中身です。
軽めの説明は以下もみてね
http://kyorohiro.blogspot.jp/2017/09/bake-tiny-calc-app-at-antlr3-and-clang.html
Emscriptenを使えば、C言語をJS化できる。
Emscriptenは、LLVMのバイトコードをJavaScriteに変換するツールです。
http://kripken.github.io/emscripten-site/
C言語の資産を利用しまくれます。
以下とか見ると、クールな使い方が沢山おがめます。
https://github.com/kripken/emscripten/wiki/Porting-Examples-and-Demos
実際に何か作って見る
Install Mac
Antlr4は、C++のParserは出力できるけど。 C言語は出力できないので、
Antlr3を使いました!!
brew install antlr3
brew install libantlr3c
Emscripten用に C Runtimeをビルドしておく。
brew で ゲットし C Runtimeは、Macようなので、C言語ようにビルドしなおす必要があります。
以下に起きましたので、参考までにどうぞ!!
https://github.com/kyorohiro/libantlr3c_emscripten
Write Grammer
grammar Calc;
options {
language = C;
output = AST;
ASTLabelType=pANTLR3_BASE_TREE;
}
gprog: (gstat {
pANTLR3_STRING s = $gstat.tree->toStringTree($gstat.tree);
printf(" tree \%s\n", s->chars);
})+;
gstat: gexpr NEWLINE -> gexpr | NEWLINE ->;
gexpr: gmultExpr (( PLUS^ | MINUS^ ) gmultExpr)*;
gmultExpr: gatom (TIMES^ gatom)*;
gatom: INT | '('! gexpr ')'!;
INT: '0'..'9'+;
NEWLINE: '\r'? '\n';
WS: (' '|'\t')+{$channel = HIDDEN;};
PLUS: '+';
MINUS: '-';
TIMES: '*';
Generate Parser
antlr3 Calc.g3
Write Main
# include <stdio.h>
# include <antlr3.h>
# include "CalcLexer.h"
# include "CalcParser.h"
int run(pANTLR3_BASE_TREE tree) {
pANTLR3_COMMON_TOKEN tok = tree->getToken(tree);
if(tok == NULL) {
int len = tree->getChildCount(tree);
int ret = 0;
for(int i = 0; i < len; i++) {
int v = run((pANTLR3_BASE_TREE)tree->getChild(tree, i));
printf("ret = %d\r\n", v);
ret += v;
}
return ret;
}
switch(tok->type) {
case INT:{
char* s = (char*) tree->getText(tree)->chars;
//printf("int : t:%d; v=%s\r\n", tok->type, s);
return atoi(s);
}
break;
case PLUS:
//printf("plus : %d\r\n", tok->type);
return run((pANTLR3_BASE_TREE)tree->getChild(tree, 0)) + run((pANTLR3_BASE_TREE)tree->getChild(tree, 1));
case MINUS:
//printf("minus : %d\r\n", tok->type);
return run((pANTLR3_BASE_TREE)tree->getChild(tree, 0)) - run((pANTLR3_BASE_TREE)tree->getChild(tree, 1));
case TIMES:
//printf("times : %d\r\n", tok->type);
return run((pANTLR3_BASE_TREE)tree->getChild(tree, 0)) * run((pANTLR3_BASE_TREE)tree->getChild(tree, 1));
break;
default:
printf("def : %d\r\n", tok->type);
}
return 0;
}
int main(int argc, char** argv) {
char* src = "1+1\r\n""1+1\r\n";
pANTLR3_INPUT_STREAM input = antlr3StringStreamNew((pANTLR3_UINT8)src, ANTLR3_ENC_8BIT, strlen(src),(pANTLR3_UINT8)"ABCD");
pCalcLexer lex = CalcLexerNew(input);
pANTLR3_COMMON_TOKEN_STREAM tokens = antlr3CommonTokenStreamSourceNew(ANTLR3_SIZE_HINT, TOKENSOURCE(lex));
pCalcParser parser = CalcParserNew(tokens);
CalcParser_gprog_return r = parser->gprog(parser);
pANTLR3_BASE_TREE tree = r.tree;
int v = run(tree);
printf("result: %d \r\n", v);
return 0;
}
Build
emcc -c CalcLexer.c -o CalcLexer.bc -I. -I/usr/local/Cellar/libantlr3c/3.4_1/include -DHAVE_MALLOC_H
emcc -c CalcParser.c -o CalcParser.bc -I. -I/usr/local/Cellar/libantlr3c/3.4_1/include -DHAVE_MALLOC_H
emcc -c Main.c -o Main.bc -I. -I/usr/local/Cellar/libantlr3c/3.4_1/include -DHAVE_MALLOC_H
emcc -o a.js Main.bc CalcLexer.bc CalcParser.bc libantlr3c.bc -DHAVE_MALLOC_H -O2
で、完了です!!
おわり!!
PS
- kyorohiroのBlogでも軽く紹介してます
http://kyorohiro.blogspot.jp/2017/09/bake-tiny-calc-app-at-antlr3-and-clang.html - kyorohiro作の Emscripten用の Cランタイム と、サンプルコードです
https://github.com/kyorohiro/libantlr3c_emscripten