ぬまのそこ

namazuのゆるいエンジニアブログ

Ruby YAMLファイルを読み込んでメソッドで値を取れるクラス

今日したこと

にゃーん って感じ。 カルシウム足りないんじゃないのってくらい情緒不安定だった。

愚痴ってもしょうがないので、さっきちょっと作ったコードを載せて今日はおわり。

やりたかったこと

env1:
  obj1: hello
  obj2:
    obj2_1: hello
    obj2_2: hello
env2:
  obj1: hello

こんなconfi.ymlみたいなのがある。 一番大本が環境毎。 そのしたにyaml

これを

config = Config.new('env1')
config.obj1 #=> 'hello'
config.obj2.obj2_1 #=> 'hello'

みたいに使いたかった。 

これをやるGemは当然既にあるだろうし、いつもはEasySettingsっていうGemを使っている。 ただせっかくだし自前でちょろっとつくった。たいした用途ではなかったしね。

とりあえず思いついたのがこれ。 メソッド名で出すからハイフンが使えなかったり色々あるけど。

require 'yaml'

class Config
  CONFIG_FILE_NAME = './config.yml'

  def initialize(env)
    config = YAML.load_file(CONFIG_FILE_NAME)
    hash_method_define = Proc.new do |res, obj|
      obj.each do |k, v|
        if(v.kind_of?(Hash))
          res_child = Object.new
          hash_method_define.call(res_child, v)
          res.define_singleton_method(k) { res_child }
        else
          res.define_singleton_method(k) { v }
        end
      end
    end
    hash_method_define.call(self, config[env])
  end
end

Rubyってすごいんですね。

どうでもいいはなし

にゃー。 あした大学行きたくない。 

RSpecでFileIOをスタブにする

今日したこと

  • テスト書いたり諸々

単体テスト、疲れたときに書くと意外と良いかもしれない。 今日は其の途中で得た知見を書いてお茶を濁す

FileIOをする処理をRSpec

テスト対象

def file_write
  File.open('hoge.txt', 'w') do |f|
    f.print 'fuga'
  end
end

こんなコードをテストしなきゃいけないとき、ファイルに色々と書き出されてしまうので面倒くさい。 また書き出されたファイルの中身が正しいかチェックするのにもファイルを読まないと行けないので大変。

こんな処理の時はStringIOが便利。 またRSpecでは既存の処理を簡単に置き換えられる。

file = StringIO.new('', 'w')
allow(File).to receive(:open).and_yield(file)

file_write
except(file.string).to eq 'fuga'

FileのOpen時にブロックに渡される引数をStringIOに置き換える。 これでファイルに書き込むはずだった物が簡単に取り出せる。

便利。

ブロックでは無く戻り値を利用してるとき

f = File.open('hoge.txt' , 'w')
f.print 'fuga'
f.close

なときは。。

allow(File).to receive(:open).and_return(file)

こうする。 戻り値を置き換えることもできる。

どうでもいいはなし

Rubyの勉強していたおかげでこのallowとかその辺の処理がどう実現されているのか大体想像が付くようになってきて嬉しい。 進捗と相談しつつテストを書いて完成度を上げていきたい。 

Wordpressの管理ユーザをDB叩いて追加する方法

今日したこと

WordpressのUserをDBから追加する方法

備忘録的に残しておく, 基本的には wp_usersにレコードを追加し wp_usermetaに必要情報を追加する

https://www.web-plains.com/?p=419 こちらを参考にクエリ組み立てさせてもらった。

insert into wp_users (
  user_login,
  user_pass,
  user_registered,
  user_email
) values (
  "namazu",
  md5("nyannyan"),
  "2018-01-25 00:26:19",
  "xxxxxx@mofumofu.com"
);

これでユーザが登録できる user_idはauto_incrementなのでこれで作成したあとに確認.

wp_usermetadataテーブルには

  • wp_dashboard_quick_press_last_post_id
  • dismissed_wp_pointers
  • wp_capabilities
  • wp_user_level
  • nickname

これを設定すればいいらしい

insert into wp_usermeta (
  user_id,
  meta_key,
  meta_value
) values (
  3,
  'wp_dashboard_quick_press_last_post_id',
  5
) , (
  3,
  'dismissed_wp_pointers',
  ''
) , (
  3,
  'wp_capabilities',
  'a:1:{s:13:"administrator";b:1;}'
) , (
  3,
  'wp_user_level',
  '10'
) , (
  3,
  'nickname',
  'namazu'
)

