M.Hiroi's Home Page

Go Language Programming

お気楽 Go 言語プログラミング入門

[ PrevPage | Golang | NextPage ]

電卓プログラムの作成 (2)

前回は四則演算を行う簡単な電卓プログラムを作りました。今回は電卓プログラムに変数と関数の機能を追加してみましょう。

●構文木の組み立て

前回は構文解析のときに式の計算をいっしょに行っていましたが、今後の拡張に備えて、簡単な「構文木」を組み立てるようにプログラムを修正してみましょう。最初に、構文木 (式) を表すデータ型 Expr を定義します。

リスト : 構文木の定義

// 値
type Value float64

// 構文木の型
type Expr interface {
    Eval() Value
}

// 評価
func (e Value) Eval() Value {
    return e
}

型 Expr はインターフェースとして定義します。メソッド Eval は式 (Expr) を計算して値 (float64) を返します。値にもメソッド Eval を定義したいので、type で Value という型を定義します。これで Value も Expr として扱うことができます。

次に、構文木を表す構造体を定義します。次のリストを見てください。

リスト : 構文木を表す構造体

// 単項演算子
type Op1 struct {
    code rune
    expr Expr
}

func newOp1(code rune, e Expr) Expr {
    return &Op1{code, e}
}

// 二項演算子
type Op2 struct {
    code rune
    left, right Expr
}

func newOp2(code rune, left, right Expr) Expr {
    return &Op2{code, left, right}
}

Op1 は単項演算子を、Op2 は二項演算子を表します。Op1 は 1 つの式を、Op2 は 2 つの式を格納します。簡単な例を示しましょう。

1 + 2 - 3  => Op2{'-', Op2{'+', 1, 2}, 3}
1 + 2 * 3  => Op2{'+', 1, Op2{'*', 2, 3}}
1 + -2 * 3 => Op2{'+', 1, Op2{'*', Op1{'-', 2}, 3}}

1 + 2 * 3 は * の優先順位が高いので、Op2{'*', ...} が Op2{'+', ...} の子になります。このように、Op2 は二分木と同じ構造になります。

プログラムは次のようになります。

リスト : 構文解析

// 因子
func factor(lex *Lex) Expr {
    switch lex.Token {
    case '(':
        lex.getToken()
        e := expression(lex)
        if lex.Token != ')' {
            panic(fmt.Errorf("')' expected"))
        }
        lex.getToken()
        return e
    case '+':
        lex.getToken()
        return newOp1('+', factor(lex))
    case '-':
        lex.getToken()
        return newOp1('-', factor(lex))
    case scanner.Int, scanner.Float:
        var n float64
        fmt.Sscan(lex.TokenText(), &n)
        lex.getToken()
        return Value(n)
    case scanner.Ident:
        text := lex.TokenText()
        if text == "quit" {
            panic(text)
        }
        fallthrough
    default:
        panic(fmt.Errorf("unexpected token: %v", lex.TokenText()))
    }
}

// 項
func term(lex *Lex) Expr {
    e := factor(lex)
    for {
        switch lex.Token {
        case '*':
            lex.getToken()
            e = newOp2('*', e, factor(lex))
        case '/':
            lex.getToken()
            e = newOp2('/', e, factor(lex))
        default:
            return e
        }
    }
}

// 式
func expression(lex *Lex) Expr {
    e := term(lex)
    for {
        switch lex.Token {
        case '+':
            lex.getToken()
            e = newOp2('+', e, term(lex))
        case '-':
            lex.getToken()
            e = newOp2('-', e, term(lex))
        default:
            return e
        }
    }
}

expression, term, factor は Expr を返すように修正します。factor の単項演算子の処理は、factor を再帰呼び出しして、その返り値を Op1 に格納して返します。数値の生成は値 n を Value に型変換して返します。term と expression も簡単で、左側にある項 (因子) を Op2 の left に、右側の項 (因子) を Op2 の right に格納するだけです。

●式の評価

次は構文木を評価して式の値を求めるメソッド Eval を作りましょう。次のリストを見てください。

リスト :  式の計算

// 単項演算子の評価
func (e *Op1) Eval() Value {
    v := e.expr.Eval()
    if e.code == '-' {
        v = -v
    }
    return v
}

