使用python写出一个Befunge语言编译器

2021年9月4日 569点热度 0人点赞

Befunge是由Chris Pressey在1993年创造,其显著特点是二维代码以及自我修改代码。其设计初衷是创造一种难以被编译的语言,但发布不久之后出现了各式各样的编译器。出于兴趣并查阅了大量资料,我顺利通过python语言将这一语言展现出来。

一.Befunge基本语法

Befunge的语法较为简单,具体见下:

# 0-9 将这个数字压入堆栈。
# +添加:弹出a和b,然后压入a+b。
# -减法:弹出a和b,然后压入b-a。
# *乘法:弹出a和b,然后压入a*b。
# /整数除法:弹出a和b,然后 压入 b/a,向下取整。
# %取模:弹出a和b,然后按下b%a。如果a为零,则按零。
# ! 逻辑非:弹出一个值。如果值为零,则按下1;否则,按零。
# `(反引号)大于:弹出a和b,然后推送1if b>a,否则推送零。
# > 开始向右移动。
# < 开始向左移动。
# ^ 开始向上移动。
# v 开始向下移动。
# ? 开始向随机的主要方向移动。
# _弹出一个值;如果 向右移动value = 0,否则向左移动。
# |弹出一个值;向下移动value = 0,否则向上移动。
# "开始字符串模式:将每个字符的 ASCII 值压入栈一直推到下一个".
# :堆栈顶部的重复值。如果堆栈顶部没有任何内容,则将0压入.
# \交换堆栈顶部的两个值。如果只有一个值,假设0堆栈底部有一个额外的值。
# $ 从堆栈中弹出值并丢弃它。
# . 弹出值并输出为整数。
# , 弹出值并输出存储在值中的整数代码表示的ASCII字符。
# # 蹦床:跳过下一个单元格。
# p“放置”调用(一种存储值以供以后使用的方法)。流行y,x并且v,把第x行第Y列的字符换成v。
# g“get”调用(一种在存储中检索数据的方法)。弹出y和x,然后在程序中压入第x行第y列字符的 ASCII 值。
# @ 结束程序。
# & 读取一个整数并压入栈
# ~ 读取一个字符并压入栈
#(即一个空格)无操作。什么也没做。

所有代码(含注释)为:

import random
import time
import tkinter
import sys
import tkinter.simpledialog     # 导入对话框相关包
# 在python中二维代码以二维数组的形式存在
# 数组理论上无限大小,这里假设为N*N大小

# 相关全局变量
mode = 1   # 窗口模式,初始为1
codes = ''  # 代码文本
stack = []  # 堆栈
direction = 'right'     # 指针运行方向
text1 = []  # 辅助元组1
code = []  # 辅助元组2
x = y = 0   # 指针坐标
point = None  # 指针位置
jump = False    # 是否跳过
char_mode = False   # 是否处于字符串模式
output_text = ''     # 输出的文字
first_time = True   # 第一次建立窗口
write_mode = False  # 是否处于输入模式
wait = False    # 程序等待
write_kind = 0
a = ''


# 相关子函数
def run():      # 程序开始运行
    global mode, codes
    codes = input_text.get('1.0', 'end')
    input_window.destroy()  # 关闭输入窗口
    mode = 2


def end():      # 结束程序
    sys.exit()


def pos(m, n):  # 返回指定位置的字符
    return code[int(n)][int(m)]


def replace(o, p, q):  # 将(x,y处的字符换成v
    code[int(p)][int(o)] = q


def input_int():    # 定义整数输入框
    result = tkinter.simpledialog.askinteger(title='获取整数', prompt='请输入一个整数:')
    return result


def input_str():    # 定义字符输入框
    result = tkinter.simpledialog.askstring(title='获取字符', prompt='请输入一个字符:')
    return result


def next_one():     # 指针指向下一个
    global x, y
    if direction == 'up':
        x = x - 1
    elif direction == 'down':
        x = x + 1
    elif direction == 'left':
        y = y - 1
    elif direction == 'right':
        y = y + 1


