3 个版本
0.1.2 | 2023 年 8 月 30 日 |
---|---|
0.1.1 | 2023 年 8 月 30 日 |
0.1.0 | 2023 年 8 月 30 日 |
#532 in 编程语言
27KB
527 行
chicken_esolang
Chicken esolang 的解释器,使用 Rust 构建。如以下内容所示,原始解释器的网页存档对于理解某些实现细节至关重要。
命令行界面使用方法
cargo run -- -f filename [--debug] [--backwards_compatible] [-i optional_input]
库使用方法
use chicken_esolang::Chicken;
let code = String("chicken");
let input = "";
let debug = false;
let backwards_compatible = false;
let mut chicken = Chicken::new(code, input, debug, backwards_compatible);
chicken.run()
JavaScript 解释器或:如何停止担心并学习鸡鸡鸡
tl;dr 99 个香蕉和 DeadFish 程序需要 --backwards_compatible 标志才能像通过原始解释器运行时那样工作。
对于这样的 esolang,抱怨其复杂的设计似乎很愚蠢——这基本上就是它的目的。然而,了解示例程序在正常运行时对原始解释器所写的 JavaScript 中的怪癖有多么依赖是很重要的。理解原始解释器几乎让我发疯,但这是调试看似无法解释的行为所必需的。请参阅 js_interpreter/demystified.js.txt
文件以获取分析。
Rust 的美在于它通过严格的类型(以及其他规则和保证)迫使开发者面对可能不规律的程序行为。本着这种精神,如果你想用鸡编写程序——明确地说,你不应该这样做,但如果你坚持这样做——这个解释器默认情况下会在原始解释器只会做些奇怪的事情的情况下抛出错误。向后兼容模式是为了在运行旧示例时模拟原始解释器的行为而专门设计的。
以下是原始解释器的反直觉行为以及我选择如何处理它们。一般来说,我只在示例程序依赖于它时才实现了特定的 JS 行为。
比较
99只鸡程序的第34条指令将比较0与从用户输入寄存器加载的值。程序依赖于这一点在某些时候为真,以便跳转到末尾并构建最终的no chickens
(对于某些输入)。理智的编程语言在比较0和空字符串时会返回false,但不是JavaScript!因此,当backwards_compatibility
启用时,比较模仿了JavaScript松散相等运算符的行为,并且'' == 0
被评估为truthy。
原始解释器在比较时返回true
和false
,当它们被拼接时会被转换为字符串。Deadfish依赖于这种行为来从false
中非法地检索字母s
,因此,在向后兼容模式下,我们返回"false".to_string()
,并硬编码空字符串比较的真值。
ADD
JavaScript中的加法运算符也用作字符串连接,自动将整数转换为字符串。我选择普遍模拟这一点,因为程序的架构依赖于最终输出存储在单个堆栈令牌中。
理论上,如果NaN
和undefined
在加法操作的任一边,JavaScript也会将它们转换为字符串。请参阅下文的SUB和LOAD条目,了解这些值何时成为问题以及我是如何处理的。
SUB
如果你给99只鸡一个字符串作为输入,它将返回以下模式:OriginalInputString chickens 1 chicken no chickens
,即OriginalInputString chickens\n1 chicken\nno chickens\n
。发生的事情是这样的:程序将在两次尝试中尝试减少输入,然后根据结果进行跳转(跳转是指令36和59)。在JS中,字符串减去数字等于NaN
,这是假的。因此,两次SUB的结果都未能通过向前跳转的条件,该跳转本应考虑输入为1的情况,以及向后跳转的该情况,该跳转旨在创建一个循环,直到计数减少到1。
为了在向后兼容模式下复制此操作,我为SUB编写了一个特殊版本,当操作数不匹配时,它返回Token::Chars("NaN".to_string())
。然后COMPARE会检查"NaN"
作为特殊案例。
CHAR
在JS解释器中,ASCII转换只是简单地插入在&#
和;
之间的字符码,从而有效地构建HTML字符码。这在“99 chickens”中扮演着重要角色,关于LOAD指令的详细解释在相关章节中。
LOAD
“99 chickens”和“Deadfish”都有从0和1以外的堆栈索引加载指令序列。这是一种用于更快地从覆盖的指令寄存器中检索存储的字符串字符的技巧。
值得注意的是,为了获取“no chickens”开头的n
,第11个字符是从索引2的值加载的,即 chickens
。讽刺的是,CHAR必须推送HTML字符码才能使此操作生效——第11个索引在" chickens\n"
中越界,但n
本身是直接前置的,因此输出看起来像no chickens
。
如果索引大于或等于标记的长度,JavaScript返回特殊的undefined
值。Deadfish依赖这种行为来巧妙地检索字母i
,因此在向后兼容模式下,我们返回"undefined".to_string()
。
堆栈
堆栈中的第一个项目应该用对堆栈自身的引用初始化。对于JavaScript这样的语言来说,这很简单,但Rust被设计成使这类事情变得困难。我可以通过将堆栈实现为RefCell<Vec<Token>>
来实现,向Token枚举中添加另一个值,即StackReference(RefCell<Vec<Token<'a>>>)
,并在需要推或弹出堆栈时使用borrow_mut()
。然而,由于这仅在LOAD指令中相关,且放弃了借用检查的编译时强制,我认为这是过度的。相反,我选择将堆栈的第一个值用作指令寄存器,并将双宽的LOAD指令实现为两个单独的函数,即load_from_stack
和load_from_token
。