3のところは先程作ったユーザIDに変更。 これで管理者が作れる。

どうでもいいはなし

ちょっと立て込んでて時間がないので今日はこれで。

Nginxの設定がミスっていたらしく,POSTするとなぜかGatewayTimeoutが発生する謎現象に悩まされている。 つらい。

Wordpressのドメインを変更時にCSS等読み込めなくなるやつの対処

きょうしたこと

  • nginxと奮闘
  • バグ潰し
  • 卒論発表原稿用意,発表練習

Wordpressドメイン変更

WordpressドメインをDBに保持してる。 なので初回インストール時の後に、ドメインを変えるとDBのデータが変わらずそのままになってしまい、 CSS等のリンクがすべて古いものになってしまう。

これの対処

以前何回かやったことがあるが,今日は忘れていて,ググったら変なサイトばかり引っかかって辛かったのでやり方をメモしておく.

サイト URL の変更 - WordPress Codex 日本語版

基本的にはこれに従えば良い。

DBの変更で対応する場合は以下を行う

変更SQL

対象となる場所は wp_optionsテーブルのoption_namesiteurl及びhomeのレコードになる

もちろん記事の中で直リンしていたりすると色々なところで変換が必要(よくある全部置換)ではあるのだが、基本的にはここを変えるだけでCSSとかはなんとかなる。

以下クエリで対象は抽出できる

select * from wp_options where option_value = '設定したドメイン( ex https://wordpress.hoge.com)'
mysql> select * from wp_options where option_value = 'https://wp.bs-lab.ks.serviice.cloud.teu.ac.jp'; 
+-----------+-------------+----------------------------------------------+----------+
| option_id | option_name | option_value                                 | autoload |
+-----------+-------------+----------------------------------------------+----------+
|         1 | siteurl     | https://wp.bs-lab.ks.service.cloud.teu.ac.jp | yes      |
|         2 | home        | https://wp.bs-lab.ks.service.cloud.teu.ac.jp | yes      |
+-----------+-------------+----------------------------------------------+----------+
2 rows in set (0.00 sec)

こんな感じで出てくるのでこれをUpdateすればいい

update wp_options  set option_value = '新しいドメイン( ex https://mofumofu.com )'  where option_name = 'siteurl' OR option_name = 'home';

これで使えるようになる.

Nginxで$argsや$request_uriの情報をサブリクエストに渡したい

今日したこと

  • nginxと眠い頭で格闘. 結局負けました. 今日はその話を書きます
  • rubyとか色々書いた
  • 卒論発表スライドアウトラインバトル

Nginxでサブリクエス

http_auth_requestモジュールを用いると,サブリクエストを行ってその結果でアクセス制御が実現できる.

{
   server_name: hoge.com;
   listen 443;

   location / {
        auth_request /auth;
        error_page 401 = @login_page;
        proxy_pass http://app.hoge.com;
   }
   location /auth {
        proxy_pass http://login.hoge.com/auth;
   }

   location @login_page {
        return 302 https://login.hoge.com/login;
   }
}

こんな具合に. 便利なもん.

ここで、http://hoge.com/?type=hogeのようなアクセスを考える, また認証エンドポイントであるlogin.hoge.comtypeを内部で利用し401or200を決定する. この場合サブリクエストにtypeの値をなんとかして付与しなければならない.

以下試したこと

proxy_passに$argsを含める

proxy_pass http://login.hoge.com/auth$is_arg$args; こう設定する. $argsはリクエストのクエリパラメータが入るnginxの変数,なのでこれで渡るのではないだろうか?

=> 無理 $argsは空となり, http://login.hoge.com/auth に単なるGetが送られる.

$argsを一度適当な変数に代入しておきそれをHTTPヘッダに付与させ送ろう

set $original_arg $args;
location /auth {
   proxy_set_header X-Originl-Args $original_arg;
   proxy_pass http://login.hoge.com/auth;
}

これでlogin側ではHTTPヘッダから$argを取り出せるのではないか??

=> 無理 $original_argは空となり,HTTPヘッダ付与は行われない.

惨敗

そもそもサブリクエスト時にコンテキストが切り替わり、$argとか$request_uriとかはすべてサブリクエスト時の値に切り替わっているっぽいことがわかった. なのでいつもの変数ではもともとの値を引くことができない?. そこそこ調べてもこうしてやった例を見つけることが出来なかった. Lua拡張を用いればおそらく引くことができるとは思うがデフォルトのNginxは変えたくない.

結局,色々とごにょって今回の問題には対処した時間もなかったし...

しかし以下のような解決策っぽいのを発見, 思いついた.

そもそも$request_uriなら取れるよ!

調べているうちに,argsは無理だが,request_uriなら引きずり出す方法を発見した.

stackoverflow.com

つまり. locationを正規表現でマッチさせ,そこでキャプチャを行う. キャプチャしたものを変数にsetする. あとは良しなにやる なるほど思いつかなかった....

proxyを2段階にすればいいじゃない

ブラウザ => nginx(1段) => nginx(2段) => (login , app) という構成にすることでも対応できることに気づいた.

nginxの1段目では$argを適当なHTTPヘッダにセットする,そして上で2段目へproxyする. 2段目はいつもどおりauth_requestを行った後APPへProxyする.

サブリクエスト時にHTTPヘッダは引き継がれるので,loginには(特に落とさなければ)HTTPヘッダとして1段目でセットしたHTTPヘッダが飛んでいくし, サブリクエスト時に参照できなくなる変数は$argとか$request_uriとかだけなので,2段目のnginxのコンフィグではサブリクエストの時の設定で1段目でセットしたヘッダを参照することができる.

どうでもいいはなし

運用中のサービスのDBの根本的変更をやるためにDBのデータとか,各所の設定値とかをすべて変更するスクリプトとか書いててめんどいなーっておもった. もっとちゃんとしっかり考えて作れよーという反省.

ボスがなんか、なまずくんに上げるよー!!っていってくれた.

これは君がほしそうだから!!! って... まぁ机の上や某所の壁にけもみみタペストリーとかグッズ大量においてればそうなるか... ふたりともかわいい>_<!

JavaでBasicライク言語のインタプリタ実装 #4 実行編

今日書くこと

昨日の式の解析ができれば構文解析は完成したような物なので、 構文解析はできたということで今日は実行の方の解説をしようと思います。 なんかあれな理由で時間がないので、軽く書くだけ。

過去記事リンクは以下

namazu-tech.hatenablog.com

namazu-tech.hatenablog.com

namazu-tech.hatenablog.com

今日で完結です。 (適当なネタが無くなってしまった)

実行

文字通りプログラムを実行することです。 例えば a=1というコードであれば実際に、変数aを用意し、そこに1という値を代入する処理を行います。

