Repository navigation
Expand file tree
/
Copy pathbytecode_api_test.go
More file actions
161 lines (156 loc) · 5.61 KB
/
Copy pathbytecode_api_test.go
File metadata and controls
161 lines (156 loc) · 5.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package glisp
import (
"bytes"
"strings"
"testing"
)
func TestAssembledBytecodeRejectsMissingOperands(t *testing.T) {
// MakeFunction is also a host API. Partially assembled code must report
// a VM error that the host can diagnose and recover from with Clear.
for _, op := range []Opcode{OpPop, OpDup, OpPut, OpBindDynFun, OpBranch, OpDispatch, OpExplode, OpSquash, OpVectorize, OpBindlist, OpRefSym, OpCons, OpCar, OpCdr, OpNot, OpSymbolNum, OpType, OpLen, OpExist, OpAppend, OpConcat, OpMakeList, OpMakeArray, OpStr, OpSexpStr, OpLt, OpArithAdd, OpHget, OpHash} {
t.Run(Instruction{Op: op}.InstrString(), func(t *testing.T) {
env := New()
fn := MakeFunction("incomplete", 0, false, Function{{Op: op, Nargs: 2}, {Op: OpReturn}})
if _, err := env.Apply(fn, MakeArgs()); err == nil {
t.Fatal("missing operands accepted")
} else {
if trace := env.GetStackTrace(err); !strings.Contains(trace, "incomplete:0") {
t.Fatalf("missing instruction location: %s", trace)
}
}
env.Clear()
if got, err := env.EvalString("(+ 20 22)"); err != nil || got.SexpString() != "42" {
t.Fatalf("recovery: %v %v", got, err)
}
})
}
}
func TestAssembledControlFlow(t *testing.T) {
for _, tc := range []struct {
name string
code Function
message string
}{
{"relative jump", Function{{Op: OpJump, Loc: -1}}, "jump out of bounds"},
{"absolute jump", Function{{Op: OpGoto, Loc: 3}}, "jump out of bounds"},
{"conditional jump", Function{{Op: OpPush, Expr: SexpBool(true)}, {Op: OpBranch, Direction: true, Loc: 3}}, "jump out of bounds"},
{"unknown instruction", Function{{Op: OpInvalid}}, "unknown opcode"},
{"incomplete cons", Function{{Op: OpPush, Expr: SexpNull}, {Op: OpCons}}, "invalid stack access"},
{"dynamic binding value", Function{{Op: OpPush, Expr: NewSexpInt(1)}, {Op: OpBindDynFun}}, "not a function"},
{"dynamic binding name", Function{{Op: OpPush, Expr: MakeFunction("f", 0, false, nil)}, {Op: OpBindDynFun}}, "invalid stack access"},
{"splicing scalar", Function{{Op: OpPush, Expr: NewSexpInt(1)}, {Op: OpExplode}}, "list"},
{"error return without message", Function{{Op: OpReturn, DynamicErr: true}}, "invalid stack access"},
} {
t.Run(tc.name, func(t *testing.T) {
env := New()
_, err := env.Apply(MakeFunction(tc.name, 0, false, tc.code), MakeArgs())
if err == nil || !strings.Contains(err.Error(), tc.message) {
t.Fatalf("%v, want %q", err, tc.message)
}
})
}
// A hand-assembled conditional computes abs, and dumps retain both branch
// direction and the actual target for debugging host-generated functions.
env := New()
arg := env.MakeSymbol("x")
code := Function{{Op: OpPut, Sym: arg}, {Op: OpGet, Sym: arg}, {Op: OpPush, Expr: NewSexpInt(0)}, {Op: OpLt, Nargs: 2}, {Op: OpBranch, Direction: false, Loc: 5}, {Op: OpPush, Expr: NewSexpInt(0)}, {Op: OpGet, Sym: arg}, {Op: OpArithSub, Nargs: 2}, {Op: OpReturn}, {Op: OpGet, Sym: arg}, {Op: OpReturn}}
fn := MakeFunction("abs", 1, false, code)
for _, n := range []int{-7, 0, 7} {
got, err := env.Apply(fn, MakeArgs(NewSexpInt(n)))
if err != nil || got.SexpString() != NewSexpInt(max(n, -n)).SexpString() {
t.Fatalf("abs(%d): %v %v", n, got, err)
}
}
var dump bytes.Buffer
DumpFunction(&dump, code)
if !strings.Contains(dump.String(), "brn 5") || !strings.Contains(dump.String(), "get x") {
t.Fatal(dump.String())
}
}
func TestDataStackOwnershipAndBounds(t *testing.T) {
stack := NewDataStack(1)
if _, err := stack.Pop(); err == nil {
t.Fatal("empty pop succeeded")
}
for i := 0; i < 20; i++ {
stack.PushExpr(NewSexpInt(i))
}
clone := stack.Clone()
for _, n := range []int{-1, 20, 21} {
if _, err := stack.Get(n); err == nil {
t.Fatalf("Get(%d) succeeded", n)
}
}
if _, err := stack.GetExpressions(-1); err == nil {
t.Fatal("negative count succeeded")
}
if _, err := stack.PopExpressions(21); err == nil {
t.Fatal("overlarge pop succeeded")
}
if _, err := stack.PeekArgsUntil(func(Sexp) bool { return false }); err == nil {
t.Fatal("missing marker succeeded")
}
args, err := stack.PeekArgsUntil(func(v Sexp) bool { return v.SexpString() == "17" })
if err != nil || args.Len() != 3 || args.Get(0).SexpString() != "17" {
t.Fatalf("peek: %v %v", args, err)
}
values, err := stack.PopExpressions(3)
if err != nil {
t.Fatal(err)
}
stack.PushExpr(NewSexpInt(99))
if values[0].SexpString() != "17" {
t.Fatal("pop result aliases reused stack storage")
}
if clone.Top() != 19 {
t.Fatal("clone changed")
}
stack.DropExpr(100)
stack.DropExpr(-1)
if stack.Top() != 17 {
t.Fatal("invalid drop changed stack")
}
stack.DropExpr(18)
if !stack.IsEmpty() {
t.Fatal("stack not empty")
}
}
func TestCallStackBoundsAndClone(t *testing.T) {
addresses := NewAddrStack(0)
scopes := NewStackStack(0)
fn := MakeFunction("caller", 0, false, nil)
scope := NewScopeStack()
scope.PushScope()
for i := 0; i < 4; i++ {
addresses.PushAddr(fn, i)
scopes.Push(scope)
}
ac, sc := addresses.Clone(), scopes.Clone()
for _, index := range []int{-1, 4} {
if _, err := addresses.Get(index); err == nil {
t.Fatal("invalid address index")
}
if _, err := scopes.Get(index); err == nil {
t.Fatal("invalid scope index")
}
}
for i := 3; i >= 0; i-- {
f, pc, err := addresses.PopAddr()
if err != nil || f != fn || pc != i {
t.Fatal("return order")
}
s, err := scopes.Pop()
if err != nil || s != scope {
t.Fatal("scope order")
}
}
if !addresses.IsEmpty() || !scopes.IsEmpty() || ac.Top() != 3 || sc.Top() != 3 {
t.Fatal("clone or stack state")
}
if _, _, err := addresses.PopAddr(); err == nil {
t.Fatal("empty address pop")
}
if _, err := scopes.Pop(); err == nil {
t.Fatal("empty scope pop")
}
}