Golang でプログラミング言語を作る__Part21
Aug 24, 2017 · 6 min read
前回は文字列の評価機能(evaluate)を実装しました。
今回は、組み込み関数(built-in)、len()を作っていきます。
まずは、オブジェクトで組み込み関数をwrapする BuiltIn タイプを作っていきます。
object/object.go
type BuiltinFunction func(args ...Object) Objecttype Builtin struct {
Fn BuiltinFunction
}func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ }
func (b *Builtin) Inspect() string { return "builtin function" }
上の設定を終えたら、次のテストケースを書いていきます。
今回のlen関数は、引数に0または、全ての種類のオブジェクトを入れられるものとします。ただし、引数の数は1つだけを、アクセプトします。
evaluator/evaluator_test.go
func TestBuiltinFunction(t *testing.T) {
tests := []struct {
input string
expected interface{}
}{
{`len("")`, 0},
{`len("four")`, 4},
{`len("hello world")`, 11},
{`len(1)`, "argument to `len` not supported, got INTEGER"},
{`len("one", "two")`, "wrong number of arguments. got=2, want=1"},
{`len([1, 2, 3])`, 3},
{`len([])`, 0}, // [...]
}for _, tt := range tests {
evaluated := testEval(tt.input)switch expected := tt.expected.(type) {
case int:
testIntegerObject(t, evaluated, int64(expected))
case string:
errObj, ok := evaluated.(*object.Error)
if !ok {
t.Errorf("object is not error, got=%T (%+v)", evaluated, evaluated)
continue
}
if errObj.Message != expected {
t.Errorf("wrong error message expected=%q, got=%q", expected, errObj.Message)
}
// [...]
}
}
}
上のテストが通るような関数を、evaluator/buitilns.goファイルに作ります。
evaluator/builtin.go
var builtins = map[string]*object.Builtin{
"len": &object.Builtin{
Fn: func(args ...object.Object) object.Object {
if len(args) != 1 {
return newError("wrong number of arguments. got=%d, want=1", len(args))
}switch arg := args[0].(type) {
case *object.Array:
return &object.Integer{Value: int64(len(arg.Elements))}
case *object.String:
return &object.Integer{Value: int64(len(arg.Value))}
default:
return newError("argument to `len` not supported, got %s", args[0].Type())
}
},
},
}
上で定義した、len関数が使えるように、evaluator側で処理を加える必要があります。まず、次のコードを top-levelの解釈関数、Eval()に加えて、 ast.CallBack が渡された時の処理を修正します。
case *ast.CallExpression:
function := Eval(node.Function, env)
if isError(function) {
return function
}
args := evalExpressions(node.Arguments, env)
if len(args) == 1 && isError(args[0]) {
return args[0]
}
return applyFunction(function, args)
}また、次のコードを識別子の解釈関数、 evalIdentifier に加え、関数名が組込関数である場合はそれを返すようにします。
if builtin, ok := builtins[node.Value]; ok {
return builtin
}最後に applyFunction 関数を定義して、 ast.CallExpression が渡された時に、それがユーザーが定義した関数なのか、或いは組み込み関数を呼んでいるのかを分岐させます。
func applyFunction(fn object.Object, args []object.Object) object.Object {
switch fn := fn.(type) {
case *object.Function:
extendedEnv := extendFunctionEnv(fn, args)
evaluated := Eval(fn.Body, extendedEnv)
return unwrapReturnValue(evaluated)
case *object.Builtin:
return fn.Fn(args...)
default:
return newError("not a function: %s", fn.Type())
}
}Callした関数がbuiltinタイプのオブジェクトであった場合は、その関数(今回はlen関数)を返します。
以上で、要素数を調べるlen()の実装が終わりました。
次は配列(array)タイプを、機能に加えていきます。