// 二項演算子の評価
func (e *Op2) Eval() Value {
    x := e.left.Eval()
    y := e.right.Eval()
    switch e.code {
    case '+': return x + y
    case '-': return x - y
    case '*': return x * y
    case '/': return x / y
    default:
        panic(fmt.Errorf("invalid op code"))
    }
}

Op1 の場合、式 expr を Eval で評価し、その結果を変数 v にセットします。Eval を評価するとき、ポリモーフィズムが働いて適切なメソッドが選択されることに注意してください。たとえば、式 e が Value であれば、Eval はその値を返します。e.code が '-' の場合は符号を反転して返します。'+' の場合はそのまま返します。なお、単項演算子 + は、構文解析の段階で削除することもできます。

Op2 の場合、式 left と right を Eval で評価し、その結果をそれぞれ変数 x, y にセットします。あとは、e.code で指定された演算を行って、その結果を返すだけです。

●式の入力と評価

最後に式を入力して評価する処理を修正します。次のリストを見てください。

リスト : 式の入力と評価

func toplevel(lex *Lex) (r bool) {
    r = false
    defer func(){
        err := recover()
        if err != nil {
            mes, ok := err.(string)
            if ok && mes == "quit" {
                r = true
            } else {
                fmt.fprintln(os.Stderr, err)
                for lex.Token != ';' {
                    lex.getToken()
                }
            }
        }
    }()
    for {
        fmt.Print("Calc> ")
        lex.getToken()
        e := expression(lex)
        if lex.Token != ';' {
            panic(fmt.Errorf("invalid expression"))
        } else {
            fmt.Println(e.Eval())
        }
    }
    return r
}

expression の返り値は構文木 e になります。それを Eval で評価して、結果を Println で表示するだけです。あとのプログラムは簡単なので説明は割愛します。詳細は プログラムリスト1 をお読みください。興味のある方は実際にプログラムを動かして、いろいろ試してみてください。

●変数

今まで作成した電卓は、計算結果を表示したあとそれを保持していないので、計算結果を再利用することができません。一般の電卓のように、計算結果を記憶しておくメモリ機能があると便利です。この機能を「変数 (variable)」として実現することにします。プログラミング言語で言えば、大域変数 (グローバル変数) と同じ機能になります。

変数を実装するのであれば、変数に値を代入する操作が必要になります。文法に「文」を定義する、つまり「代入文」を追加する方法もありますが、今回は簡単な電卓プログラムなので、代入演算子 "=" を用意して式の中で処理することにしましょう。代入演算子は右辺の式の値を左辺の変数に代入するので、文法は次のように表すことができます。

[EBNF]
  式   = 代入式 | 式1.
代入式 = 変数, "=", 式.
 式1  = 項, { ("+" | "-"), 項 }.
  項   = 因子, { ("*" | "/"), 因子 }.
 因子  = 数値 | ("+" | "-"), 因子 | "(", 式, ")" | 変数.
 変数  = 識別子

[注意] 数値と識別子の定義は省略

演算子 = は他の演算子と違って右結合になることに注意してください。このため、他の演算子よりも優先順位を低くし、右辺の式の評価を優先して行います。そして、その結果を変数にセットします。文法では、式を 代入式 | 式1 に変更し、代入式で演算子 = の処理を行います。式1は今までの式の定義と同じです。これで演算子 = の優先順位を低くすることができます。あとは代入式の処理で、右辺の式を先に評価して、その結果を変数にセットすればいいわけです。

それから、因子に「変数」を追加します。変数の定義は「識別子」とし、識別子は Go 言語 (Scanner) の識別子と同じ規則とします。Scheme 入門で作成したプログラムと違って、今回のプログラムは構文木を組み立ててからそれを評価するので、構文解析の段階では変数をそのまま返すだけで OK です。

●関数

次は文法に関数を追加しましょう。関数の処理は「因子」に追加します。

[EBNF]
  式   = 代入式 | 式1.
代入式 = 変数, "=", 式.
 式1  = 項, { ("+" | "-"), 項 }.
  項   = 因子, { ("*" | "/"), 因子 }.
 因子  = 数値 | ("+" | "-"), 因子 | "(", 式, ")" | 変数 | 関数, "(", 引数リスト, ")".
 変数  = 識別子
 関数  = 識別子

引数リスト = 式, { ",", 式 }.

[注意] 数値と識別子の定義は省略

