Skip to content

Commit 5ab700b

Browse files
authored
Add virtual (#31)
* Add virtual machine - part 1 * Fix build. * Fix build. Co-authored-by: Bowen Fu <missing>
1 parent 8532623 commit 5ab700b

File tree

8 files changed

+277
-2
lines changed

8 files changed

+277
-2
lines changed

.github/FUNDING.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
99
liberapay: bfu # Replace with a single Liberapay username
1010
issuehunt: # Replace with a single IssueHunt username
1111
otechie: # Replace with a single Otechie username
12-
custom: http://paypal.me/bowfu # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
12+
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# lisp.cpp
22

3-
lisp : a simple computer algebra system in C++
3+
lisp : a simple lisp interpreter in C++ with scheme style syntax + quote / unquote style macros.
44

55
<!-- ![lisp](./lisp.svg) -->
66

core.lisp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,14 @@
122122
(expand-clauses clauses)
123123
))
124124

125+
(define delay (macro (action)
126+
`(lambda () ,action))
127+
)
128+
129+
(define force (macro (delayed)
130+
`(,delayed))
131+
)
132+
125133
(define atom?
126134
(lambda (x)
127135
(and (not (pair? x)) (not (null? x)))))

include/lisp/vm.h

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#include <cstdint>
2+
#include <vector>
3+
#include <stack>
4+
#include <string>
5+
#include <variant>
6+
7+
using Byte = uint8_t;
8+
9+
enum Instruction
10+
{
11+
kICONST,
12+
kIADD,
13+
kSCONST,
14+
kHALT,
15+
kPRINT,
16+
kCALL,
17+
kRET,
18+
kLOAD,
19+
kSTORE,
20+
};
21+
22+
23+
class FunctionSymbol
24+
{
25+
std::string mName{};
26+
size_t mNbArgs{};
27+
size_t mNbLocals{};
28+
size_t mAddress{};
29+
public:
30+
FunctionSymbol(std::string name, size_t nbArgs, size_t nbLocals, size_t address)
31+
: mName{name}
32+
, mNbArgs{nbArgs}
33+
, mNbLocals{nbLocals}
34+
, mAddress{address}
35+
{}
36+
std::string name() const
37+
{
38+
return mName;
39+
}
40+
size_t nbArgs() const
41+
{
42+
return mNbArgs;
43+
}
44+
size_t nbLocals() const
45+
{
46+
return mNbLocals;
47+
}
48+
size_t address() const
49+
{
50+
return mAddress;
51+
}
52+
};
53+
54+
using Object = std::variant<int32_t, float, std::string, FunctionSymbol>;
55+
56+
class VM;
57+
58+
class StackFrame
59+
{
60+
FunctionSymbol const& mFunc;
61+
std::vector<Object> mLocals;
62+
size_t mReturnAddress;
63+
public:
64+
StackFrame(FunctionSymbol const& func, std::vector<Object>&& locals, size_t returnAddress)
65+
: mFunc{func}
66+
, mLocals{std::move(locals)}
67+
, mReturnAddress{returnAddress}
68+
{
69+
}
70+
auto func() const
71+
{
72+
return mFunc;
73+
}
74+
auto returnAddress() const
75+
{
76+
return mReturnAddress;
77+
}
78+
auto& locals(size_t i)
79+
{
80+
return mLocals.at(i);
81+
}
82+
};
83+
84+
class VM
85+
{
86+
public:
87+
VM(std::vector<Byte> const& code, std::vector<Object> const& constantPool = {})
88+
: mCode{code}
89+
, mConstantPool{constantPool}
90+
{}
91+
void run();
92+
auto peekOperandStack() const
93+
{
94+
return mOperands.top();
95+
}
96+
auto& operandStack()
97+
{
98+
return mOperands;
99+
}
100+
private:
101+
std::vector<Byte> mCode{};
102+
std::vector<Object> mConstantPool{};
103+
size_t mIp{};
104+
std::stack<Object> mOperands{};
105+
std::stack<StackFrame> mCallStack{};
106+
};
107+
108+
template <typename T>
109+
auto fourBytesToInteger(Byte const* buffer) -> T
110+
{
111+
return static_cast<T>(buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3]);
112+
}

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ target_include_directories(lisp PUBLIC
33

44
target_sources(lisp PRIVATE
55
evaluator.cpp
6+
vm.cpp
67
)
78

89
target_compile_options(lisp PRIVATE ${BASE_COMPILE_FLAGS})

src/vm.cpp

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#include "lisp/vm.h"
2+
#include <iostream>
3+
4+
std::ostream& operator << (std::ostream& o, StackFrame const& f)
5+
{
6+
return o << "StackFrame " << f.func().name();
7+
}
8+
9+
std::ostream& operator << (std::ostream& o, FunctionSymbol const& f)
10+
{
11+
return o << "Function " << f.name();
12+
}
13+
14+
void VM::run()
15+
{
16+
while (mIp < mCode.size())
17+
{
18+
Byte bytecode = mCode[mIp];
19+
++mIp;
20+
switch (bytecode)
21+
{
22+
case kICONST:
23+
{
24+
int32_t word = fourBytesToInteger<int32_t>(&mCode[mIp]);
25+
mIp += 4;
26+
operandStack().push(word);
27+
break;
28+
}
29+
case kIADD:
30+
{
31+
int32_t rhs = std::get<int32_t>(operandStack().top());
32+
operandStack().pop();
33+
int32_t lhs = std::get<int32_t>(operandStack().top());
34+
operandStack().pop();
35+
int32_t result = lhs + rhs;
36+
operandStack().push(result);
37+
break;
38+
}
39+
case kSCONST:
40+
{
41+
uint32_t index = fourBytesToInteger<uint32_t>(&mCode[mIp]);
42+
mIp += 4;
43+
operandStack().push(mConstantPool.at(index));
44+
break;
45+
}
46+
case kPRINT:
47+
{
48+
auto op = operandStack().top();
49+
operandStack().pop();
50+
std::visit([](auto op)
51+
{
52+
std::cout << op << std::endl;
53+
}, op);
54+
break;
55+
}
56+
case kHALT:
57+
return;
58+
case kCALL:
59+
{
60+
uint32_t index = fourBytesToInteger<uint32_t>(&mCode[mIp]);
61+
mIp += 4;
62+
63+
auto const& functionSymbol = std::get<FunctionSymbol>(mConstantPool.at(index));
64+
std::vector<Object> params(functionSymbol.nbArgs() + functionSymbol.nbLocals());
65+
for (size_t i = functionSymbol.nbArgs(); i > 0; --i)
66+
{
67+
params.at(i - 1) = operandStack().top();
68+
operandStack().pop();
69+
}
70+
mCallStack.push(StackFrame{functionSymbol, std::move(params), mIp});
71+
mIp = functionSymbol.address();
72+
break;
73+
}
74+
case kRET:
75+
{
76+
mIp = mCallStack.top().returnAddress();
77+
mCallStack.pop();
78+
break;
79+
}
80+
case kLOAD:
81+
{
82+
uint32_t index = fourBytesToInteger<uint32_t>(&mCode[mIp]);
83+
mIp += 4;
84+
85+
operandStack().push(mCallStack.top().locals(index));
86+
break;
87+
}
88+
case kSTORE:
89+
{
90+
uint32_t index = fourBytesToInteger<uint32_t>(&mCode[mIp]);
91+
mIp += 4;
92+
93+
mCallStack.top().locals(index) = operandStack().top();
94+
operandStack().pop();
95+
break;
96+
}
97+
}
98+
}
99+
}

test/lisp/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
add_executable(unittests
22
test.cpp
3+
testVm.cpp
34
)
45
target_include_directories(unittests PRIVATE
56
${PROJECT_SOURCE_DIR}/src)

test/lisp/testVm.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#include "gtest/gtest.h"
2+
#include "lisp/vm.h"
3+
#include <numeric>
4+
5+
TEST(VM, add)
6+
{
7+
std::vector<Byte> const code = {kICONST, 0, 0, 0, 1, kICONST, 0, 0, 0, 2, kIADD};
8+
VM vm{code, {}};
9+
vm.run();
10+
auto result = vm.peekOperandStack();
11+
EXPECT_EQ(std::get<int32_t>(result), 3);
12+
}
13+
14+
TEST(VM, print)
15+
{
16+
std::vector<Byte> const code = {kICONST, 0, 0, 0, 1, kICONST, 0, 0, 0, 2, kIADD, kPRINT};
17+
VM vm{code, {}};
18+
testing::internal::CaptureStdout();
19+
vm.run();
20+
std::string output = testing::internal::GetCapturedStdout();
21+
EXPECT_EQ(output, "3\n");
22+
}
23+
24+
TEST(VM, str)
25+
{
26+
std::vector<Byte> const code = {kSCONST, 0, 0, 0, 0, kPRINT};
27+
VM vm{code, {"some str: 123"}};
28+
testing::internal::CaptureStdout();
29+
vm.run();
30+
std::string output = testing::internal::GetCapturedStdout();
31+
EXPECT_EQ(output, "some str: 123\n");
32+
}
33+
34+
TEST(VM, func)
35+
{
36+
std::vector<Byte> const code = {kICONST, 0, 0, 0, 11, kCALL, 0, 0, 0, 0, kHALT, kLOAD, 0, 0, 0, 0, kPRINT};
37+
std::vector<Object> const constPool = {FunctionSymbol{"main", 1, 1, 11}};
38+
VM vm{code, constPool};
39+
testing::internal::CaptureStdout();
40+
vm.run();
41+
std::string output = testing::internal::GetCapturedStdout();
42+
EXPECT_EQ(output, "11\n");
43+
}
44+
45+
TEST(VM, func2)
46+
{
47+
std::vector<Byte> const code = {kICONST, 0, 0, 0, 11, kCALL, 0, 0, 0, 0, kPRINT, kHALT, kLOAD, 0, 0, 0, 0, kICONST, 0, 0, 0, 2, kIADD, kSTORE, 0, 0, 0, 1, kLOAD, 0, 0, 0, 1, kRET};
48+
std::vector<Object> const constPool = {FunctionSymbol{"id", 1, 1, 12}};
49+
VM vm{code, constPool};
50+
testing::internal::CaptureStdout();
51+
vm.run();
52+
std::string output = testing::internal::GetCapturedStdout();
53+
EXPECT_EQ(output, "13\n");
54+
}

0 commit comments

Comments
 (0)