问题描述
只有在 repl 中逐行输入代码时,我才会收到错误。当整个程序一次粘贴或从命令行粘贴时,它就可以工作。
class A {
method a () {
return 1;
}
}
class B {
method b () {
return 2;
}
}
这是错误声明:
===SORRY!=== Error while compiling:
Package 'B' already has a method 'b' (did you mean to declare a multi method?)
这个屏幕截图可能会更清楚。在左边我只是粘贴了代码,在右边我一行一行地输入了它。代码仍然有效,但导致错误的原因是什么?
出于某种原因,我无法仅使用一个类来重现这一点。
解决方法
我可以重现那个错误,看起来像是 REPL 错误,或者只是 REPL 不准备做的事情。例如,这也会引发异常:
class A {
method a() {
return 1;
}
};
class foo {
has $.bar = 3;
};
无论哪种形式,都可以直接粘贴或分段粘贴。永远是二等舱。这可能与 EVAL
的工作方式有关,但我真的不知道。归根结底,REPL 只能带您到此为止,我不完全确定这是否在用例范围内。对于比一行更复杂的任何事情,您可能想使用 Comma 或任何其他 IDE,例如 emacs;逗号还为评估表达式甚至语法提供帮助。
我认为 Comma 是蜜蜂的膝盖。而且我几乎从不使用 repl。但我喜欢:
-
高尔夫你的例子是绰绰有余的MRE。但我喜欢尽量减少错误示例。
-
推测我想我可以看到发生了什么。
-
Spelunking 编译器代码 Rakudo 大部分是用 Raku 编写的;也许我们可以找出 REPL 代码(它是编译器的一部分)中的这个问题?
高尔夫
首先,错误:
Welcome to ??????™ v2021.03.
Implementing the ????™ programming language v6.d.
Built on MoarVM version 2021.03.
To exit type 'exit' or '^D'
> # 42
Nil
> { subset a
*
===SORRY!=== Error while compiling:
Redeclaration of symbol 'a'.
at line 3
------> <BOL>⏏<EOL>
评论:
-
要进入球道,请输入任何一行,而不仅仅是空格,然后按 Enter。
-
选择合适的熨斗;用
{
打开一个块,声明一些命名类型,然后按 Enter。 REPL 通过显示*
多行提示表示您处于绿色状态。 -
要击沉球,只需按 Enter。
其次,打高尔夫球有助于投机:
> # 42
Nil
> { BEGIN say 99
99
* }
99
>
(BEGIN
标记在编译器遇到它时将在编译期间运行的代码。)
推测
为什么初始 # 42
评估很重要?据推测,REPL 试图在 REPL 会话期间维护声明/状态(变量和类型等)。
作为其中的一部分,它可能会记住会话中的所有先前代码。
并且大概它看到除了空行之外的任何东西都被视为与以前的代码一样计数。
仅仅存在一些/任何以前的代码会以某种方式影响 REPL 维护的状态和/或它要求编译器做什么。
也许吧。
为什么类型声明很重要,比如变量声明不重要?
大概是 REPL 和/或编译器区分了这两种声明。
忽略 REPL,在编译代码时,重复的 my
声明只会引发警告,而重复的类型声明是错误的。很有可能这就是为什么?
为什么类型声明会产生这种效果?
大概是该类型编译成功,然后才抛出异常(因为缺少右大括号导致代码不完整)。
然后 REPL 要求编译器 再次 尝试编译到目前为止输入的多行代码,以及用户输入的任何附加代码(在我的高尔夫版本中,我什么都不输入,只需按 Enter ,不再添加代码)。
这个重复的编译尝试再次包括类型声明,该类型声明,因为先前尝试编译多行代码的编译状态以某种方式被保留,被编译器视为重复 声明,导致它抛出异常,导致 REPL 退出多行模式并报告错误。
换句话说,REPL 循环大概是这样的:
-
在输入每一行时,将其传递给编译器,编译器会编译代码并在出现任何错误时抛出异常。
-
如果抛出异常:
2.1 如果处于多行模式(带
*
提示),则退出多行模式(返回>
提示)并显示异常信息。2.2 否则(所以不是在多行模式下),如果对异常和/或输入的代码的分析(似乎非常基本)表明多行模式会很有用,那么进入该模式(使用
*
迅速的)。在多行模式下,每次用户按 Enter 键时,都会重新编译到目前为止的整个多行代码。2.3 否则,显示异常信息。
-
(鉴于需要从一些评估开始以显示此错误,显然还有其他一些事情与初始化相关,但这很可能是一个完全不同的问题。)
正在搜索
我浏览了 GH 上与“repl”匹配的新旧队列中所有未解决的 Rakudo 问题。我选择了四个来说明 REPL 在维护会话状态方面的困难范围:
-
REPL loses custom operators。 “有趣的是,如果像这样的后缀运算符是由 REPL 加载的模块导出的,则 REPL 可以成功解析该运算符一次,之后它将失败并出现类似于上述的错误。”这是否与此 SO 所关注的错误直到 第二次 或以后的评估才出现的方式有关?
-
Perl6 REPL forgets the definition of infix sub。看起来像上述问题的欺骗,但包括来自 Brian Duggan 的额外调试好东西。 ❤️
我没有做的一件事是检查这些错误是否仍然存在。我的猜测是他们这样做。和他们一样的还有很多。也许他们有一些共同的原因?我不知道。或许我们需要看看代码...
洞穴探险
在 Rakudo 资源中搜索“repl”很快就找到了一个 REPL
模块。不到 500 行高级 Raku 代码! \o/(现在,让我们假设我们几乎可以忽略深入研究它调用的代码......)
从我最初的浏览器中,我会提请注意:
-
A
sub repl
:sub repl(*%_) { my $repl := REPL.new(nqp::getcomp("Raku"),%_,True); nqp::bindattr($repl,REPL,'$!save_ctx',nqp::ctxcaller(nqp::ctx)); $repl.repl-loop(:no-exit); }
Blame 显示 Liz added this a couple months ago。这与此错误非常相关,但我猜测名称中带有
ctx
的方法和变量非常重要,因此希望这是开始思考这一点的好方法。 -
method repl-eval
。 30 行左右。 -
REPL: loop { ... }
。 60 行左右。
今晚就到此为止。我会发布这个然后改天再回来。