関数の名前は識別子とし、そのあとに引数をカッコで囲んで渡します。カッコの中は「引数リスト」として定義します。これは「式」をカンマで区切って並べたもので、一般的な手続き型言語の関数呼び出しと同じ形式になります。

ただし、変数と関数は同じ識別子なので、このままでは区別することができません。この場合、簡単な方法が 2 つあります。ひとつは関数として登録されている識別子を関数とする方法、もうひとつは次のトークンが左カッコであれば関数とする方法です。今回は前者の方法を採用することにしましょう。

●変数と関数の操作

それではプログラムを作ります。最初に、変数と関数を表すデータ型を定義します。次のリストを見てください。

リスト : データ型の定義

// 変数
type Variable string

// 組み込み関数
type Func interface {
    Argc() int
}

// 引数が 1 つの関数
type Func1 func(float64) float64

func (f Func1) Argc() int {
    return 1
}

// 引数が 2 つの関数
type Func2 func(float64, float64) float64

func (f Func2) Argc() int {
    return 2
}

// 組み込み関数の構文木
type App struct {
    fn Func
    xs []Expr
}

func newApp(fn Func, xs []Expr) *App {
    return &App{fn, xs}
}

変数を表す型は Variable で、その実体は string です。Variable にはメソッド Eval を定義し、Eval を呼び出すと変数の値を求めるようにプログラムします。Func は組み込み関数を表す型 (インターフェース) です。Argc は引数の個数を返すメソッドです。Func1 は引数の個数が 1 の関数を、Func2 は引数の個数が 2 の関数を表します。どちらも type で関数の型に別名を付けているだけです。構造体 App は組み込み関数を評価するための構文木です。fn は評価する組み込み関数で、xs に引数を格納したスライスをセットします。

次は、変数と関数を格納する大域変数を定義します。

リスト : 関数と変数のアクセス関数

// 大域的な環境
var globalEnv = make(map[Variable]Value)

// 組み込み関数表
var funcTable = make(map[string]Func)

// 組み込み関数の初期化
func initFunc() {
    funcTable["sqrt"]  = Func1(math.Sqrt)
    funcTable["sin"]   = Func1(math.Sin)
    funcTable["cos"]   = Func1(math.Cos)
    funcTable["tan"]   = Func1(math.Tan)
    funcTable["sinh"]  = Func1(math.Sinh)
    funcTable["cosh"]  = Func1(math.Cosh)
    funcTable["tanh"]  = Func1(math.Tanh)
    funcTable["asin"]  = Func1(math.Asin)
    funcTable["acos"]  = Func1(math.Acos)
    funcTable["atan"]  = Func1(math.Atan)
    funcTable["atan2"] = Func2(math.Atan2)
    funcTable["exp"]   = Func1(math.Exp)
    funcTable["pow"]   = Func2(math.Pow)
    funcTable["log"]   = Func1(math.Log)
    funcTable["log10"] = Func1(math.Log10)
    funcTable["log2"]  = Func1(math.Log2)
}

大域変数 globalEnv は変数名とその値を格納する map です。組み込み関数は大域変数 funcTable の map に格納します。funcTable は関数 initFunc で初期化します。パッケージ math に定義されている関数を funcTable に登録するだけです。

●構文解析

次は構文解析を修正します。まず最初に、代入演算子の処理を expression に追加します。次のリストを見てください。

リスト : expression の修正

// 式
func expr1(lex *Lex) Expr {
    e := term(lex)
    for {
        switch lex.Token {
        case '+':
            lex.getToken()
            e = newOp2('+', e, term(lex))
        case '-':
            lex.getToken()
            e = newOp2('-', e, term(lex))
        default:
            return e
        }
    }
}

func expression(lex *Lex) Expr {
    e := expr1(lex)
    if lex.Token == '=' {
        v, ok := e.(Variable)
        if ok {
            lex.getToken()
            return newAgn(v, expression(lex))
        } else {
            panic(fmt.Errorf("invalid assign form"))
        }
    }
    return e
}

演算子 +, - の処理は関数 expr1 で行い、演算子 = の処理を expression で行います。expression は最初に expr1 を評価して、その返り値を変数 e にセットします。lex.Token が '=' の場合は代入式の処理を行います。型アサーションで e の値が Variable かチェックして、そうでなければ panic でエラーを送出します。そして、expression を呼び出して右辺の式を評価して、その返り値を Agn にセットします。expr1 は今までの expression と同じです。

