<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/feeds/atom-style.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://657.life/zh-tw/</id>
    <title>六五七的生活</title>
    <updated>2025-09-30T02:07:50.448Z</updated>
    <generator>Astro-Theme-Retypeset with Feed for Node.js</generator>
    <author>
        <name>657</name>
        <uri>https://657.life</uri>
    </author>
    <link rel="alternate" href="https://657.life/zh-tw/"/>
    <link rel="self" href="https://657.life/zh-tw/atom.xml"/>
    <subtitle>這是我的個人網站，用於記錄我在井底看到的天空。</subtitle>
    <rights>Copyright © 2025 657</rights>
    <entry>
        <title type="html"><![CDATA[何留意的海]]></title>
        <id>https://657.life/zh-tw/posts/he-liu-yi-de-hai/</id>
        <link href="https://657.life/zh-tw/posts/he-liu-yi-de-hai/"/>
        <updated>2025-08-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[人生像是一望無際的海洋。每一個人都是孤島，漂泊、隔絶，卻又彼此在深不可測的洋流中相互聯繫——隻是他們並不自知。何留意不是一座島嶼，他拒絶了這種狹隘的疆域觀唸。他立誌要成爲那整個汪洋本身，包容一切島嶼、暗礁、鯨群與沉船的浩瀚。]]></summary>
        <content type="html"><![CDATA[<p>關於何留意此人，我們所知甚少，一如我們對任何一片海域的真正了解，總是停留在其變幻莫測的表面。歷史，尤其是個人史，無非是一些倖存的、支離破碎的島嶼，四周是沉默的、無法測度的遺忘之洋。有人，或許是某個已經湮沒在瓜哇或蘇門答臘的無名傳教士，曾斷言：人生像是一望無際的海洋，每一個人都是孤島，漂泊、隔絕，卻又彼此在深不可測的洋流中相互聯繫——只是他們並不自知。在《人類神學之謬誤》這本被梵蒂岡列為禁書的冊子裡，我曾讀到過類似的論斷。</p>
<p>何留意拒絕了這種狹隘的疆域觀念。他立志要成為那整個汪洋本身，包容一切島嶼、暗礁、魚群與沉船，以及那些島嶼上短暫的、喧囂的文明。他的計劃近乎一種瀆神的狂妄，一種形而上學的篡奪。</p>
<p>這門成為大海的哲學，其濫觴可追溯至他二十歲那年。在一個潮濕的、仿佛剛從海底打撈上來的午後，他讀到一部被西方世界誤解為東方神秘主義箴言的古書——《道德經》。其中一句，如同投入他心湖的石子，激起了無窮的盡的漣漪：「上善若水，水善利萬物而不爭。」這兩個字——「不爭」——成為了他一生的羅盤，指針永遠指向退讓與包容的方位。</p>
<p>為了踐行這幾乎不可能的教條，他開始系統性地清空自我。在朋友們為究竟是該多研究些「問題」，還是該擁抱一個根本性的「主義」爭得面紅耳赤時，他保持着深海般的沉默，他能聽到他們論點之下脆弱的基石，以及欲望那如同海怪般攪動的觸手。他退出了所有世事的角逐，將功名利祿視為海岸上瑣碎的貝殼，潮來時被短暫沖刷，潮去時便曝於烈日，終將化為齏粉。他辭去了在海關那份頗有前途的文書工作，因為他發現，審視貨船的清單與盤查水手的名冊，是在強化一種「邊界」的幻覺，而他，決心要廢除一切邊界。</p>
<p>甚至，當一段後人稱之為「愛情」的激流湧入他的生命時，他也以退讓為懷。那個女子，我們不知其名，在何留意的回憶錄（一部從未被書寫，僅存在於潮汐漲落間的著作）里，她被記為「來自上游的芬芳」。她的話語如山澗清泉，她的觸摸如春日暖流。然而，何留意察覺到，愛情的本質是一種最激烈的「爭」，是靈魂對另一靈魂的占有，是兩座孤島試圖合併成一塊大陸的徒勞嘗試。於是，他選擇成為河口。他沒有挽留，也沒有追逐，只是以無垠的寬廣與略帶鹹味的沉默，接納了這條河流的匯入，也接納了她最終流向更遠方的、不可知的命運。她離去後，他並未感到失落，只是覺得自己的「海域」中，多了一股溫暖而又苦澀的洋流。</p>
<p>他選擇的居所本身就是一則寓言。那是一座位於南海海岸的舊屋，牆壁上凝結着鹽與時間的結晶。屋中陳設簡陋得如同一個禁欲主義者的祈禱室。一張被海風侵蝕得如同鯨骨的木桌，以及一隻破望遠鏡。望遠鏡的鏡片上有一層厚得無法拭去的鹽霧。外人以為這望遠鏡已然報廢，但他們錯了。何留意從不用它眺望遠方的船隻或島嶼，那屬於「疆域」的範疇。他用它凝視近在咫尺的海浪。</p>
<p>透過那層鹽霧，時空發生了奇異的折射。他看到的不是此刻的海，而是所有時間裡的海。一朵浪花破碎的瞬間，他看到了其中包裹着的、一千年前某個被充軍的宋代詩人的嘆息；海面上粼粼的波光，是前往新大陸的水手們的祈禱與貪婪的倒影；風暴的怒吼中，他聽到了鄭和寶船艦隊的船錨沉入海底時，那撕裂歷史的巨響。這望遠鏡並非觀察之工具，而是冥想之媒介，那層鹽霧不是障礙，而是濾鏡，濾去了短暫的現實，使永恆的本質得以顯現。</p>
<p>他的書房裡沒有書，或者說，只有一本——那便是南海本身。他放棄了文字，因為文字是固化的、有限的符號，是風乾的魚，早已失去了海洋的生命。他日復一日「閱讀」那片活着的、無限的文本。潮汐是它的呼吸，是它章節的起落；洋流是它龐大的句法，聯結着不同的時空與敘事；風暴是它激昂的感嘆，而無風的靜謐則是它意義最為幽深的留白。</p>
<p>漸漸地，一種恐怖而又莊嚴的變化在他身上發生。他自己的記憶開始與大海的記憶混淆。他會為一個在亞丁灣被海盜殺害的腓尼基水手感到悲傷，會為一條在馬里亞納海溝深處死去的抹香鯨感到孤獨。他的身份，那個名為「何留意」的脆弱容器，開始出現裂痕。海水，那承載着地球全部歷史與遺忘的液體，正從裂縫中湧入，淹沒他個人的、微不足道的存在。一些晦澀的、記錄在羊皮紙上的諾斯替派文獻曾提及一種「液體神化」的儀式，修煉者通過沉思將自我消融於原始之水中。何留意的實踐，無疑是這種古老異端思想在東方的孤獨迴響。</p>
<p>我所能查到的、關於何留意的最後一份記錄，來自一位遠房的、經商致富的表兄。這位表兄在某個初秋前來探望他，試圖以世俗的成功勸說他回歸「正常」的生活。他發現何留意坐在海邊的礁石上，目光空洞，對他的呼喚置若罔聞。表兄談起童年往事，談起他們曾一起偷看鄰村社戲的夏夜。何留意沉默良久，忽然用一種不屬於他自己的、古老的聲調回答：「那晚的月光，含鹽量比今晚要低一些。西南方二十三海里處，有一群烏賊正在產卵。」</p>
<p>表兄驚恐地離去，向所有人宣告何留意已經瘋了。</p>
<p>不久後，何留意消失了。村民們說，他只是在一個平凡的清晨，赤着腳，一步步走進了大海，再也沒有回來。沒有遺體，沒有遺書。他就這樣成為了一個地方傳說，一個關於瘋子的故事。</p>
<p>然而，我相信事情的真相遠不止於此。我，作為一個偏愛神話、地圖與古老異端的蹩腳考據者，曾在一本偽托的《山海經補遺》中，找到過一幅奇異的地圖。那張圖畫的並非我們所熟知的南海，圖上標註着：「何留意之海」。那裡的洋流並非由信風或地轉偏向力驅動，而是由記憶、情感與哲思所引導。悲傷的記憶形成寒流，喜悅的往事化為暖流，而那些關於存在與虛無的沉思，則構成了深不可測的海溝。</p>
<p>或許，何留意成功了。他沒有死去，也沒有瘋癲。他完成了那樁形而上學的偉業，將一個有限的自我，置換成了一個無限的、流動的宇宙。如今，航行於那片海域的水手們，有時會在羅盤失靈的迷霧中，聽到風中傳來並非出自任何人之口的低語，或是在某個浪峰之巔，看到一張由億萬水珠構成的、一閃而逝的悲憫面容。他們不知道，他們正航行在一個人的意識里。他們所謂的「海」，不過是旁人對何留意廣闊而又孤獨的靈魂的稱呼。</p>
]]></content>
        <author>
            <name>657</name>
            <uri>https://657.life</uri>
        </author>
        <published>2025-08-12T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[蒙斯特咖啡]]></title>
        <id>https://657.life/zh-tw/posts/mungstern-coffee/</id>
        <link href="https://657.life/zh-tw/posts/mungstern-coffee/"/>
        <updated>2024-04-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[蒙斯特（Mungstern）最著名的，是當地的咖啡。與尋常咖啡不同，這種咖啡的原料並不是咖啡豆，蒙斯特人用只在當地生長的帕帕草種子來製作蒙斯特咖啡。]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>[!NOTE]
