[譯] Javascript 中最長的關鍵字序列長什麼樣子?

熊咆龍吟發表於2019-03-18

最近有幾個關於使用 Javascript 編寫最長關鍵字序列的挑戰。

但問題是:

  • 這些解決方案使用了非關鍵字標記(null、true、false 實際上是字面量,而不是關鍵字)
  • 其中一個解決方案有些說不通

讓我們試試能不能做的更好。

(但是我們首先要回顧一些基礎規則)

規則

  1. 程式碼必須能作為有效的 Javascript 進行解析和執行。不能忽略 early errors
  2. 只允許使用關鍵字
  3. 除小寫字母外,其它唯一允許的字元是空格
  4. 不能在序列中重複使用一個關鍵字
  5. 您可以根據需要新增儘可能多的前同步碼和後同步碼

額外挑戰

  1. 關鍵字之間允許換行
  2. 允許使用類似關鍵字的標記

進入正題

@arjunb_msft 提出的最長 15 個關鍵字的程式

function *f() {
  if (1);
  else do return yield delete true instanceof typeof void new class extends false in this {}; while (1)
}
複製程式碼

不幸的是,他的方法裡使用了保留字 truefalse,而兩者實際上不是關鍵字。在 Chrome 中執行程式也會丟擲一個錯誤:“Uncaught SyntaxError: Unexpected token in”。

@bluepnume 提出 15 個關鍵字的方案是:

async function* foo() {
  return yield delete void await null in this instanceof typeof new class extends async function () {} {}
}
複製程式碼

這段程式可以在 Chrome 中執行,但是程式中使用了 null,這也不是一個關鍵字。

雖然有些賣弄,如果我們從第二個解決方案中剔除 null,並結合第一個解決方案,可以得到一個不同的 15 個關鍵字長度的解決方案:

async function* foo() {
  if (0);
  else do return yield delete void await this instanceof typeof new class extends async function
  () {} {}; while (0)
}
複製程式碼

哦耶!

更有趣的在這兒

雖然這樣做沒什麼意思,但賣弄知識卻很有趣。

但不用擔心,因為在下面的討論中 Bterlson 作了這樣的補充:

thisnullundefined 可以認為是關鍵字,即使它們在技術上不是關鍵字。這使得比賽更有趣(加上編輯們把它們標記成關鍵字,所以這麼說也行得通)

從技術層面講,this 實際上是一個關鍵字。但是,Bterlson 對 nullundefined 不是關鍵字的認定卻是正確的。

在餘下部分,我們可以看到 truefalse 也被當作關鍵字使用。這就給我們帶來了一個問題:如果可以使用非關鍵字標記,那麼對於這個挑戰,哪些標記更合適?

nulltruefalse 的共同點是它們都是隻包含字母的字面量(顯然,包含字元和數字的字面量是不允許的)。

由於可以使用 null 字元和 boolean 字元,我們可以輕鬆地復現出先前的序列,並構建 17 個單詞長度的序列:

async function* foo() {
  if (0);
  else do return yield await delete void typeof null instanceof this in new class extends async function () {} {}; while (0);
}
複製程式碼

undefined 呢?它實際上是一個識別符號。如果允許 ASI,那我們就可以使用任意識別符號去構造一個無限序列,但這就索然無味了,也失去了挑戰的樂趣。

a
b
c
// boooring
複製程式碼

我倒認為這項挑戰的意義在於僅使用類似關鍵字的標記完成挑戰(即使這些類似關鍵字的詞在技術層面不能算作規範的關鍵字)。

下面是一些看起來像關鍵字但實際上不是關鍵字的標記:

let x
for (foo of bar) {}
class { static foo() {} }
import {foo as bar} from 'baz'
{get foo() {}, set foo() {}}
複製程式碼

如果你不喜歡仔細研究程式語言諸多的規範,而且不能一眼看出哪些標記是關鍵字,哪些不是關鍵字,那麼下面的標記都可以當作是關鍵字:letofstaticasfromgetset。它們看起來也確實像關鍵字。

我們可能認為不可以往上面的列表中新增 NaNInfinity 之類的東西,是因為它們與 undefined 屬於同一個型別,都是識別符號(識別符號總是指向相同的值),也可能是由於只允許使用小寫字元。不管怎樣,我們將它們排除在外。我們也應該排除 atguments,因為在語法規範中它沒有作為標記出現,因此它實際上只是一個 magic 變數,而不是關鍵字。

另一個我們需要排除是 new.target,因為它中間有一個“.”。

一些標記例如 enumpublic 是保留字,它們看起來非常像關鍵字,特別是如果你熟悉像 Java 這樣的語言。問題是,它們在語法中幾乎處處都會自動變成語法錯誤,所以即使我們允許使用它們,也不能真正地使用它們...

// the party poopers

let enum // SyntaxError
interface Bar {} // SyntaxError
package Baz; // SyntaxError
class {
  private foo() {} // SyntaxError
}
複製程式碼

既然我們已經理清了規則,我們接下來能做什麼呢?

當然有很多啦

由於一貫的向後相容性問題,在某些情況下,許多“關鍵字” 充當...呃,不能稱它們為關鍵詞。我們之前說過濫用 ASI 和識別符號很無聊,但你知道嗎?在 Javascript 他們卻是有效的語法。

var undefined
typeof let
複製程式碼

這當然不是無聊的,而且非常有希望,所以以娛樂的名義,我們必須允許它。

最後還有一個小細節要談。雖然上面的程式碼片段很有趣,而且讓人眼花繚亂,但它有一個問題:它跨越了兩行。很不幸,但我們需要 ASI 將這兩個語句分開,所以我們無法將它們放在同一行。

或者這樣做:

輸入一個段落分隔符(\u2029),如果正確呈現,它看起來如下:

什麼都看不見?這就對了!這是一個隱形變數

現在,有了上面的知識儲備,我們可以提出自己的解決方案:

async function* foo() {
  from: set: while (0) {
    if (0)
    throw aselse thisnullcontinue fromfalsebreak set
 truevar letdebuggerdo return yield await delete void typeof get instanceof static in new class of extends async function undefined
    () {} {}; while (0);
  }
}
複製程式碼

你沒看錯,這就是在 Chrome 上解析和執行有效的 Javascript 程式。這可是在一行中有 32 個關鍵字的序列!

當然,並不是所有 32 個詞都是關鍵字,這可能是 ASI 有史以來最嚴重的濫用,但是,這仍然有挑戰意義。另外,我很開心,這才是最重要的!

那麼,你覺得呢?你能做一個更長的序列嗎?你能弄明白為什麼這在語法上是有效的嗎?這是作弊嗎?Gists [譯者注:原文釋出在 gist 上]是有史以來最被濫用的部落格平臺嗎?

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章