次は関数 factor を修正します。

リスト : 因子の修正

func factor(lex *Lex) Expr {
    switch lex.Token {
    case '(':
        lex.getToken()
        e := expression(lex)
        if lex.Token != ')' {
            panic(fmt.Errorf("')' expected"))
        }
        lex.getToken()
        return e
    case '+':
        lex.getToken()
        return newOp1('+', factor(lex))
    case '-':
        lex.getToken()
        return newOp1('-', factor(lex))
    case scanner.Int, scanner.Float:
        var n float64
        fmt.Sscan(lex.TokenText(), &n)
        lex.getToken()
        return Value(n)
    case scanner.Ident:
        name := lex.TokenText()
        lex.getToken()
        if name == "quit" {
            panic(name)
        }
        v, ok := funcTable[name]
        if ok {
            xs := getArgs(lex)
            if len(xs) != v.Argc() {
                panic(fmt.Errorf("wrong number of arguments: %v", name))
            }
            return newApp(v, xs)
        } else {
            return Variable(name)
        }
    default:
        panic(fmt.Errorf("unexpected token: %v", lex.TokenText()))
    }
}

lexToken が Ident で、その値が "quit" でない場合、変数または関数呼び出しの処理を行います。最初に funcTable を調べて、識別子 name が組み込み関数かチェックします。そうであれば、組み込み関数を呼び出す App を生成します。引数の取得は関数 getArgs で行います。あとは引数の数をチェックして、newApp(v, xs) を返します。関数でなければ変数なので Variable(name) を返します。

次は引数を取得する関数 getArgs を作ります。

リスト : 引数の取得

func getArgs(lex *Lex) []Expr {
    e := make([]Expr, 0)
    if lex.Token != '(' {
        panic(fmt.Errorf("'(' expected"))
    }
    lex.getToken()
    if lex.Token == ')' {
        lex.getToken()
        return e
    }
    for {
        e = append(e, expression(lex))
        switch lex.Token {
        case ')':
            lex.getToken()
            return e
        case ',':
            lex.getToken()
        default:
            panic(fmt.Errorf("unexpected token in argument list"))
        }
    }
}

getArgs はカンマで区切られた式を expression で評価し、それをスライス e に格納して返します。最初に左カッコ '(' があることを確認します。次のトークンが右カッコ '(' であれば e を返します。この場合、引数がないのでエラーになります。そのあと、for ループで expression を評価してスライス e に追加します。case で lex.Token をチェックして、右カッコ ')' であれば e を返します。カンマ ',' であれば、まだ引数があるので次の式を評価します。そうでなければ、式に誤りがあるのでエラーを送出します。

●式の評価 (2)

次は式を評価するメソッド Eval を作ります。

リスト : 式の評価

// 変数の評価
func (v Variable) Eval() Value {
    val, ok := globalEnv[v]
    if !ok {
        panic(fmt.Errorf("unbound variable: %v", v))
    }
    return val
}

// 代入演算子の評価
func (a *Agn) Eval() Value {
    val := a.expr.Eval()
    globalEnv[a.name] = val
    return val
}

// 組み込み関数の評価
func (a *App) Eval() Value {
    switch f := a.fn.(type) {
    case Func1:
        x := float64(a.xs[0].Eval())
        return Value(f(x))
    case Func2:
        x := float64(a.xs[0].Eval())
        y := float64(a.xs[1].Eval())
        return Value(f(x, y))
    default:
        panic(fmt.Errorf("function Eval error"))
    }
}

変数 v の評価は簡単です。globalEnv から v の値を求めるだけです。このとき、v が globalEnv にない場合はエラーを送出します。代入演算子の評価も簡単です。式 a.expr を Eval で評価して、その値を変数 val にセットします。それを globalEnv[a.name] にセットし、return で val を返します。

組み込み関数を評価する App の処理も簡単です。型 switch で関数の実体 f を求めます。あとは、引数 xs を Eval で評価して、関数 f に与えて呼び出すだけです。

あとの修正は簡単なので説明は割愛します。詳細は プログラムリスト2 をお読みください。

●実行例

それでは実行してみましょう。

