Skip to content

Latest commit

 

History

History
222 lines (200 loc) · 8.71 KB

File metadata and controls

222 lines (200 loc) · 8.71 KB

4.3内置函数

在本节中,我们将向解释器添加内置函数。 它们被称为“内置”,因为它们不是由解释器的用户定义的,也不是 Monkey 代码——它们直接内置在解释器中,内置在语言本身中。

这些内置函数被我们定义,用Go,并将 Monkey 的世界与我们的解释器实现的世界联系起来。许多语言实现都提供了这样的功能,以便为语言的用户提供语言“内部”没有提供的功能。

这是一个例子:一个返回当前时间的函数。 为了获得当前时间,可以询问内核(或另一台计算机等)。 询问和与内核交谈通常是通过称为系统调用的东西来完成的。 但是,如果编程语言不提供用户自己进行此类系统调用,那么语言实现,无论是编译器还是解释器,都必须提供一些东西来代替用户进行这些系统调用。

因此,我们将要添加的内置函数再次由解释器的实现者定义。 解释器的用户可以调用它们,但我们定义它们。 这些功能可以做什么,我们保持开放。 它们唯一的限制是它们需要接受零个或多个 object.Object 作为参数并返回一个 object.Object。

// object/object.go

type BuiltinFunction func(args ...Object) Object

这是可调用 Go 函数的类型定义。 但是由于我们需要将这些内置函数提供给我们的用户,我们需要将它们放入我们的对象系统中。 我们通过包装它们来做到这一点:

// object/object.go

const (
// [...]
    BUILTIN_OBJ = "BUILTIN"
)

type Builtin struct {
    Fn BuiltinFunction
}
func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ }
func (b *Builtin) Inspect() string { return "builtin function" }

没有什么可反对的。如您所见,内置。 这显然只是一个包装。 但是结合 object.BuiltinFunction 就足以让我们开始了。

LEN

我们要添加到解释器中的第一个内置函数是 len。 它的工作是返回字符串中的字符数。 不可能将这个函数定义为 Monkey 的用户。 这就是为什么我们需要内置它。 我们想要从 len 得到的是:

>> len("Hello World!")
12
>> len("")
0
>> len("Hey Bob, how ya doin?")
21

我认为这使得 len 背后的想法非常清楚。 事实上很清楚,我们可以很容易地为它编写一个测试:

// evaluator/evaluator_test.go
func TestBuiltinFunctions(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"},
    }

    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)
            }
        }
    }
}

所以这里我们有几个测试用例来运行 len :一个空字符串、一个普通字符串和一个包含空格的字符串。 字符串中是否有空格真的不重要,但你永远不会知道,所以我把测试用例放进去。 最后两个测试用例更有趣:我们要确保 len 返回一个 *object.Error 当使用整数或错误数量的参数调用时。 如果我们运行测试,我们可以看到调用 len 会给我们一个错误,但不是我们测试用例中预期的错误:

$ go test ./evaluator
--- FAIL: TestBuiltinFunctions (0.00s)
    evaluator_test.go:389: object is not Integer. got=*object.Error\
        (&{Message:identifier not found: len})
    evaluator_test.go:389: object is not Integer. got=*object.Error\
        (&{Message:identifier not found: len})
    evaluator_test.go:389: object is not Integer. got=*object.Error\
        (&{Message:identifier not found: len})
    evaluator_test.go:371: wrong error message.\
    expected="argument to `len` not supported, got INTEGER",\
    got="identifier not found: len"
FAIL
FAIL monkey/evaluator 0.007s

len 无法找到,考虑到我们还没有定义它,这并不令人费解。

为了做到这一点,我们要做的第一件事是提供一种可以找到内置函数的方法。 一种选择是将它们添加到传递给 Eval 的顶级 object.Environment。 但相反,我们将保留一个单独的内置函数环境:

// evaluator/builtins.go

package evaluator

import "monkey/object"

var builtins = map[string]*object.Builtin{
    "len": &object.Builtin{
        Fn: func(args ...object.Object) object.Object {
            return NULL
        },
    },
}

为了利用这一点,我们需要编辑我们的 evalIdentifier 函数以在给定的标识符未绑定到当前环境中的值时查找内置函数作为回退:

// evaluator/evaluator.go

func evalIdentifier(
    node *ast.Identifier,
    env *object.Environment,
) object.Object {
    if val, ok := env.Get(node.Value); ok {
        return val
    }

    if builtin, ok := builtins[node.Value]; ok {
        return builtin
    }

    return newError("identifier not found: " + node.Value)
}

所以现在查找len标识符的时候找到了len,调用还不行:

$ go run main.go
Hello mrnugget! This is the Monkey programming language!
Feel free to type in commands
>> len()
ERROR: not a function: BUILTIN
>>

运行测试会给我们同样的错误。 我们需要教我们的 applyFunction 关于 *object.Builtin 和 object.BuiltinFunction:

// evaluator/evaluator.go
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())
    }
}

除了移动现有的行,这里的变化是添加了 case *object.Builtin 分支,我们称之为 object.BuiltinFunction。 这样做就像使用一样简单 args 切片作为参数并调用函数。

需要注意的是,我们在调用内置函数时不需要 unwrapReturnValue。 那是因为我们从不从这些函数中返回 *object.ReturnValue。

现在,测试正确地抱怨调用 len 时返回 NULL:

$ go test ./evaluator
--- FAIL: TestBuiltinFunctions (0.00s)
    evaluator_test.go:389: object is not Integer. got=*object.Null (&{})
    evaluator_test.go:389: object is not Integer. got=*object.Null (&{})
    evaluator_test.go:389: object is not Integer. got=*object.Null (&{})
    evaluator_test.go:366: object is not Error. got=*object.Null (&{})
    evaluator_test.go:366: object is not Error. got=*object.Null (&{})
FAIL
FAIL monkey/evaluator 0.007s

这意味着调用 len 是有效的! 只是它只返回NULL。 但是解决这个问题就像编写任何其他 Go 函数一样简单:

// evaluator/builtins.go

import (
    "monkey/object"
    "unicode/utf8"
)

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.String:
                return &object.Integer{Value: int64(len(arg.Value))}
            default:
                return newError("argument to `len` not supported, got %s",
                    args[0].Type())
            }
        },
    },
}

这个函数最重要的部分是调用 Go 的 len 并返回一个新分配的 object.Integer。 除此之外,我们还进行了错误检查,以确保我们不能使用错误数量的参数或不支持类型的参数调用此函数。 唉,我们的测试通过了:

$ go test ./evaluator
ok monkey/evaluator 0.007s

这意味着我们可以在 REPL 中试驾 len:

$ go run main.go
Hello mrnugget! This is the Monkey programming language!
Feel free to type in commands
>> len("1234")
4
>> len("Hello World!")
12
>> len("Woooooohooo!", "len works!!")
ERROR: wrong number of arguments. got=2, want=1
>> len(12345)
ERROR: argument to `len` not supported, got INTEGER

完美的! 我们的第一个内置函数可以运行并准备就绪。

< 4.2字符串 > 4.4数组