這是一篇練筆文章，內容皆為編造。</p>
</blockquote>
<p>蒙斯特（Mungstern）最著名的，是當地的咖啡。與尋常咖啡不同，這種咖啡的原料並不是咖啡豆，蒙斯特人用只在當地生長的帕帕草種子來製作蒙斯特咖啡。</p>
<p>帕帕草與路邊雜草無異，但它的種子比較特別，帕帕草的種子是藍色的，形狀與瓜子相近。帕帕草不開花，種子從莖葉頂端長出。三月的時候，帕帕草發芽；五月的時候，葉子頂端開始出現藍色小點；再過一個月，種子成熟，帕帕草細長而柔軟的莖葉會因為無法承受種子的重量而彎曲，種子因此能接觸地面；最後在八月，種子會斷開與莖葉的連接，在下一次發芽的時間到來之前，長出根莖深入土壤，汲取發芽所需的養分。</p>
<p>蒙斯特人的祖先發現，剛與莖葉斷開的種子有微香，初聞像檸檬，細聞像桂花，聞久了卻又變成松木的味道。敢於嘗試的蒙斯特人將種子曬乾、磨碎後用熱水浸泡，這樣浸泡出的液體也是藍色的，比天空深邃，比海洋明朗。直接浸泡得到的帕帕草種子汁液聞起來有青草味，味道略苦，苦中夾雜著一絲酸澀，但入口後回甜。這種奇妙的味覺體驗讓帕帕汁（最開始是這麼稱呼的）在蒙斯特族開始變得流行。</p>
<p>一名蒙斯特人突發奇想，如果烤一下種子再磨碎會怎麼樣？於是他這麼做了，味道變得大有不同——帕帕汁的口感變得更加綿軟，味道往苦的方向更深了一層，酸澀感完全消失，最後的回甜變得厚重，像巧克力化在嘴裡的最後一刻。帕帕汁的顏色也變了，烤過之後的種子浸泡出來的汁液是淺棕色的，有點像拿鐵。</p>
<p>上個世紀 60 年代，蒙斯特地區被一支德國探險隊發現，他們回國後將探險日誌公開，蒙斯特人的存在第一次被世人所知。在探險日誌中提到的「口感奇妙的飲品」引起了當時著名咖啡師埃德溫（Edwin）先生的好奇。探險隊歸來的次月，埃德溫先生動身前往蒙斯特族所在地；一個月後，他帶著約 15 磅的帕帕草種子回來，並舉辦了一次以帕帕汁為主題的品鑑會，在會上他將其命名為蒙斯特咖啡，雖然這並不是由咖啡豆製成，但其外觀與味道乃至製作方式都與咖啡相近，且具有比咖啡更強勁的提神效果以及獨特的風味，所以將帕帕汁稱呼為咖啡也沒什麼不對（其實主要還是因為埃德溫對咖啡的狂熱）。</p>
<p>至此，蒙斯特咖啡正式出現在人們面前。</p>
]]></content>
        <author>
            <name>657</name>
            <uri>https://657.life</uri>
        </author>
        <published>2024-04-23T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[如何在 Flutter 中使用 QuickJS]]></title>
        <id>https://657.life/zh-tw/posts/how-to-use-quick-js-in-flutter/</id>
        <link href="https://657.life/zh-tw/posts/how-to-use-quick-js-in-flutter/"/>
        <updated>2023-11-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這篇文章介紹如何在 Flutter 中嵌入 QuickJS，包括如何編 Windows 和 Android 的運行庫以及調用 QuickJs 運行 JavaScript 代碼。]]></summary>
        <content type="html"><![CDATA[<h2>1. QuickJS 介紹</h2>
<p>以下內容來自 <a href="https://bellard.org/quickjs/">QuickJS 官方網站</a>。</p>
<p>QuickJS 是一個小型且可嵌入的JavaScript引擎。它支持 ES2020 規範，包括模塊、異步生成器、代理和 BigInt。它還可選擇性地支持數學擴展，如大十進製浮點數（BigDecimal）、大二進製浮點數（BigFloat）和運算符重載。</p>
<p>主要特點：</p>
<ul>
<li>小巧且易於嵌入：隻需幾個 C 文件，沒有外部依賴，對於一個簡單的"Hello World"程序，x86 代碼僅佔 210 KiB；</li>
<li>快速的解釋器，啟動時間非常短：在桌麵 PC 的單核心上，可以在大約 100 秒內運行 ECMAScript 測試套件的 75000 個測試。運行時實例的完整生命週期不到300微秒；</li>
<li>幾乎完整支持 ES2020，包括模塊、異步生成器和完整的附錄B支持（用於遺留 Web 兼容性）；</li>
<li>當選擇 ES2020 功能時，通過了接近 100％ 的 ECMAScript 測試套件測試。測試概要可在 Test262 報告中找到；</li>
<li>可以將 JavaScript 源代碼編譯爲可執行文件，無需外部依賴；</li>
<li>使用引用計數進行垃圾回收（以減少內存使用並具有確定性行爲），並帶有循環刪除功能；</li>
<li>數學擴展：BigDecimal、BigFloat、運算符重載、bigint 模式、math 模式；</li>
<li>具有基本的C庫包裝的小型內置標準庫；</li>
<li>帶有 JavaScript 實現的命令行解釋器，帶有上下文着色功能。</li>
</ul>
<h2>2. 了解 Dart 如何與 C 交互</h2>
<p>Flutter 應用使用 Dart 開髮，與 C 庫交互，就得使用 <code>dart:ffi</code> 庫。</p>
<p><code>dart:ffi</code> 是專門用來與原生 C APIs 進行交互的庫，<strong>FFI</strong> 代表 <a href="https://en.wikipedia.org/wiki/Foreign_function_interface">foreign function interface</a>，即外部函數接口。該庫的詳細使用方式可以參閱 <a href="https://dart.dev/guides/libraries/c-interop">官方文檔</a>。</p>
<h2>3. 爲不同平颱編譯 QuickJS</h2>
<p>Flutter 是跨平颱的 UI 框架，要在不同平颱使用 QuickJS，就需要爲不同平颱編譯 QuickJS 的動態庫。在 Windows 上，需要編譯出 <code>.dll</code> 文件；在 Linux 和 Android 上，需要編譯出 <code>.so</code> 文件。</p>
<p>編譯不同平颱的動態庫是在 Flutter 中使用 QuickJS 的前期準備，這裡主要介紹 Windows 和 Android 平颱的編譯步驟，編譯 Linux 平颱的動態庫較簡單所以省略。</p>
<h3>3.1 爲 Windows 平颱編譯 QuickJS 動態庫</h3>
<p>安裝 <a href="https://www.msys2.org/">MYSYS2</a>，編譯 QuickJS 需要使用 MYSYS2 中的 MINGW。</p>
<p>安裝完成後，運行 MYSYS2 中的 MINGW64（32 位運行 MINGW32），執行下麵的命令安裝編譯所需工具鏈：</p>
<ul>
<li>如果想要編譯 64 位的 QuickJS，則安裝 <code>x86_64-toolchain</code>：</li>
</ul>
<pre><code>pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-make mingw-w64-x86_64-dlfcn
echo "#! /bin/sh" &gt; /mingw64/bin/make
echo "\"mingw32-make\" \"\$@\"" &gt;&gt; /mingw64/bin/make
</code></pre>
<ul>
<li>如果想要編譯 32 位的 QuickJS，則安裝 <code>i686-toolchain</code>：</li>
</ul>
<pre><code>pacman -S mingw-w64-i686-gcc mingw-w64-i686-make mingw-w64-i686-dlfcn
echo "#! /bin/sh" &gt; /mingw32/bin/make
echo "\"mingw32-make\" \"\$@\"" &gt;&gt; /mingw32/bin/make
</code></pre>
<p>之後，繼續使用 MYSYS2 打開的 MINGW 終端 clone QuickJS 的倉庫：</p>
<pre><code>git clone https://github.com/bellard/quickjs.git
</code></pre>
<p>切換到 QuickJS 倉庫下，執行命令進行編譯：</p>
<pre><code>cd quickjs &amp;&amp; make
</code></pre>
<p>運行完 <code>make</code> 命令後可以得到 <code>libquickjs.a</code>，此時再運行下麵的命令即可得到 <code>libquickjs.dll</code>：</p>
<pre><code>gcc -shared -o libquickjs.dll -static -s -Wl,--whole-archive libquickjs.a -lm -Wl,--no-whole-archive
</code></pre>
<h3>3.2 爲 Android 平颱編譯 QuickJS 動態庫</h3>
<p>Android 中使用 C/C++ 庫需要編冩一個 <code>CMakeLists.txt</code>：</p>
<pre><code>cmake_minimum_required(VERSION 3.4.1)

project(quickjs LANGUAGES C)

include_directories(quickjs)

set(QUICK_JS_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../../quickjs)

set(
  SOURCE_DIR
  ${QUICK_JS_DIR}/cutils.c
  ${QUICK_JS_DIR}/libbf.c
  ${QUICK_JS_DIR}/libregexp.c
  ${QUICK_JS_DIR}/libunicode.c
  ${QUICK_JS_DIR}/quickjs.c
  ${QUICK_JS_DIR}/quickjs-libc.c
)

file(STRINGS "${QUICK_JS_DIR}/VERSION" CONFIG_VERSION)

add_definitions(-DCONFIG_VERSION="${CONFIG_VERSION}")
add_definitions(-DCONFIG_BIGNUM)
add_definitions(-D_GNU_SOURCE)
add_definitions(-DCONFIG_CC="gcc")
add_definitions(-DCONFIG_PREFIX="/usr/local")

add_library(
  ${PROJECT_NAME}
  SHARED
  ${SOURCE_DIR}
)

target_include_directories(${PROJECT_NAME} PUBLIC .)
</code></pre>
<p>這個 <code>CMakeLists.txt</code> 位於 Flutter 項目目錄的 <code>android/src/main/cpp</code> 文件夾下，將 QuickJS 倉庫放置於 Flutter 項目根目錄，當 Flutter 編譯 Android 平颱應用時，會自動生成一個 <code>libquickjs.so</code> 並打包進安裝包中。</p>
<p>關於 Android 平颱集成 C/C++ 的詳細介紹，請參閱<a href="https://developer.android.com/studio/projects/add-native-code?hl=zh-cn">官方文檔</a>。</p>
<h2>4. 使用 ffigen 生成函數綁定</h2>
<p>要調用 C/C++ 庫中的函數，首先要在 Dart 側進行“聲明”，例如在 C 中有這樣一個函數：</p>
<pre><code>int add(int a, int b) {
  return a + b;
}
</code></pre>
<p>那麼在 Dart 中，我們就要有一個對應的函數聲明，以供 Dart 代碼調用這個函數：</p>
<pre><code>import ''dart:ffi'' as ffi;

final nativAddFunc = dynamicLibrary.lookup&lt;ffi.NativeFunction&lt;ffi.Int Function(ffi.Int, ffi.Int)&gt;&gt;(''add'');
</code></pre>
<blockquote>
<p>這裡的 dynamicLibrary.lookup 方法會通過函數名、返回類型、參數類型去查找對應的 C 函數。</p>
</blockquote>
<p>在一個編程語言中對另一個編程語言的函數/變量進行聲明，專業術語稱之爲 <strong>語言綁定（language bindings）</strong>。</p>
<p>那麼問題來了，QuickJS 裡有那麼多函數，每一個都要在 Dart 側聲明一遍嗎？</p>
<p>答案是確實如此，雖然我們大多數時候用不到所有函數和變量，但我們也要編冩相當多的代碼來使用 QuickJS。</p>
<p>然而 Dart 的官方開髮人員非常給力，開髮了 <code>ffigen</code> 這個庫，<strong>該庫可以通過頭文件自動生成 bindings</strong>，大大提高了開髮效率！</p>
<p>要生成 QuickJS 的 bindings 隻需要：</p>
<p>i. 在 Flutter 項目中安裝 <code>ffigen</code>：</p>
<pre><code>flutter pub add ffigen
</code></pre>
<p>ii. 配置 <code>pubspecs.yaml</code>：</p>
<pre><code>...
# 增加下麵的配置
ffigen:
  name: QuickJSBindings
  description: generate bindings for quick js
  output: lib/bindings.dart
  headers:
    entry-points:
      - quickjs/quickjs.h
      - quickjs/quickjs-libc.h
</code></pre>
<p>iii. 運行 <code>dart run ffigen</code>。</p>
<p>僅需三步，即可生成完整的 bindings（生成前記得將 QuickJS 的倉庫放置於項目根目錄）。</p>
<h2>5. 使用 QuickJS</h2>
<p>首先，打開 libquickjs 動態庫：</p>
<pre><code>final _lib = DynamicLibrary.open(libquickjs);
final _ = QuickJSBindings(_lib);
</code></pre>
<p>然後，創建 JSContext 和 JSRuntime：</p>
<pre><code>final _runtime = _.JS_NewRuntime();
final _context = _.JS_NewContext(_runtime);
</code></pre>
<p>最後，調用 JS_Eval 方法執行 JS 代碼：</p>
<pre><code>const flag = JS_EVAL_FLAG_STRICT;
final input = code.toNativeUtf8().cast&lt;Char&gt;();
final name = filename.toNativeUtf8().cast&lt;Char&gt;();
final inputLen = _getPtrCharLen(input);
final jsValue = _.JS_Eval(_context, input, inputLen, name, flag);
calloc.free(input);
calloc.free(name);
final result = _js2Dart(_context, jsValue);
_jsStdLoop(_context);
_jsFreeValue(_context, jsValue);
if (result is Exception) {
  throw ret;
}
</code></pre>
<p>以上便是使用 QuickJS 的方法，當然，還有一些細節問題需要處理，例如如何處理 Promise 類型的返回值，如何創建事件循環等等。但這篇文章主要介紹如何接入 QuickJS，所以在此不再詳細展開。</p>
<h2>參考鏈接</h2>
<ul>
<li><a href="https://github.com/abner/flutter_js">A Javascript engine to use with flutter. It uses quickjs on Android and JavascriptCore on IOS</a></li>
<li><a href="https://github.com/ekibun/flutter_qjs">A quickjs engine for flutter.</a></li>
<li><a href="https://github.com/mengmo/QuickJS-Windows-Build">Build QuickJS on Windows</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/623863082">如何在Windows編譯和使用QuickJS</a></li>
</ul>
]]></content>
        <author>
            <name>657</name>
            <uri>https://657.life</uri>
        </author>
        <published>2023-11-02T00:00:00.000Z</published>
    </entry>
</feed>