我們這次實(shí)現(xiàn)的簡(jiǎn)單虛擬機(jī),和計(jì)算機(jī)的 cpu 有點(diǎn)類似。 無非就是取指令,執(zhí)行指令之類的操作。
常見的虛擬機(jī)通常分為兩類,一種是棧式虛擬機(jī),另一種是寄存器虛擬機(jī)。比如說 CPython, Jvm 就是基于棧的虛擬機(jī),而 lua 則是基于寄存器的虛擬機(jī)。
我們這次實(shí)現(xiàn)的“玩具”虛擬機(jī),就是一種基于棧的虛擬機(jī)。
虛擬機(jī)有三個(gè)重要屬性,code 代表要執(zhí)行的指令列表,stack 用于保存臨時(shí)變量,而 addr 代表當(dāng)前指令的地址。
#?Python高效編程
class?Machine:
????def?__init__(self,?code):
????????self.code?=?code
????????self.stack?=?list()
????????self.addr?=?0
原理其實(shí)很簡(jiǎn)單,我們通過不斷獲取當(dāng)前指令地址,從指令列表中獲取指令和數(shù)據(jù),如果是數(shù)字或者字符串,就壓入棧中;如果是指令,就執(zhí)行相應(yīng)函數(shù)。
為了少些幾個(gè)字符,我們向 Machine 類中添加幾個(gè)方法:
def?push(self,?value):
????self.stack.append(value)
def?pop(self):
????return?self.stack.pop()
@property
def?top(self):
????return?self.stack[-1]
我們通過 dispatch 方法,來判斷當(dāng)前從指令列表中取得的片段是指令還是數(shù)據(jù):
def?dispatch(self,?opcode):
????dispatch_map?=?{
????????"%":????????self.mod,
????????"*":????????self.mul,
????????"+":????????self.plus,
????????"-":????????self.minus,
????????"/":????????self.div,
????????"==":???????self.eq,
????????"cast_int":?self.cast_int,
????????"cast_str":?self.cast_str,
????????"drop":?????self.drop,
????????"dup":??????self.dup,
????????"exit":?????self.exit,
????????"if":???????self.if_stmt,
????????"jmp":??????self.jmp,
????????"over":?????self.over,
????????"print":????self.print,
????????"println":??self.println,
????????"read":?????self.read,
????????"stack":????self.dump_stack,
????????"swap":?????self.swap,
????????}
????if?opcode?in?dispatch_map:
????????dispatch_map[opcode]()
????elif?isinstance(opcode,?int):
????????self.push(opcode)
????elif?isinstance(opcode,?str)\
????????and?opcode[0]?==?opcode[-1]?==?'"':
????????self.push(opcode[1:-1])
dispatch_map 就對(duì)應(yīng)我們?cè)?Machine 類中實(shí)現(xiàn)的方法。
比如說 plus 方法和 jmp 方法:
def?plus(self):
????v2?=?self.pop()
????v1?=?self.pop()
????self.push(v1?+?v2)
def?jmp(self):
????addr?=?self.pop()
????if?0?<=?addr?<?len(self.code):
????????self.addr?=?addr
????else:
????????raise?RuntimeError("addr?must?be?integer")
其余方法也很簡(jiǎn)單,大家可以直接查看源代碼。
好了,在加入一個(gè) run 函數(shù),我們就可以解釋代碼了。只要當(dāng)前地址小于指令長(zhǎng)度,就不斷取指令,執(zhí)行指令。
def?run(self):
????while?self.addr?<?len(self.code):
????????opcode?=?self.code[self.addr]
????????self.addr?+=?1
????????self.dispatch(opcode)
我們創(chuàng)建 Machine 類,并執(zhí)行 run 函數(shù), 注意字符串要用引號(hào)括起來 。
>>>?from?vm?import?Machine
>>>?Machine([521,?1314,"+",?6,?"*","println"]).run()
11010
我們還可以給虛擬機(jī)加一個(gè)交互式界面:
def?repl(prompt="VM>>?"):
????welcome()
????while?True:
????????try:
????????????text?=?read(prompt)
????????????code?=?list(tokenize(text))
????????????code?=?constants_fold(code)
????????????Machine(code).run()
????????except?(RuntimeError,?IndexError):
????????????stdout.write("1表達(dá)式不合法\n")
????????except?KeyboardInterrupt:
????????????stdout.write("請(qǐng)使用exit退出程序\n")
在讀取用戶輸入字符串之后,對(duì)字符串處理:
def?parse_word(word):
????try:
????????return?int(word)
????except?ValueError:
????????try:
????????????return?float(word)
????????except?ValueError:
????????????return?word
def?tokenize(text):
????for?word?in?text.split():
????????yield?parse_word(word)
最后放張效果圖:
201991
獲取源代碼。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元