Calc> a = 10;
10
Calc> a;
10
Calc> a * 10;
100
Calc> (b = 20) * 10;
200
Calc> b;
20
Calc> x = y = z = 0;
0
Calc> x;
0
Calc> y;
0
Calc> z;
0
Calc> p = p + 1;
unbound variable: p
Calc> q = 1;
1
Calc> q;
1
Calc> q = q + 1;
2
Calc> q;
2

変数に値を代入すると、その値を使って式を評価することができます。また、式の中に演算子 = が入っていても、その式を評価することができます。x = y = z = 0; のように、多重代入することも可能です。ただし、新しい変数 p で p = p + 1; のようなことはできません。q = 1; を評価したあとならば、既に変数 q は定義されているので、q = q + 1; は評価することができます。

次は組み込み関数を実行してみましょう。

Calc> sqrt(2);
1.4142135623730951
Calc> pow(2, 32);
4.294967296e+09
Calc> pi = asin(0.5) * 6;
3.1415926535897936
Calc> sin(0);
0
Calc> sin(pi/2);
1
Calc> sin(pi);
-3.216285744678249e-16
Calc> log2(8);
3
Calc> log10(10000);
4
Calc> sqrt();
wrong number of arguments: sqrt
Calc> sqrt(1,2,3);
wrong number of arguments: sqrt

正常に動作していますね。

今回はここまでです。次回はユーザが関数を定義する機能を追加してみましょう。

●参考文献

  1. 松田晋, 『実践アルゴリズム戦略 解法のテクニック 再帰降下型構文解析』, C MAGAZINE 1992 年 9 月号, ソフトバンク
  2. 水野順, 『スクリプト言語を作ろう』, C MAGAZINE 2000 年 5 月号, ソフトバンク
  3. 松浦健一郎, 『コンパイラの作成』, C MAGAZINE 2003 年 1 月号, ソフトバンク
  4. 高田昌之, 『インタプリタ進化論』, CQ出版社, 1992
  5. 久野靖, 『言語プロセッサ』, 丸善株式会社, 1993

●プログラムリスト1

//
// calc1.go : 電卓プログラム (構文木の組み立て)
//
//            Copyright (C) 2014-2021 Makoto Hiroi
//
package main

import (
    "fmt"
    "os"
    "text/scanner"
)

// 値
type Value float64

// 構文木の型
type Expr interface {
    Eval() Value
}

// 評価
func (e Value) Eval() Value {
    return e
}

// 単項演算子
type Op1 struct {
    code rune
    expr Expr
}

func newOp1(code rune, e Expr) Expr {
    return &Op1{code, e}
}

func (e *Op1) Eval() Value {
    v := e.expr.Eval()
    if e.code == '-' {
        v = -v
    }
    return v
}

// 二項演算子
type Op2 struct {
    code rune
    left, right Expr
}

func newOp2(code rune, left, right Expr) Expr {
    return &Op2{code, left, right}
}

func (e *Op2) Eval() Value {
    x := e.left.Eval()
    y := e.right.Eval()
    switch e.code {
    case '+': return x + y
    case '-': return x - y
    case '*': return x * y
    case '/': return x / y
    default:
        panic(fmt.Errorf("invalid op code"))
    }
}

// 字句解析
type Lex struct {
    scanner.Scanner
    Token rune
}

func (lex *Lex) getToken() {
    lex.Token = lex.Scan()
}

// 因子
func factor(lex *Lex) Expr {
    switch lex.Token {
    case '(':
        lex.getToken()
        e := expression(lex)
        if lex.Token != ')' {
            panic(fmt.Errorf("')' expected"))
        }
        lex.getToken()
        return e
    case '+':
        lex.getToken()
        return newOp1('+', factor(lex))
    case '-':
        lex.getToken()
        return newOp1('-', factor(lex))
    case scanner.Int, scanner.Float:
        var n float64
        fmt.Sscan(lex.TokenText(), &n)
        lex.getToken()
        return Value(n)
    case scanner.Ident:
        text := lex.TokenText()
        if text == "quit" {
            panic(text)
        }
        fallthrough
    default:
        panic(fmt.Errorf("unexpected token: %v", lex.TokenText()))
    }
}

// 項
func term(lex *Lex) Expr {
    e := factor(lex)
    for {
        switch lex.Token {
        case '*':
            lex.getToken()
            e = newOp2('*', e, factor(lex))
        case '/':
            lex.getToken()
            e = newOp2('/', e, factor(lex))
        default:
            return e
        }
    }
}

