tk555 diary

プログラミング、もしくはそれ以外のこと書きます。

[Java]再帰降下法で四則演算・変数へ代入・標準出力に出力できるDSLを作る

再帰降下法で四則演算するプログラムはネット上にかなりあったので、それに+αするものを自分で書いてみた。


やりたいこと

const saihu=1000;
const orange=150;
const apple=200;
const amari=saihu-(orange*2+apple);
put amari;

を食べさせ標準出力に

500

と表示させたい。

BNFを決める

<program>::= <statement>*
<statement>::= (<const statement> | <put statement>) ";" "\n"*
<const statement>::= "const" <space> <var> "=" <expr>
<put statement>::= "put" <space> <expr>
<space>::= " "+
<var>::= ["a".."z","A".."Z"]+
<expr>::= <term> [('+'|'-') <term>]*
<term>::= <factor>[('*'|'/')<factor>]*
<factor> ::= <number> |'(' <expr> ')'
<number> ::=([0..9]+) | <var>

多分こんな感じ。

実装する

public class Parser{
    public static void main(String[] args)throws Exception {
        Path source=Paths.get(Parser.class.getResource("/source.txt").toURI());
        String s=Files.readAllLines(source).stream().collect(Collectors.joining("\n"));
        System.out.println(s);
        Parser p=new Parser(s);
        p.program();

        
    }
    private final Set<Character> lower=new HashSet<>(){
        {
            for(char c='a';c<='z';c++)add(c);
        }
    };
    private final Set<Character> upper=new HashSet<>(){
        {
            for(char c='A';c<='Z';c++)add(c);
        }
    };
    private final String str;
    private int cursor;
    private final Var var=new Var();

    public Parser(String str){
        this.str=str;
    }
    /**
     * カーソルを次に進める
     */
    public void next(){
        cursor++;
    }
    public int nextInt(){
        int ret=Character.getNumericValue(nextChar());
        while(hasNext()&&Character.isDigit(getChar())){
            ret*=10;
            ret+=Character.getNumericValue(nextChar());
        }
        return ret;
    }
    public String nextString(){
        StringBuilder sb=new StringBuilder();
        char c=nextChar();
        if(c==' ')throw new IllegalStateException("空白がある");
        sb.append(c);
        while(hasNext()&&getChar()!=' '){
            sb.append(nextChar());
        }
        return sb.toString();
    }
    public String getString(){
        final int originCursor=cursor;
        String s=nextString();
        cursor=originCursor;
        return s;
    }
    
    public String nextVar(){
        StringBuilder sb=new StringBuilder();
        char c=nextChar();
        if(!allowedLetterForVar(c)){
            throw new SyntaxException("変数名にならない");
        }
        sb.append(c);
        while(hasNext()&&allowedLetterForVar(getChar())){
            sb.append(nextChar());
        }
        return sb.toString();
    }
    public char getChar(){
        return str.charAt(cursor);
    }
    public char nextChar(){
        char c=getChar();
        next();
        return c;
    }
    public boolean hasNext(){
        return cursor<str.length();
    }
    public void program(){
        while(hasNext()){
            statement();
        }
    }
    public void statement(){
        if(isConstStatement()){
            constStatement();
        }else if(isPutStatement()){
            putStatement();
        }else{
            throw new SyntaxException("<const statement>でも<put statement>でもない");
        }
        char c=nextChar();
        if(c!=';'){
            throw new SyntaxException("セミコロンがない");
        }
        while(hasNext()&&getChar()=='\n'){
            next();
        }
    }
    private boolean isConstStatement(){
        return getString().equals("const");
    }
    private boolean isPutStatement(){
        return getString().equals("put");
    }
    public void constStatement(){
        String s=nextString();
        if(!s.equals("const")){
            throw new SyntaxException("const識別子がない");
        }
        space();
        String varName=var();
        char c=nextChar();
        if(c!='='){
            throw new SyntaxException("=でない");
        }
        int expr=expr();
        var.put(varName, expr);        
    }
    public void space(){
        char c=getChar();
        if(c!=' '){
            throw new SyntaxException("空白でない");
        }
        while(hasNext()&&getChar()==' '){
            next();
        }
    }
    /**
     * 変数名を返す
     */
    public String var(){
        StringBuilder sb=new StringBuilder();
        char c=nextChar();
        if(!allowedLetterForVar(c)){
            throw new SyntaxException("変数名が間違っている");
        }
        sb.append(c);
        while(hasNext()&&getChar()!='='){
            c=nextChar();
            if(!allowedLetterForVar(c)){
                throw new SyntaxException("変数名が間違っている");
            }
            sb.append(c);
        }
        return sb.toString();
    }
    private boolean allowedLetterForVar(char c){
        return lower.contains(c)||upper.contains(c);
    }
    public int expr(){
        int x=term();
        while(hasNext()){
            switch(getChar()){
                case '+':
                    next();
                    x+=term();
                    break;
                case '-':
                    next();
                    x-=term();
                    break;
                default:
                    return x;
            }
        }
        return x;
    }
    public int term(){
        int x=factor();
        while(hasNext()){
            switch(getChar()){
                case '*':
                    next();
                    x*=factor();
                    break;
                case '/':
                    next();
                    x/=factor();
                    break;
                default:
                    return x;
            }
        }
        return x;
    }
    public int factor(){
        if(getChar()=='('){
            next();
            int x=expr();
            if(getChar()!=')'){
                throw new SyntaxException("閉じかっこが見つからない");
            }
            next();
            return x;
        }
        return number();
    }
    public int number(){
        if(Character.isDigit(getChar())){
            return nextInt();
        }
        String s=nextVar();
        if(!var.has(s)){
            throw new SyntaxException("まだ存在しない変数名です");
        }
        return var.get(s);
    }
    public void putStatement(){
        String s=nextString();
        if(!s.equals("put")){
            throw new SyntaxException("put識別子がない");
        }
        space();
        int x=expr();
        System.out.println(x);
    }
}

結果

ちゃんと出力できた。 うれしい。

所感

最初はjavaccとかAntlrとか使ってみようかとか考えていたけど、この程度なら直で書いても苦にならないと感じた。 今後はラムダ式とか関数宣言とかも追加したものも作ってみたい。