def refresh():
    global show_box, screenWidth, screenHeight, output_window, width, height, left, top, end_button, write_box
    global write_button
    screenWidth = output_window.winfo_screenwidth()
    screenHeight = output_window.winfo_screenheight()
    width = 600
    height = 400
    left = (screenWidth - width) / 2
    top = (screenHeight - height) / 2
    # 输出窗口设置
    show_box = tkinter.Text(output_window, height=27, width=75)  # 输出文本框
    show_box.place(x=10, y=10)
    show_box.delete('1.0', 'end')
    for group in code:
        for word in group:
            show_box.insert('insert', word)
        show_box.insert('insert', '\n')
    show_box.tag_add('the_tag', str(str(x + 1) + '.' + str(y)))     # 高亮文本显示
    show_box.tag_config('the_tag', background='yellow')
    show_box.update()
    end_button = tkinter.Button(output_window, text='结束程序', command=end)  # 结束程序按钮
    end_button.place(x=100, y=370)
    end_button.update()
    stack_box.delete(0, 'end')      # 更新栈窗口
    for item in stack:
        stack_box.insert(0, item)
    stack_box.update()
    if len(output_text) != 0:
        out = tkinter.StringVar()
        out.set(output_text)
        show_ans = tkinter.Label(output_window, textvariable=out)
        show_ans.place(x=10, y=300-int(len(output_text) / 5))
        show_ans.update()