// 式
func expression(lex *Lex) Expr {
    e := term(lex)
    for {
        switch lex.Token {
        case '+':
            lex.getToken()
            e = newOp2('+', e, term(lex))
        case '-':
            lex.getToken()
            e = newOp2('-', e, term(lex))
        default:
            return e
        }
    }
}

// 式の入力と評価
func toplevel(lex *Lex) (r bool) {
    r = false
    defer func(){
        err := recover()
        if err != nil {
            mes, ok := err.(string)
            if ok && mes == "quit" {
                r = true
            } else {
                fmt.Fprintln(os.Stderr, err)
                for lex.Token != ';' {
                    lex.getToken()
                }
            }
        }
    }()
    for {
        fmt.Print("Calc> ")
        lex.getToken()
        e := expression(lex)
        if lex.Token != ';' {
            panic(fmt.Errorf("invalid expression"))
        } else {
            fmt.Println(e.Eval())
        }
    }
    return r
}

func main() {
    var lex Lex
    lex.Init(os.Stdin)
    for {
        if toplevel(&lex) { break }
    }
}

●プログラムリスト2

//
// calc2.go : 電卓プログラム (変数、組み込み関数の追加)
//
//            Copyright (C) 2014-2021 Makoto Hiroi
//
package main

import (
    "fmt"
    "os"
    "math"
    "text/scanner"
)

// 値
type Value float64

// 構文木の型
type Expr interface {
    Eval() Value
}

// 評価
func (e Value) Eval() Value {
    return e
}

// 単項演算子
type Op1 struct {
    code rune
    expr Expr
}

func newOp1(code rune, e Expr) Expr {
    return &Op1{code, e}
}

func (e *Op1) Eval() Value {
    v := e.expr.Eval()
    if e.code == '-' {
        v = -v
    }
    return v
}

// 二項演算子
type Op2 struct {
    code rune
    left, right Expr
}

func newOp2(code rune, left, right Expr) Expr {
    return &Op2{code, left, right}
}

func (e *Op2) Eval() Value {
    x := e.left.Eval()
    y := e.right.Eval()
    switch e.code {
    case '+': return x + y
    case '-': return x - y
    case '*': return x * y
    case '/': return x / y
    default:
        panic(fmt.Errorf("invalid op code"))
    }
}

// 変数
type Variable string

// 大域的な環境
var globalEnv = make(map[Variable]Value)

// 変数の評価
func (v Variable) Eval() Value {
    val, ok := globalEnv[v]
    if !ok {
        panic(fmt.Errorf("unbound variable: %v", v))
    }
    return val
}

// 代入演算子
type Agn struct {
    name Variable
    expr Expr
}

func newAgn(v Variable, e Expr) *Agn {
    return &Agn{v, e}
}

// 代入演算子の評価
func (a *Agn) Eval() Value {
    val := a.expr.Eval()
    globalEnv[a.name] = val
    return val
}

// 組み込み関数
type Func interface {
    Argc() int
}

type Func1 func(float64) float64

func (f Func1) Argc() int {
    return 1
}

type Func2 func(float64, float64) float64

func (f Func2) Argc() int {
    return 2
}

// 組み込み関数の構文
type App struct {
    fn Func
    xs []Expr
}

func newApp(fn Func, xs []Expr) *App {
    return &App{fn, xs}
}

// 組み込み関数の評価
func (a *App) Eval() Value {
    switch f := a.fn.(type) {
    case Func1:
        x := float64(a.xs[0].Eval())
        return Value(f(x))
    case Func2:
        x := float64(a.xs[0].Eval())
        y := float64(a.xs[1].Eval())
        return Value(f(x, y))
    default:
        panic(fmt.Errorf("function Eval error"))
    }
}

// 組み込み関数の初期化
var funcTable = make(map[string]Func)

func initFunc() {
    funcTable["sqrt"]  = Func1(math.Sqrt)
    funcTable["sin"]   = Func1(math.Sin)
    funcTable["cos"]   = Func1(math.Cos)
    funcTable["tan"]   = Func1(math.Tan)
    funcTable["sinh"]  = Func1(math.Sinh)
    funcTable["cosh"]  = Func1(math.Cosh)
    funcTable["tanh"]  = Func1(math.Tanh)
    funcTable["asin"]  = Func1(math.Asin)
    funcTable["acos"]  = Func1(math.Acos)
    funcTable["atan"]  = Func1(math.Atan)
    funcTable["atan2"] = Func2(math.Atan2)
    funcTable["exp"]   = Func1(math.Exp)
    funcTable["pow"]   = Func2(math.Pow)
    funcTable["log"]   = Func1(math.Log)
    funcTable["log10"] = Func1(math.Log10)
    funcTable["log2"]  = Func1(math.Log2)
}


// 字句解析
type Lex struct {
    scanner.Scanner
    Token rune
}

func (lex *Lex) getToken() {
    lex.Token = lex.Scan()
}

// 引数の取得
func getArgs(lex *Lex) []Expr {
    e := make([]Expr, 0)
    if lex.Token != '(' {
        panic(fmt.Errorf("'(' expected"))
    }
    lex.getToken()
    if lex.Token == ')' {
        lex.getToken()
        return e
    }
    for {
        e = append(e, expression(lex))
        switch lex.Token {
        case ')':
            lex.getToken()
            return e
        case ',':
            lex.getToken()
        default:
            panic(fmt.Errorf("unexpected token in argument list"))
        }
    }
}

// 因子
func factor(lex *Lex) Expr {
    switch lex.Token {
    case '(':
        lex.getToken()
        e := expression(lex)
        if lex.Token != ')' {
            panic(fmt.Errorf("')' expected"))
        }
        lex.getToken()
        return e
    case '+':
        lex.getToken()
        return newOp1('+', factor(lex))
    case '-':
        lex.getToken()
        return newOp1('-', factor(lex))
    case scanner.Int, scanner.Float:
        var n float64
        fmt.Sscan(lex.TokenText(), &n)
        lex.getToken()
        return Value(n)
    case scanner.Ident:
        name := lex.TokenText()
        lex.getToken()
        if name == "quit" {
            panic(name)
        }
        v, ok := funcTable[name]
        if ok {
            xs := getArgs(lex)
            if len(xs) != v.Argc() {
                panic(fmt.Errorf("wrong number of arguments: %v", name))
            }
            return newApp(v, xs)
        } else {
            return Variable(name)
        }
    default:
        panic(fmt.Errorf("unexpected token: %v", lex.TokenText()))
    }
}

// 項
func term(lex *Lex) Expr {
    e := factor(lex)
    for {
        switch lex.Token {
        case '*':
            lex.getToken()
            e = newOp2('*', e, factor(lex))
        case '/':
            lex.getToken()
            e = newOp2('/', e, factor(lex))
        default:
            return e
        }
    }
}

// 式
func expr1(lex *Lex) Expr {
    e := term(lex)
    for {
        switch lex.Token {
        case '+':
            lex.getToken()
            e = newOp2('+', e, term(lex))
        case '-':
            lex.getToken()
            e = newOp2('-', e, term(lex))
        default:
            return e
        }
    }
}

func expression(lex *Lex) Expr {
    e := expr1(lex)
    if lex.Token == '=' {
        v, ok := e.(Variable)
        if ok {
            lex.getToken()
            return newAgn(v, expression(lex))
        } else {
            panic(fmt.Errorf("invalid assign form"))
        }
    }
    return e
}

// 式の入力と評価
func toplevel(lex *Lex) (r bool) {
    r = false
    defer func(){
        err := recover()
        if err != nil {
            mes, ok := err.(string)
            if ok && mes == "quit" {
                r = true
            } else {
                fmt.Fprintln(os.Stderr, err)
                for lex.Token != ';' {
                    lex.getToken()
                }
            }
        }
    }()
    for {
        fmt.Print("Calc> ")
        lex.getToken()
        e := expression(lex)
        if lex.Token != ';' {
            panic(fmt.Errorf("invalid expression"))
        } else {
            fmt.Println(e.Eval())
        }
    }
    return r
}

func main() {
    var lex Lex
    lex.Init(os.Stdin)
    initFunc()
    for {
        if toplevel(&lex) { break }
    }
}

初版 2014 年 4 月 27 日
改訂 2021 年 12 月 22 日

Copyright (C) 2014-2021 Makoto Hiroi
All rights reserved.

[ PrevPage | Golang | NextPage ]