711 lines
17 KiB
Go
711 lines
17 KiB
Go
package compiler
|
|
|
|
import (
|
|
"fmt"
|
|
"monkey/ast"
|
|
"monkey/code"
|
|
"monkey/lexer"
|
|
"monkey/object"
|
|
"monkey/parser"
|
|
"testing"
|
|
)
|
|
|
|
type compilerTestCase struct {
|
|
input string
|
|
expectedConstants []interface{}
|
|
expectedInstructions []code.Instructions
|
|
}
|
|
|
|
func TestIntegerArithmetic(t *testing.T) {
|
|
tests := []compilerTestCase{
|
|
{
|
|
input: "1 + 2",
|
|
expectedConstants: []interface{}{1, 2},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpAdd),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "1; 2",
|
|
expectedConstants: []interface{}{1, 2},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpPop),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "1 - 2",
|
|
expectedConstants: []interface{}{1, 2},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpSub),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "1 * 2",
|
|
expectedConstants: []interface{}{1, 2},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpMul),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "2 / 1",
|
|
expectedConstants: []interface{}{2, 1},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpDiv),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "-1",
|
|
expectedConstants: []interface{}{1},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpMinus),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func TestBooleanExpressions(t *testing.T) {
|
|
tests := []compilerTestCase{
|
|
{
|
|
input: "true",
|
|
expectedConstants: []interface{}{},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpTrue),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "false",
|
|
expectedConstants: []interface{}{},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpFalse),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "1 > 2",
|
|
expectedConstants: []interface{}{1, 2},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpGreaterThan),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "1 < 2",
|
|
expectedConstants: []interface{}{2, 1},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpGreaterThan),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "1 == 2",
|
|
expectedConstants: []interface{}{1, 2},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpEqual),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "1 != 2",
|
|
expectedConstants: []interface{}{1, 2},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpNotEqual),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "true == false",
|
|
expectedConstants: []interface{}{},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpTrue),
|
|
code.Make(code.OpFalse),
|
|
code.Make(code.OpEqual),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "true != false",
|
|
expectedConstants: []interface{}{},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpTrue),
|
|
code.Make(code.OpFalse),
|
|
code.Make(code.OpNotEqual),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "!true",
|
|
expectedConstants: []interface{}{},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpTrue),
|
|
code.Make(code.OpBang),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func TestConditionals(t *testing.T) {
|
|
tests := []compilerTestCase{
|
|
{
|
|
input: `
|
|
if (true) { 10 }; 3333;
|
|
`,
|
|
expectedConstants: []interface{}{10, 3333},
|
|
expectedInstructions: []code.Instructions{
|
|
// 0000
|
|
code.Make(code.OpTrue),
|
|
// 0001
|
|
code.Make(code.OpJumpNotTruthy, 10),
|
|
// 0004
|
|
code.Make(code.OpConstant, 0),
|
|
// 0007
|
|
code.Make(code.OpJump, 11),
|
|
// 0010
|
|
code.Make(code.OpNull),
|
|
// 0011
|
|
code.Make(code.OpPop),
|
|
// 0012
|
|
code.Make(code.OpConstant, 1),
|
|
// 0015
|
|
code.Make(code.OpPop),
|
|
},
|
|
}, {
|
|
input: `
|
|
if (true) { 10 } else { 20 }; 3333;
|
|
`,
|
|
expectedConstants: []interface{}{10, 20, 3333},
|
|
expectedInstructions: []code.Instructions{
|
|
// 0000
|
|
code.Make(code.OpTrue),
|
|
// 0001
|
|
code.Make(code.OpJumpNotTruthy, 10),
|
|
// 0004
|
|
code.Make(code.OpConstant, 0),
|
|
// 0007
|
|
code.Make(code.OpJump, 13),
|
|
// 0010
|
|
code.Make(code.OpConstant, 1),
|
|
// 0013
|
|
code.Make(code.OpPop),
|
|
// 0014
|
|
code.Make(code.OpConstant, 2),
|
|
// 0017
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func TestGlobalLetStatements(t *testing.T) {
|
|
tests := []compilerTestCase{
|
|
{
|
|
input: `
|
|
let one = 1;
|
|
let two = 2;
|
|
`,
|
|
expectedConstants: []interface{}{1, 2},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpSetGlobal, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpSetGlobal, 1),
|
|
},
|
|
},
|
|
{
|
|
input: `
|
|
let one = 1;
|
|
one;
|
|
`,
|
|
expectedConstants: []interface{}{1},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpSetGlobal, 0),
|
|
code.Make(code.OpGetGlobal, 0),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: `
|
|
let one = 1;
|
|
let two = one;
|
|
two;
|
|
`,
|
|
expectedConstants: []interface{}{1},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpSetGlobal, 0),
|
|
code.Make(code.OpGetGlobal, 0),
|
|
code.Make(code.OpSetGlobal, 1),
|
|
code.Make(code.OpGetGlobal, 1),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func TestStringExpressions(t *testing.T) {
|
|
tests := []compilerTestCase{
|
|
{
|
|
input: `"monkey"`,
|
|
expectedConstants: []interface{}{"monkey"},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: `"mon" + "key"`,
|
|
expectedConstants: []interface{}{"mon", "key"},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpAdd),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func TestArrayLiterals(t *testing.T) {
|
|
tests := []compilerTestCase{
|
|
{
|
|
input: "[]",
|
|
expectedConstants: []interface{}{},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpArray, 0),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "[1, 2, 3]",
|
|
expectedConstants: []interface{}{1, 2, 3},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpConstant, 2),
|
|
code.Make(code.OpArray, 3),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "[1 + 2, 3 - 4, 5 * 6]",
|
|
expectedConstants: []interface{}{1, 2, 3, 4, 5, 6},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpAdd),
|
|
code.Make(code.OpConstant, 2),
|
|
code.Make(code.OpConstant, 3),
|
|
code.Make(code.OpSub),
|
|
code.Make(code.OpConstant, 4),
|
|
code.Make(code.OpConstant, 5),
|
|
code.Make(code.OpMul),
|
|
code.Make(code.OpArray, 3),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func TestHashLiterals(t *testing.T) {
|
|
tests := []compilerTestCase{
|
|
{
|
|
input: "{}",
|
|
expectedConstants: []interface{}{},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpHash, 0),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "{1: 2, 3: 4, 5: 6}",
|
|
expectedConstants: []interface{}{1, 2, 3, 4, 5, 6},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpConstant, 2),
|
|
code.Make(code.OpConstant, 3),
|
|
code.Make(code.OpConstant, 4),
|
|
code.Make(code.OpConstant, 5),
|
|
code.Make(code.OpHash, 6),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "{1: 2 + 3, 4: 5 * 6}",
|
|
expectedConstants: []interface{}{1, 2, 3, 4, 5, 6},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpConstant, 2),
|
|
code.Make(code.OpAdd),
|
|
code.Make(code.OpConstant, 3),
|
|
code.Make(code.OpConstant, 4),
|
|
code.Make(code.OpConstant, 5),
|
|
code.Make(code.OpMul),
|
|
code.Make(code.OpHash, 4),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func TestIndexExpressions(t *testing.T) {
|
|
tests := []compilerTestCase{
|
|
{
|
|
input: "[1, 2, 3][1 + 1]",
|
|
expectedConstants: []interface{}{1, 2, 3, 1, 1},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpConstant, 2),
|
|
code.Make(code.OpArray, 3),
|
|
code.Make(code.OpConstant, 3),
|
|
code.Make(code.OpConstant, 4),
|
|
code.Make(code.OpAdd),
|
|
code.Make(code.OpIndex),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: "{1: 2}[2 - 1]",
|
|
expectedConstants: []interface{}{1, 2, 2, 1},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpHash, 2),
|
|
code.Make(code.OpConstant, 2),
|
|
code.Make(code.OpConstant, 3),
|
|
code.Make(code.OpSub),
|
|
code.Make(code.OpIndex),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: `fn() { 1; 2 }`,
|
|
expectedConstants: []interface{}{
|
|
1,
|
|
2,
|
|
[]code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpPop),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpReturnValue),
|
|
},
|
|
},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 2),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func TestFunctions(t *testing.T) {
|
|
tests := []compilerTestCase{
|
|
{
|
|
input: "fn() { return 5 + 10 }",
|
|
expectedConstants: []interface{}{
|
|
5,
|
|
10,
|
|
[]code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpAdd),
|
|
code.Make(code.OpReturnValue),
|
|
},
|
|
},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 2),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: `fn() { 5 + 10 }`,
|
|
expectedConstants: []interface{}{
|
|
5,
|
|
10,
|
|
[]code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpConstant, 1),
|
|
code.Make(code.OpAdd),
|
|
code.Make(code.OpReturnValue),
|
|
},
|
|
},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 2),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func TestFunctionsWithoutReturnValue(t *testing.T) {
|
|
tests := []compilerTestCase{
|
|
{
|
|
input: `fn() { }`,
|
|
expectedConstants: []interface{}{
|
|
[]code.Instructions{
|
|
code.Make(code.OpReturn),
|
|
},
|
|
},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 0),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func TestCompilerScopes(t *testing.T) {
|
|
compiler := New()
|
|
if compiler.scopeIndex != 0 {
|
|
t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 0)
|
|
}
|
|
|
|
compiler.emit(code.OpMul)
|
|
|
|
compiler.enterScope()
|
|
if compiler.scopeIndex != 1 {
|
|
t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 1)
|
|
}
|
|
|
|
compiler.emit(code.OpSub)
|
|
|
|
if len(compiler.scopes[compiler.scopeIndex].instructions) != 1 {
|
|
t.Errorf("instructions length is wrong. got=%d", len(compiler.scopes[compiler.scopeIndex].instructions))
|
|
}
|
|
|
|
last := compiler.scopes[compiler.scopeIndex].lastInstruction
|
|
if last.Opcode != code.OpSub {
|
|
t.Errorf("lastInstruction.OpCode wrong. got=%d, want=%d", last.Opcode, code.OpSub)
|
|
}
|
|
|
|
compiler.leaveScope()
|
|
if compiler.scopeIndex != 0 {
|
|
t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 0)
|
|
}
|
|
|
|
compiler.emit(code.OpAdd)
|
|
|
|
if len(compiler.scopes[compiler.scopeIndex].instructions) != 2 {
|
|
t.Errorf("instructions length is wrong. got=%d", len(compiler.scopes[compiler.scopeIndex].instructions))
|
|
}
|
|
|
|
last = compiler.scopes[compiler.scopeIndex].lastInstruction
|
|
if last.Opcode != code.OpAdd {
|
|
t.Errorf("lastInstruction.OpCode wrong. got=%d, want=%d", last.Opcode, code.OpSub)
|
|
}
|
|
|
|
previous := compiler.scopes[compiler.scopeIndex].previousInstruction
|
|
if previous.Opcode != code.OpMul {
|
|
t.Errorf("previousInstruction.OpCode wrong. got=%d, want=%d", last.Opcode, code.OpMul)
|
|
}
|
|
}
|
|
|
|
func TestFunctionCalls(t *testing.T) {
|
|
tests := []compilerTestCase{
|
|
{
|
|
input: `fn() { 24 }();`,
|
|
expectedConstants: []interface{}{
|
|
24,
|
|
[]code.Instructions{
|
|
code.Make(code.OpConstant, 0), // The literal "24"
|
|
code.Make(code.OpReturnValue),
|
|
},
|
|
},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 1), // The compiled function
|
|
code.Make(code.OpCall),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
{
|
|
input: `
|
|
let noArg = fn() { 24 };
|
|
noArg();
|
|
`,
|
|
expectedConstants: []interface{}{
|
|
24,
|
|
[]code.Instructions{
|
|
code.Make(code.OpConstant, 0), // The literal "24"
|
|
code.Make(code.OpReturnValue),
|
|
},
|
|
},
|
|
expectedInstructions: []code.Instructions{
|
|
code.Make(code.OpConstant, 1), // The compiled function
|
|
code.Make(code.OpSetGlobal, 0),
|
|
code.Make(code.OpGetGlobal, 0),
|
|
code.Make(code.OpCall),
|
|
code.Make(code.OpPop),
|
|
},
|
|
},
|
|
}
|
|
|
|
runCompilerTests(t, tests)
|
|
}
|
|
|
|
func runCompilerTests(t *testing.T, tests []compilerTestCase) {
|
|
t.Helper()
|
|
|
|
for _, tt := range tests {
|
|
program := parse(tt.input)
|
|
|
|
compiler := New()
|
|
err := compiler.Compile(program)
|
|
if err != nil {
|
|
t.Fatalf("compiler error: %s", err)
|
|
}
|
|
|
|
bytecode := compiler.Bytecode()
|
|
|
|
err = testInstructions(tt.expectedInstructions, bytecode.Instructions)
|
|
if err != nil {
|
|
t.Fatalf("testInstructions failed: %s", err)
|
|
}
|
|
|
|
err = testConstants(t, tt.expectedConstants, bytecode.Constants)
|
|
if err != nil {
|
|
t.Fatalf("testConstants failed: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func parse(input string) *ast.Program {
|
|
l := lexer.New(input)
|
|
p := parser.New(l)
|
|
return p.ParseProgram()
|
|
}
|
|
|
|
func testInstructions(expected []code.Instructions, actual code.Instructions) error {
|
|
concatted := concatInstructions(expected)
|
|
|
|
if len(actual) != len(concatted) {
|
|
return fmt.Errorf("wrong instructions length.\nwant=%q\ngot =%q", concatted, actual)
|
|
}
|
|
|
|
for i, ins := range concatted {
|
|
if actual[i] != ins {
|
|
return fmt.Errorf("wrong instruction at %d.\nwant=%q\ngot =%q", i, concatted, actual)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func concatInstructions(s []code.Instructions) code.Instructions {
|
|
out := code.Instructions{}
|
|
|
|
for _, ins := range s {
|
|
out = append(out, ins...)
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func testConstants(t *testing.T, expected []interface{}, actual []object.Object) error {
|
|
if len(expected) != len(actual) {
|
|
return fmt.Errorf("wrong number of constants. got=%d, want=%d", len(actual), len(expected))
|
|
}
|
|
|
|
for i, constant := range expected {
|
|
switch constant := constant.(type) {
|
|
case int:
|
|
err := testIntegerObject(int64(constant), actual[i])
|
|
if err != nil {
|
|
return fmt.Errorf("constant %d = testIntegerObject failed : %s", i, err)
|
|
}
|
|
|
|
case string:
|
|
err := testStringObject(constant, actual[i])
|
|
if err != nil {
|
|
return fmt.Errorf("constant %d = testStringObject failed : %s", i, err)
|
|
}
|
|
|
|
case []code.Instructions:
|
|
fn, ok := actual[i].(*object.CompiledFunction)
|
|
if !ok {
|
|
return fmt.Errorf("constant %d - not a function: %T", i, actual[i])
|
|
}
|
|
|
|
err := testInstructions(constant, fn.Instructions)
|
|
if err != nil {
|
|
return fmt.Errorf("constant %d = testInstructions failed: %s", i, err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func testIntegerObject(expected int64, actual object.Object) interface{} {
|
|
result, ok := actual.(*object.Integer)
|
|
if !ok {
|
|
return fmt.Errorf("object is not Integer. got=%T (%+v", actual, actual)
|
|
}
|
|
|
|
if result.Value != expected {
|
|
return fmt.Errorf("object has wrong value. got=%d, want=%d", result.Value, expected)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func testStringObject(expected string, actual object.Object) error {
|
|
result, ok := actual.(*object.String)
|
|
if !ok {
|
|
return fmt.Errorf("object is not String. got %T (%+v", actual, actual)
|
|
}
|
|
|
|
if result.Value != expected {
|
|
return fmt.Errorf("object has wrong value. got=%q, want=%q", result.Value, expected)
|
|
}
|
|
|
|
return nil
|
|
}
|