Golang でプログラミング言語を作る__Part21

Tuyoshi Akiyama
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)タイプを、機能に加えていきます。

)
Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade