builtins
This commit is contained in:
@@ -37,6 +37,7 @@ const (
|
||||
OpReturn
|
||||
OpGetLocal
|
||||
OpSetLocal
|
||||
OpGetBuiltin
|
||||
)
|
||||
|
||||
type Definition struct {
|
||||
@@ -71,6 +72,7 @@ var definitions = map[Opcode]*Definition{
|
||||
OpReturn: {"OpReturn", []int{}},
|
||||
OpGetLocal: {"OpGetLocal", []int{1}},
|
||||
OpSetLocal: {"OpSetLocal", []int{1}},
|
||||
OpGetBuiltin: {"OpGetBuiltin", []int{1}},
|
||||
}
|
||||
|
||||
func Lookup(op byte) (*Definition, error) {
|
||||
|
||||
@@ -34,9 +34,16 @@ func New() *Compiler {
|
||||
lastInstruction: EmittedInstruction{},
|
||||
previousInstruction: EmittedInstruction{},
|
||||
}
|
||||
|
||||
symbolTable := NewSymbolTable()
|
||||
|
||||
for i, v := range object.Builtins {
|
||||
symbolTable.DefineBuiltin(i, v.Name)
|
||||
}
|
||||
|
||||
return &Compiler{
|
||||
constants: []object.Object{},
|
||||
symbolTable: NewSymbolTable(),
|
||||
symbolTable: symbolTable,
|
||||
scopes: []CompilationScope{mainScope},
|
||||
scopeIndex: 0,
|
||||
}
|
||||
@@ -207,11 +214,7 @@ func (c *Compiler) Compile(node ast.Node) error {
|
||||
return fmt.Errorf("undefined varible %s", node.Value)
|
||||
}
|
||||
|
||||
if symbol.Scope == GlobalScope {
|
||||
c.emit(code.OpGetGlobal, symbol.Index)
|
||||
} else {
|
||||
c.emit(code.OpGetLocal, symbol.Index)
|
||||
}
|
||||
c.loadSymbol(symbol)
|
||||
|
||||
case *ast.StringLiteral:
|
||||
str := &object.String{Value: node.Value}
|
||||
@@ -425,3 +428,14 @@ type Bytecode struct {
|
||||
Instructions code.Instructions
|
||||
Constants []object.Object
|
||||
}
|
||||
|
||||
func (c *Compiler) loadSymbol(s Symbol) {
|
||||
switch s.Scope {
|
||||
case GlobalScope:
|
||||
c.emit(code.OpGetGlobal, s.Index)
|
||||
case LocalScope:
|
||||
c.emit(code.OpGetLocal, s.Index)
|
||||
case BuiltinScope:
|
||||
c.emit(code.OpGetBuiltin, s.Index)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -832,3 +832,43 @@ func testStringObject(expected string, actual object.Object) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestBuiltins(t *testing.T) {
|
||||
tests := []compilerTestCase{
|
||||
{
|
||||
input: `
|
||||
len([]);
|
||||
push([], 1);
|
||||
`,
|
||||
expectedConstants: []interface{}{1},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpGetBuiltin, 0),
|
||||
code.Make(code.OpArray, 0),
|
||||
code.Make(code.OpCall, 1),
|
||||
code.Make(code.OpPop),
|
||||
code.Make(code.OpGetBuiltin, 5),
|
||||
code.Make(code.OpArray, 0),
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpCall, 2),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `fn() { len([]) }`,
|
||||
expectedConstants: []interface{}{
|
||||
[]code.Instructions{
|
||||
code.Make(code.OpGetBuiltin, 0),
|
||||
code.Make(code.OpArray, 0),
|
||||
code.Make(code.OpCall, 1),
|
||||
code.Make(code.OpReturnValue),
|
||||
},
|
||||
},
|
||||
expectedInstructions: []code.Instructions{
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runCompilerTests(t, tests)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ type SymbolScope string
|
||||
const (
|
||||
LocalScope SymbolScope = "LOCAL"
|
||||
GlobalScope SymbolScope = "GLOBAL"
|
||||
BuiltinScope SymbolScope = "BUILTIN"
|
||||
)
|
||||
|
||||
type Symbol struct {
|
||||
@@ -53,3 +54,9 @@ func (s *SymbolTable) Resolve(name string) (Symbol, bool) {
|
||||
|
||||
return obj, ok
|
||||
}
|
||||
|
||||
func (s *SymbolTable) DefineBuiltin(index int, name string) Symbol {
|
||||
symbol := Symbol{Name: name, Index: index, Scope: BuiltinScope}
|
||||
s.store[name] = symbol
|
||||
return symbol
|
||||
}
|
||||
|
||||
@@ -166,3 +166,34 @@ func TestResolveNestedLocal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefineResolveBuiltins(t *testing.T) {
|
||||
global := NewSymbolTable()
|
||||
firstLocal := NewEnclosedSymbolTable(global)
|
||||
secondLocal := NewEnclosedSymbolTable(firstLocal)
|
||||
|
||||
expected := []Symbol{
|
||||
Symbol{Name: "a", Scope: BuiltinScope, Index: 0},
|
||||
Symbol{Name: "c", Scope: BuiltinScope, Index: 1},
|
||||
Symbol{Name: "e", Scope: BuiltinScope, Index: 2},
|
||||
Symbol{Name: "f", Scope: BuiltinScope, Index: 3},
|
||||
}
|
||||
|
||||
for i, v := range expected {
|
||||
global.DefineBuiltin(i, v.Name)
|
||||
}
|
||||
|
||||
for _, table := range []*SymbolTable{global, firstLocal, secondLocal} {
|
||||
for _, sym := range expected {
|
||||
result, ok := table.Resolve(sym.Name)
|
||||
if !ok {
|
||||
t.Errorf("name %s not resolvable", sym.Name)
|
||||
continue
|
||||
}
|
||||
if result != sym {
|
||||
t.Errorf("expected %s to resolve to %+v, got=%+v",
|
||||
sym.Name, sym, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,119 +1,14 @@
|
||||
package evaluator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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.Array:
|
||||
return &object.Integer{Value: int64(len(arg.Elements))}
|
||||
case *object.String:
|
||||
return &object.Integer{Value: int64(utf8.RuneCountInString(arg.Value))}
|
||||
default:
|
||||
return newError("argument to `len` not supported, got %s", args[0].Type())
|
||||
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
"first": &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))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `first` must be ARRAY, got %s", args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
if len(arr.Elements) > 0 {
|
||||
return arr.Elements[0]
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
|
||||
"last": &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))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `last` must be ARRAY, got %s", args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
length := len(arr.Elements)
|
||||
if length > 0 {
|
||||
return arr.Elements[length-1]
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
|
||||
"rest": &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))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `rest` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
length := len(arr.Elements)
|
||||
if length > 0 {
|
||||
newElements := make([]object.Object, length-1, length-1)
|
||||
copy(newElements, arr.Elements[1:length])
|
||||
return &object.Array{Elements: newElements}
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
|
||||
"push": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
if len(args) != 2 {
|
||||
return newError("wrong number of arguments. got=%d, want=2",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != object.ARRAY_OBJ {
|
||||
return newError("argument to `push` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*object.Array)
|
||||
length := len(arr.Elements)
|
||||
|
||||
newElements := make([]object.Object, length+1, length+1)
|
||||
copy(newElements, arr.Elements)
|
||||
newElements[length] = args[1]
|
||||
|
||||
return &object.Array{Elements: newElements}
|
||||
},
|
||||
},
|
||||
|
||||
"puts": &object.Builtin{
|
||||
Fn: func(args ...object.Object) object.Object {
|
||||
for _, arg := range args {
|
||||
fmt.Println(arg.Inspect())
|
||||
}
|
||||
|
||||
return NULL
|
||||
},
|
||||
},
|
||||
"len": object.GetBuiltinByName("len"),
|
||||
"first": object.GetBuiltinByName("first"),
|
||||
"last": object.GetBuiltinByName("last"),
|
||||
"rest": object.GetBuiltinByName("rest"),
|
||||
"push": object.GetBuiltinByName("push"),
|
||||
"puts": object.GetBuiltinByName("puts"),
|
||||
}
|
||||
|
||||
@@ -319,7 +319,10 @@ func applyFunction(fn object.Object, args []object.Object) object.Object {
|
||||
return unwrapReturnValue(evaluated)
|
||||
|
||||
case *object.Builtin:
|
||||
return fn.Fn(args...)
|
||||
if result := fn.Fn(args...); result != nil {
|
||||
return result
|
||||
}
|
||||
return NULL
|
||||
|
||||
default:
|
||||
return newError("not a function: %s", fn.Type())
|
||||
|
||||
139
object/builtins.go
Normal file
139
object/builtins.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package object
|
||||
|
||||
import "fmt"
|
||||
|
||||
var Builtins = []struct {
|
||||
Name string
|
||||
Builtin *Builtin
|
||||
}{
|
||||
{
|
||||
"len",
|
||||
&Builtin{Fn: func(args ...Object) Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
|
||||
switch arg := args[0].(type) {
|
||||
case *Array:
|
||||
return &Integer{Value: int64(len(arg.Elements))}
|
||||
case *String:
|
||||
return &Integer{Value: int64(len(arg.Value))}
|
||||
default:
|
||||
return newError("argument to `len` not supported, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"puts",
|
||||
&Builtin{Fn: func(args ...Object) Object {
|
||||
for _, arg := range args {
|
||||
fmt.Println(arg.Inspect())
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"first",
|
||||
&Builtin{Fn: func(args ...Object) Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1", len(args))
|
||||
}
|
||||
if args[0].Type() != ARRAY_OBJ {
|
||||
return newError("argument to `first` must be ARRAY, got %s", args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*Array)
|
||||
if len(arr.Elements) > 0 {
|
||||
return arr.Elements[0]
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"last",
|
||||
&Builtin{Fn: func(args ...Object) Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1", len(args))
|
||||
}
|
||||
if args[0].Type() != ARRAY_OBJ {
|
||||
return newError("argument to `last` must be ARRAY, got %s", args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*Array)
|
||||
length := len(arr.Elements)
|
||||
if length > 0 {
|
||||
return arr.Elements[length-1]
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"rest",
|
||||
&Builtin{Fn: func(args ...Object) Object {
|
||||
if len(args) != 1 {
|
||||
return newError("wrong number of arguments. got=%d, want=1",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != ARRAY_OBJ {
|
||||
return newError("argument to `rest` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*Array)
|
||||
length := len(arr.Elements)
|
||||
if length > 0 {
|
||||
newElements := make([]Object, length-1, length-1)
|
||||
copy(newElements, arr.Elements[1:length])
|
||||
return &Array{Elements: newElements}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"push",
|
||||
&Builtin{Fn: func(args ...Object) Object {
|
||||
if len(args) != 2 {
|
||||
return newError("wrong number of arguments. got=%d, want=2",
|
||||
len(args))
|
||||
}
|
||||
if args[0].Type() != ARRAY_OBJ {
|
||||
return newError("argument to `push` must be ARRAY, got %s",
|
||||
args[0].Type())
|
||||
}
|
||||
|
||||
arr := args[0].(*Array)
|
||||
length := len(arr.Elements)
|
||||
|
||||
newElements := make([]Object, length+1, length+1)
|
||||
copy(newElements, arr.Elements)
|
||||
newElements[length] = args[1]
|
||||
|
||||
return &Array{Elements: newElements}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func newError(format string, a ...interface{}) *Error {
|
||||
return &Error{Message: fmt.Sprintf(format, a...)}
|
||||
}
|
||||
|
||||
func GetBuiltinByName(name string) *Builtin {
|
||||
for _, def := range Builtins {
|
||||
if def.Name == name {
|
||||
return def.Builtin
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -19,6 +19,9 @@ func Start(in io.Reader, out io.Writer) {
|
||||
constants := []object.Object{}
|
||||
globals := make([]object.Object, vm.GlobalsSize)
|
||||
symbolTable := compiler.NewSymbolTable()
|
||||
for i, v := range object.Builtins {
|
||||
symbolTable.DefineBuiltin(i, v.Name)
|
||||
}
|
||||
|
||||
for {
|
||||
fmt.Fprintf(out, PROMPT)
|
||||
|
||||
49
vm/vm.go
49
vm/vm.go
@@ -206,7 +206,7 @@ func (vm *VM) Run() error {
|
||||
numArgs := code.ReadUint8(ins[ip+1:])
|
||||
vm.currentFrame().ip += 1
|
||||
|
||||
err := vm.callFunction(int(numArgs))
|
||||
err := vm.executeCall(int(numArgs))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -249,6 +249,17 @@ func (vm *VM) Run() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case code.OpGetBuiltin:
|
||||
builtinIndex := code.ReadUint8(ins[ip+1:])
|
||||
vm.currentFrame().ip += 1
|
||||
|
||||
definition := object.Builtins[builtinIndex]
|
||||
|
||||
err := vm.push(definition.Builtin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,12 +476,19 @@ func (vm *VM) executeMinusOperator() error {
|
||||
return vm.push(&object.Integer{Value: -value})
|
||||
}
|
||||
|
||||
func (vm *VM) callFunction(numArgs int) error {
|
||||
fn, ok := vm.stack[vm.sp-1-numArgs].(*object.CompiledFunction)
|
||||
if !ok {
|
||||
return fmt.Errorf("calling non-function")
|
||||
func (vm *VM) executeCall(numArgs int) error {
|
||||
callee := vm.stack[vm.sp-1-numArgs]
|
||||
switch callee := callee.(type) {
|
||||
case *object.CompiledFunction:
|
||||
return vm.callFunction(callee, numArgs)
|
||||
case *object.Builtin:
|
||||
return vm.callBuiltin(callee, numArgs)
|
||||
default:
|
||||
return fmt.Errorf("calling non-function and non-built-in")
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) callFunction(fn *object.CompiledFunction, numArgs int) error {
|
||||
if numArgs != fn.NumParameters {
|
||||
return fmt.Errorf("wrong number of arguments: want=%d, got=%d", fn.NumParameters, numArgs)
|
||||
}
|
||||
@@ -482,6 +500,27 @@ func (vm *VM) callFunction(numArgs int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *VM) callBuiltin(builtin *object.Builtin, numArgs int) error {
|
||||
args := vm.stack[vm.sp-numArgs : vm.sp]
|
||||
|
||||
result := builtin.Fn(args...)
|
||||
vm.sp = vm.sp - numArgs - 1
|
||||
|
||||
if result != nil {
|
||||
err := vm.push(result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := vm.push(Null)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func nativeBoolToBooleanObject(input bool) *object.Boolean {
|
||||
if input {
|
||||
return True
|
||||
|
||||
@@ -106,6 +106,16 @@ func testExpectedObject(t *testing.T, expected interface{}, actual object.Object
|
||||
if actual != Null {
|
||||
t.Errorf("object is not Null: %T (%+v)", actual, actual)
|
||||
}
|
||||
|
||||
case *object.Error:
|
||||
errObj, ok := actual.(*object.Error)
|
||||
if !ok {
|
||||
t.Errorf("object is not Error: %T (%+v)", actual, actual)
|
||||
return
|
||||
}
|
||||
if errObj.Message != expected.Message {
|
||||
t.Errorf("wrong error message. expected=%q, got=%q", expected.Message, errObj.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,3 +565,49 @@ func TestCallingFunctionsWithWrongArguments(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinFunctions(t *testing.T) {
|
||||
tests := []vmTestCase{
|
||||
{`len("")`, 0},
|
||||
{`len("four")`, 4},
|
||||
{`len("hello world")`, 11},
|
||||
{
|
||||
`len(1)`,
|
||||
&object.Error{
|
||||
Message: "argument to `len` not supported, got INTEGER",
|
||||
},
|
||||
},
|
||||
{`len("one", "two")`,
|
||||
&object.Error{
|
||||
Message: "wrong number of arguments. got=2, want=1",
|
||||
},
|
||||
},
|
||||
{`len([1, 2, 3])`, 3},
|
||||
{`len([])`, 0},
|
||||
{`puts("hello", "world!")`, Null},
|
||||
{`first([1, 2, 3])`, 1},
|
||||
{`first([])`, Null},
|
||||
{`first(1)`,
|
||||
&object.Error{
|
||||
Message: "argument to `first` must be ARRAY, got INTEGER",
|
||||
},
|
||||
},
|
||||
{`last([1, 2, 3])`, 3},
|
||||
{`last([])`, Null},
|
||||
{`last(1)`,
|
||||
&object.Error{
|
||||
Message: "argument to `last` must be ARRAY, got INTEGER",
|
||||
},
|
||||
},
|
||||
{`rest([1, 2, 3])`, []int{2, 3}},
|
||||
{`rest([])`, Null},
|
||||
{`push([], 1)`, []int{1}},
|
||||
{`push(1, 1)`,
|
||||
&object.Error{
|
||||
Message: "argument to `push` must be ARRAY, got INTEGER",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runVmTests(t, tests)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user