また`PRINT "Hello"であればPRINT関数を実行します。

構文木インスタンスと実行

構文木の要素(LoopBlockクラスなど)はそれ自体がどんな実行をすれば良いかを知っています。 つまり、それぞれのクラスに対して、evel()メソッドを実装して、これを呼んでやればインスタンスがよしなにやってくれるようにします。 オブジェクト指向って感じがしますね。

例えば、LoopBlockクラスのevel()メソッドは

  • 子のCondNodeをevalしループ実行判定
  • 実行するならStmtListを実行
  • 繰り返す

という処理を行います。

ExprNodeクラスのeval()メソッドであれば単純に式を計算して1つの値を返すようにします。

それぞれの構文要素のクラスにあった計算方法をそれぞれのクラスのevalとして実装することで ProgramNodeのevalを呼ぶと再帰的に実行が行えるようになります。 (programはstmtlistのevalを行い、stmtlistのevalは子それぞれのevalを順々に呼び・・・と)

実行とEnviroment

これまで適当に扱ってきたEnviromentクラスが本気を出し始めます。

このクラスには、

  • 変数テーブル
  • 関数テーブル

を持たせ、変数を問い合わせする処理や新規定義する処理、関数を呼び出す処理などを行います。

実行中の変数テーブルとか関数テーブルをここに作っていくことになります。

関数定義を動的に行うことは今回の言語仕様にないので、関数テーブルについてはコンストラクタで決め打ちにしてしまえばいいと思います。

実装

AssigneStmt.java

代入文のクラスであるAssigneStmtならば、こんな感じにします。 式を評価して、その結果をnameの変数に代入

   
    @Override
    public Value eval() {
        // 変数テーブルに入れる
        Value value = exprNode.eval();
        // Debug
        // System.out.println(name + "に" + value.getSValue() + "をいれる!");
        env.setVarValue(name, value);
        return null;
}

ここのenvはenviromentで変数テーブルを持ちます。

Enviroment.java

/**
 * 実行環境を持たせる.
 *
 * 変数名表とか関数テーブルとか
 */
public class Environment {
    
    private LexicalAnalyzer input;
    
    private Hashtable<String, Value> varTable;
    private Hashtable<String, Function> funcTable;
    
    public Environment(LexicalAnalyzer my_input) {
        input = my_input;
        varTable = new Hashtable<>();
        funcTable = new Hashtable<>();
        
        funcInit();
    }
    
    private void funcInit() {
        funcTable.put("PRINT", new PrintFunction());
        funcTable.put("SQRT", new SqrtFunction());
    }
    
    public LexicalAnalyzer getInput() {
        return input;
    }
    
    public void setVarValue(LexicalUnit varName, Value value) {
        varTable.put(varName.getValue().getSValue(), value);
    }
    
    public Value getVarValue(LexicalUnit varName) {
        Value res = varTable.get(varName.getValue().getSValue());
        if (res == null) {
            throw new IllegalStateException("存在しない変数を参照しようとしました!");
        }
        return res;
    }
    
    public void setFunc(LexicalUnit funcName, Function function) {
        funcTable.put(funcName.getValue().getSValue(), function);
    }
    
    public Function getFunction(LexicalUnit name) {
        Function res = funcTable.get(name.getValue().getSValue());
        if (res == null) {
            throw new IllegalStateException("存在しない関数を参照しようとしました!");
        }
        return res;
    }
}

こんなかんじで変数テーブルと関数テーブルを用意しておき、使えば良いでしょう。

関数定義と実行

/**
 * 平方根出す関数
 */
public class SqrtFunction extends Function {
    @Override
    public Value eval(ExprListNode arg) {
        Value val = arg.getValue(0);
        return new ValueImpl(Math.sqrt(val.getDValue()));
    }
}

こんなクラスをつくっておいて、enviromentのコンストラクタで、関数テーブルに登録します。

これで、

FunctionCallNodeクラスのevalで

   @Override
    public Value eval() {
        Function function = env.getFunction(name);
        return function.eval(exprList);
    }

こんな感じで使えるようになります。

IFBlockNode.java

IF文などの条件判定ノードやループノードの実行は小のCondNodeの評価を行って、ループを行ったり、どのStmtを実行するか決めたりすれば良いです。 IF文はIF-ELSEで続いてるときがあって、ちょっと面倒だったけど

@Override
    public Value eval() {
        Value condValue = cond.eval();
        if (condValue.getBValue()) {
            getThenNode().eval();
        } else {
            
            // 連続するIF文を実行する
            // ELSEIFですべてつながってるので順繰りにやっていく
            // どれか実行されたらevalElseIfNodeの戻り値がfalseで抜ける.
            int n = 0;
            while (evalElseIfNode(n)) {
                n++;
            }
            
            // 本体のELSEを実行する.
            if (getElseNode() != null) {
                getElseNode().eval();
            }
        }
        return null;
    }

こんなんでいけます。

さいご

駆け足で実行系を書いちゃったけどこんなんでいけるはずです。

構文木つくれたのなら余裕?。 

そろそろレポートの提出締め切りが近いようですが頑張って下さい。

書いてて思ったけどそんな難しくないけど実装量多いなって思いました。 

アノテーションで文法定義だけ書いてよしなにやる実装とかみたので、そうやるとSyntaxNode部分は大分分量を削れるかなぁと今になって思う。

まぁ頑張ってささっと終わらせちゃって下さい

どうでもいいはなし

今日はコミケ1日目でした。

こんなんで、東京テレポートの方の橋の上に列が来たときに並ぶなんていう物見遊山勢してました。

お使いがけっこうあって、2つくらいいけば終わるはずが、普通に回ってしまいました。 これなら速く入ってさっさと終わらせとけばよかったなぁって今では反省しています。

明後日が本番なので体調に気をつけて準備していきたいですね。

今日は昔から買ってる毎度の藍様本、なぜか1日目にいたけもみみクラスタさん、性癖買いしたFGO本とかを買いました。 昔1日目の始発で狙ってた狐サークルさんが艦これをずっと書いててしょぼーんです。 」

てか3日目のオリジナルの半分をどっかに隔離して1日目、2日目にやれば大分平和になると思うんだけどなんでしないんだろう? 

JavaでBasicライク言語のインタプリタ実装 #3 構文解析編-2

今日書くこと

JavaでBasicライク言語のインタプリタを作る。 講義の解説。 今日は式の構文解析構文木生成のところを細かく書きます。

これまでの記事は↓

namazu-tech.hatenablog.com

namazu-tech.hatenablog.com

今回は式のクラスExprNodeの実装をざっと書きます。

式と構文木

式はBNF記法で

<expr> ::= 
    <expr> <ADD> <expr>
    | <expr> <SUB> <expr>
    | <expr> <MUL> <expr>
    | <expr> <DIV> <expr>
    | <SUB> <expr>
    | <LP> <expr> <RP>
    | <NAME>
    | <INTVAL>
    | <DOUBLEVAL>
    | <LITERAL>
    | <call_func>


<call_func>   ::=
     <NAME> <LP> <expr_list> <RP>

こう書けます。 つまり

SQRT((b * b) - (4 * a * c))

こんな式に対して

SQRT[SUB[MUL[b,b],MUL[MUL[4,a],c]]]

こんな結果が返ってくるようにすればいい。

式解析の攻略法

とりあえず逆ポーランド記法について知らなければググる

qiita.com

この辺を参考に理解しましょう。 これがわかればあとは実装すればいいです。

1 + 2 * 3 みたいな標準入力を渡されて計算するプログラムをとりあえず書いてみるのがいいでしょう。

実装

実際にコードにするときには 関数呼び出し中のカッコと普通のカッコとか、単項演算子の扱いとかがやっかい。

カッコがきたら対のカッコまでスタックからpopするとか、SUBが真っ先にやってくるor記号の後にSUBがくる場合は単項演算子適用対象 みたいなことを考えると実直実装が面倒くさくなってくる。

/**
 * 式を計算.
 *
 * 1式を読みつつ逆ポーランド記法変換する
 * 2逆ポーランド記法の計算アルゴリズムを利用しツリーを生成する
 * 3生成したツリーを自信のインスタンスに適用する
 */
public class ExprNode extends Node {
    
    // 式の末端ならこれ
    private ExprEnd expression;
    
    // 2項式ならこっち
    private ExprEnd ope;
    private ExprNode left, right;
    
    public ExprNode(NodeType type, Environment env) {
        super(type, env);
    }
    
    public ExprNode(Environment env) {
        super(NodeType.EXPR, env);
    }
    
    public static Node isMatch(Environment env, LexicalUnit first) {
        FirstCollection fc = new FirstCollection(
                LexicalType.SUB,
                LexicalType.RP,
                LexicalType.NAME,
                LexicalType.INTVAL,
                LexicalType.DOUBLEVAL,
                LexicalType.LITERAL);
        
        if (fc.contains(first)) {
            Node node = new ExprNode(NodeType.EXPR, env);
            return node;
        }
        
        return null;
    }
    
    // Exprに含まれうる字句
    private static final Set<LexicalType> allowSet = new HashSet() {
        {
            add(LexicalType.NAME);
            
            add(LexicalType.LP);
            add(LexicalType.RP);
            
            add(LexicalType.INTVAL);
            add(LexicalType.DOUBLEVAL);
            add(LexicalType.LITERAL);
            
            add(LexicalType.ADD);
            add(LexicalType.SUB);
            add(LexicalType.MUL);
            add(LexicalType.DIV);
        }
    };
    
    // FunctionCallの時の括弧を式中の括弧か関数を閉じる括弧か見分けるための数値.
    boolean fcCallEnv = false;
    private int nest = 0;
    
    @Override
    public boolean parse() {
        
        // 読みつつ逆ポーランド記法へ
        // LexicalUnitのままだと困るからExprEnd 式末端ノードとして変換する
        
        // 演算子の連続は単項演算子. 数値が来るべき所に単項演算子きたらそれは単項演算子
        // ExprEndは記号を拡張出来るので単項演算子情報をつけておく
        
        // 逆ポーランド記法への変換のアルゴリズムはいくつかあるけどStackを使うやつが楽かな
        
        // 結果格納用
        Deque<ExprEnd> output = new ArrayDeque<>();
        
        // 単項演算子判別用. trueのときにSUBがきたらそれは単項演算子のSUB
        boolean expectValue = true;
        // 単項演算子バッファ.
        Deque<ExprEnd> singleOpeEnd = new ArrayDeque<>();
        
        // 演算子Stack
        Deque<LexicalUnit> opeStack = new ArrayDeque<>();
        while (true) {
            final LexicalUnit in = peekLexicalUnit();
            final LexicalType inType = in.getType();
            
            // 式構成要素じゃないものが飛んできた
            if (!allowSet.contains(inType)) {
                break;
            }
            env.getInput().get();
            
            // CALL SUBの処理
            if (inType == LexicalType.NAME) {
                // NAME -> RP はCALL SUB
                LexicalUnit test = env.getInput().get();
                if (test.getType() == LexicalType.RP) {
                    
                    //  FCNのparse呼び出すために字句を戻す.
                    env.getInput().unget(in);
                    env.getInput().unget(test);
                    
                    // CALL SUBは必ず1つ値が返るから数値と同じと見なせる
                    // 優先順位0でOutputする
                    FunctionCallNode fcN = new FunctionCallNode(NodeType.FUNCTION_CALL, env);
                    if (!fcN.parse()) {
                        return false;
                    }
                    
                    // CALL SUB全体を末端ノードと見なして出力する
                    output.add(new ExprEnd(fcN, env));
                    
                    expectValue = false;
                    
                    continue;
                }
                env.getInput().unget(test);
            }
            
            if (inType == LexicalType.RP) {
                // RP ( はopeStackに入れる
                opeStack.push(in);
                
                nest++;
                
                continue;
            }
            
            if (inType == LexicalType.LP) {
                // LPが来たらopeStackからRPが出てくるまで出力する
                
                // 関数呼び出し内での閉じ括弧対応
                if (fcCallEnv && nest == 0) {
                    env.getInput().unget(in);
                    break;
                }
                
                LexicalUnit tmp;
                if (opeStack.size() == 0) {
                    return false;
                }
                while ((tmp = opeStack.pop()).getType() != LexicalType.RP) {
                    output.add(new ExprEnd(tmp, env));
                    
                    // ()の組み合わせがおかしい -> 構文エラー
                    if (opeStack.size() == 0) {
                        return false;
                    }
                }
                
                nest--;
                
                continue;
            }
            
            final int priority = getPriority(inType);
            if (priority != 0) {
                
                // 単項演算子が現れたとき.
                if (expectValue == true) {
                    if (inType == LexicalType.SUB) {
                        // 単項演算子の優先度は最大
                        ExprEnd subEnd = new ExprEnd(in, env);
                        subEnd.isSingleOpe = true;
                        singleOpeEnd.add(subEnd);
                    }
                    continue;
                }
                
                // 優先度最高の単項演算子を全部出す.
                while (singleOpeEnd.size() != 0) {
                    output.add(singleOpeEnd.poll());
                }
                
                // 優先順位の高い演算子をすべて出し
                // スタックにいれる
                while (opeStack.size() != 0 && getPriority(opeStack.peekFirst().getType()) >= priority) {
                    output.add(new ExprEnd(opeStack.pop(), env));
                }
                expectValue = true;
                opeStack.push(in);
            } else {
                // 数値はそのまま出力
                output.add(new ExprEnd(in, env));
                
                expectValue = false;
            }
        }
        // 優先度最高の単項演算子を全部出す.
        while (singleOpeEnd.size() != 0) {
            output.add(singleOpeEnd.poll());
        }
        // 演算子バッファ全出力
        while (opeStack.size() != 0) {
            output.add(new ExprEnd(opeStack.pop(), env));
        }
        
        // DEBUG
        // output.forEach(System.out::println);
        // System.out.println("");
        
        // --------------
        // ここまででoutputに逆ポーランド記法で投入される
        // --------------
        
        // outputをツリーにする.
        
        // この方式思いっきり計算できる
        Deque<ExprNode> nodeStack = new ArrayDeque<>();
        while (output.size() != 0) {
            ExprNode in = output.pop();
            
            if (in instanceof ExprEnd) {
                ExprEnd endElm = (ExprEnd) in;
                if (endElm.isOpe) {
                    
                    // 単項演算子
                    if (endElm.isSingleOpe == true) {
                        if (nodeStack.size() < 1) {
                            // 計算不能だから ^ ^;
                            return false;
                        }
                        ExprNode expr = new ExprNode(env);
                        expr.left = nodeStack.pop();
                        expr.ope = endElm;
                        
                        nodeStack.push(expr);
                        continue;
                        
                    }
                    
                    // 2項演算子
                    if (nodeStack.size() < 2) {
                        // 計算不能だから^^;
                        return false;
                    }
                    
                    ExprNode expr = new ExprNode(env);
                    expr.right = nodeStack.pop();
                    expr.left = nodeStack.pop();
                    expr.ope = endElm;
                    
                    nodeStack.push(expr);
                    continue;
                    
                }
                // 演算子以外はスタックに入れる
                nodeStack.push(endElm);
            }
            
        }
        
        // 何で計算しないでツリーにしちゃったのか謎だけど構文木つくらないとだからね・・・
        // とりあえずこれでツリー完成
        
        // 自分のインスタンスに適用する
        ExprNode expr = nodeStack.pop();
        if (expr.isEnd()) {
            this.expression = (ExprEnd) expr;
        } else {
            this.left = expr.left;
            this.right = expr.right;
            this.ope = expr.ope;
        }
        
        // DEBUG:
        // System.out.println(expr);
        
        return true;
    }
    
    private int getPriority(LexicalType type) {
        // 0 は数値
        switch (type) {
        case MUL:
            return 3;
        case DIV:
            return 3;
            
        case SUB:
            return 2;
            
        case ADD:
            return 2;
            
        case RP:
        case LP:
            return -1;
            
        case INTVAL:
        case DOUBLEVAL:
        case LITERAL:
        case NAME:
            return 0;
            
     default:
            throw new IllegalArgumentException("優先度なんてないLexicalType:" + type.name());
        }
    }
    
    /**
    * 与えたLexicalTypeが式のなかで使われる演算子ならtrue
    *  + or - or / or *
    */
    public boolean isOpe(LexicalType lt) {
        Set<LexicalType> opeSet = new HashSet() {
            {
                add(LexicalType.ADD);
                add(LexicalType.SUB);
                add(LexicalType.DIV);
                add(LexicalType.MUL);
            }
        };
        
        return opeSet.contains(lt);
    }
    
    /**
    * このExprが末端かどうか返す.
    * (枝分かれしないかどうか)
    */
    protected boolean isEnd() {
        // TODO: もうちょっとちゃんとチェック
        if (left == null) {
            return true;
        }
        return false;
    }
    

    @Override
    public String toString() {
        // 5 -> 5 , a - 1 -> -[a, 1]
        
        // 末端
        if (isEnd()) {
            return expression.toString();
        }
        
        // 単項
        if (this.right == null) {
            return String.format("%s[%s]", ope.toString(), left.toString());
        }
        
        // 二項
        return String.format("%s[%s,%s]", ope.toString(), left.toString(), right.toString());
    }
    
}

/**
 * Exprの末端ノード
 */
class ExprEnd extends ExprNode {
    
    // 演算子Nodeか?
    boolean isOpe;
    
    // 単項演算子か?
    boolean isSingleOpe;
    
    // 字句
    LexicalUnit val;
    
    // 関数呼び出し値か?
    boolean isFcval;
    // 関数呼び出しNode
    FunctionCallNode fcval;
    
    public ExprEnd(LexicalUnit val, Environment env) {
        super(env);
        this.val = val;
        this.isOpe = isOpe(val.getType());
    }
    
    public ExprEnd(FunctionCallNode fcN, Environment env) {
        super(env);
        this.fcval = fcN;
        this.isFcval = true;
        isOpe = false;
    }
    
    @Override
    public String toString() {
        if (isFcval) {
            return fcval.toString();
        }
        if (isOpe) {
            return val.getType().name();
        }
        return val.getValue().getSValue();
    }
    
    @Override
    protected boolean isEnd() {
        return true;
    }
}

まぁこんなんでいけます。

さいご

時間がなくて書きなぐり感すごい。

あしたはもっと書きなぐりになる。 明日は実行系、eval()メソッドの実装の話を書きます。