while True:
    if mode == 1:
        input_window = tkinter.Tk()  # 创建输入窗口
        input_window.title('Befunge编译器')  # 设置窗口名称
        # 设置窗口大小以及位置
        screenWidth = input_window.winfo_screenwidth()
        screenHeight = input_window.winfo_screenheight()
        width = 600
        height = 400
        left = (screenWidth - width) / 2
        top = (screenHeight - height) / 2
        input_window.geometry("%dx%d+%d+%d" % (width, height, left, top))  # 设置窗口大小
        input_text = tkinter.Text(input_window)  # 定义命令文本输入框
        input_text.pack()
        button = tkinter.Button(input_window, text='运行', command=run)  # 定义按钮
        button.pack()
        end_button = tkinter.Button(input_window, text='结束程序', command=end)  # 结束程序按钮
        end_button.place(x=100, y=370)
        input_window.mainloop()
    elif mode == 2:
        if first_time:
            output_window = tkinter.Tk()    # 创建输出窗口
            output_window.title('Befunge编译器')
            screenWidth = output_window.winfo_screenwidth()
            screenHeight = output_window.winfo_screenheight()
            width = 600
            height = 400
            left = (screenWidth - width) / 2
            top = (screenHeight - height) / 2
            output_window.geometry("%dx%d+%d+%d" % (width, height, left, top))  # 设置窗口大小
            show_box = tkinter.Text(output_window, height=27, width=75)  # 输出文本框
            show_box.place(x=10, y=10)
            show_box.insert('insert', codes)
            end_button = tkinter.Button(output_window, text='结束程序', command=end)    # 结束程序按钮
            end_button.place(x=100, y=370)
            stack_box = tkinter.Listbox(output_window, width=5)      # 堆栈
            stack_box.place(x=550, y=10)
            show_box.tag_add('the_tag', str(str(x + 1) + '.' + str(y)))     # 高亮文本定位
            show_box.tag_config('the_tag', background='yellow')


        # 运算程序
        # 二维代码数组化
        if first_time:
            code = []
            for x0 in codes:
                if x0 != '\n':
                    text1.append(x0)
                else:
                    code.append(text1)
                    text1 = []
            first_time = False           # 并非第一次刷新
        # 依次读取字符并进行操作
        if point != '@':  # 程序终止条件:读取到字符@
            point = code[x][y]  # 指针刷新
            time.sleep(0.1)
            # 更新相关组件
            refresh()
            # 下面依次对不同字符给予操作
            if not char_mode:  # 字符串模式关闭情况下才能实现字符功能
                if not jump:
                    if isinstance(point, int):  # 如果输入0~9,则将数字压入栈
                        stack.append(int(point))
                    elif point == '+':
                        a = stack.pop()
                        b = stack.pop()
                        c = int(a) + int(b)
                        stack.append(c)
                    elif point == '-':
                        a = stack.pop()
                        b = stack.pop()
                        c = int(b) - int(a)
                        stack.append(c)
                    elif point == '*':
                        a = stack.pop()
                        b = stack.pop()
                        c = int(a) * int(b)
                        stack.append(c)
                    elif point == '/':
                        a = stack.pop()
                        b = stack.pop()
                        c = int(int(b) / int(a))
                        stack.append(c)
                    elif point == '%':  # 四则运算符
                        a = stack.pop()
                        b = stack.pop()
                        c = int(b) % int(a)
                        stack.append(c)
                    elif point == '"':  # 开启字符串模式,直到下一个"为止遇到的所有字符全部压入栈
                        char_mode = True
                    elif point == '&':  # 输入一个整数并压入栈
                        a = input_int()
                        stack.append(a)
                    elif point == '~':  # 输入一个字符,
                        a = input_str()
                        stack.append(a)
                    elif point == '.':  # 弹出一个元素并作为整数输出
                        a = stack.pop()
                        if isinstance(a, int):
                            output_text = output_text + str(a)
                        else:
                            output_text = output_text + str(ord(str(a)))
                    elif point == ',':  # 弹出一个元素并作为字符输出
                        a = stack.pop()
                        if not isinstance(a, int):
                            output_text = output_text + str(a)
                        else:
                            output_text = output_text + chr(a)
                    elif point == '_':  # 弹出一个元素,若为0 向右走否则向左走
                        a = stack.pop()
                        if a == 0:
                            direction = 'right'
                        else:
                            direction = 'left'
                    elif point == '|':  # 弹出一个元素,若为0向下走否则向上走
                        a = stack.pop()
                        if a == 0 or len(stack) == 0 or a == '0':
                            direction = 'down'
                        else:
                            direction = 'up'
                    elif point == ':':  # 复制栈顶的元素
                        if len(stack) == 0:
                            stack.append(0)
                        else:
                            a = stack.pop()
                            stack.append(a)
                            stack.append(a)
                    elif point == '\\':  # 交换栈顶两元素
                        a = stack.pop()
                        b = stack.pop()
                        stack.append(a)
                        stack.append(b)
                    elif point == '#':  # 跳过下一个指令
                        next_one()
                    elif point == '!':  # 弹出一个值,如果是0压入1,否则压入0
                        a = stack.pop()
                        if a == 0:
                            stack.append(1)
                        else:
                            stack.append(0)
                    elif point == '`':  # 弹出a和b,如果b>a 压入1,否则压入0
                        a = stack.pop()
                        b = stack.pop()
                        if b > a:
                            stack.append(1)
                        else:
                            stack.append(0)
                    elif point == 'g':  # 弹出y和x,然后压入(x,y)处的字符的ASCLL值
                        y2 = stack.pop()
                        x2 = stack.pop()
                        stack.append(ord(pos(x2, y2)))
                    elif point == 'p':  # 弹出y,x,v,然后将(x,y)处替换为v
                        y2 = stack.pop()
                        x2 = stack.pop()
                        v = stack.pop()
                        replace(x2, y2, v)
                    elif point == '?':  # 随机选择方向
                        dirctions = ['left', 'right', 'up', 'down']
                        direction = dirctions[random.randrange(0, 4)]
                    elif point == '$':  # 丢掉栈顶
                        a = stack.pop()
                    elif point == ' ':  # 空格跳过
                        pass
                    elif point == '>':  # 四个方向
                        direction = 'right'
                    elif point == '<':
                        direction = 'left'
                    elif point == '^':
                        direction = 'up'
                    elif point == 'v':
                        direction = 'down'
                else:
                    jump = False
            elif point == '"' and char_mode:  # 读取对应",关闭字符串模式
                char_mode = False
            else:
                stack.append(point)  # 字符串,模式中所有字符都会压入栈
            # 指针坐标变动
            next_one()
        else:
            print(output_text)  # 输出所有
            output_window.mainloop()

能满足基本的代码需求,至于美观性就有时间再完善吧( •̀ ω •́ )y,以下为代码运行示例:

Wantz

这个人很懒,什么都没留下