xmake 远程包管理入门

缘起

最近在使用 xmake 管理几个新开的 C/C++ 项目, 有一些自建包需求, 但是实话说官方文档里的描述实在太过简略, 或许因知识之诅咒, 有不少重要的东西没有明示, 还有一些最佳实践也只能读官方打包好的东西才能发现. 但是我觉得 xmake 确实是一个很棒的 C/C++ 项目管理工具, 因此在我花了很久大致明白如何使用 xmake 的包管理后, 遂有此文.

但本文也只是盲人摸象, 作者也可能受到知识诅咒, 如有错误和过于简略之处, 请您指正.

内容块儿

  • 说明 xmake 包管理流程
  • 提供从零开始的 xmake 远程包编纂流程
  • 演示将 apache-arrow 包装为 xmake 远程包

概念澄清

此部分可以先略读, 等到实战时用到相关概念时再回来详看

项目编译方案和项目元信息描述方案

最初人们直接使用编译器命令行直接编译, 如g++ test.cpp -o test, 后来有了makefile方案, 可以稍微简单地使用make命令编译, 后来make表达力不足又有了cmakeautomake. 这些东西都是只用来指导如何编译的, 统称做项目编译方案.

但是关于如何获取这些包, 可以通过手动下载/pacman/apt等等, 手动下载太麻烦, 系统自带的包管理器不能在各个平台统一, 因此有了vcpkg/conan/conda/clib这样的统一的项目元信息描述方案, 它们至少描述了项目版本和版本对应的源代码地址.

xmake 是一个使用 lua 语法的项目编译方案, xmake 官方所做的 xrepo 是一个用 xmake 式语法的项目元信息描述方案, 即通常所言的包管理方案.

但是很怪异的是, xrepo 的包相关文档放在了 xmake 文档里, 以至于下面两个概念难以厘清.

远程包和本地包

xmake 中包含两种看起来同级的包

  • 远程包
  • 本地包

在官方文档里看起来项目可以打包成本地包, 也能打包成远程包, 远程包也能本地使用, 但是远程包/本地包并不能算是同级的.

在 xmake 语境下, 本地包是总是被一个正常的 xmake 项目生成的元信息描述方案, 其内包含:

  • xmake.lua 中包含一个package(name)
  • 包含引用时需要的头文件
  • 包含编译好的 lib 文件

按官方文档, 其可能的一个文件结构如下

1
2
3
4
5
6
7
8
9
10
11
$ tree build/packages/f/foo/
build/packages/f/foo/
├── macosx
│ └── x86_64
│ └── release
│ ├── include
│ │ └── foo.h
│ └── lib
│ └── libfoo.a
└── xmake.lua

本地包总是预编译过用 xmake 式语法描述的某个东西.

但在 xmake 文档语境下, 远程包并不特指使用 xmake 语法描述的某种东西.

远程包只是关于如何获得某个包的描述, 其描述方案可以是 xmake 式的, 也可以是xrepo/homebrew/vcpkg/conan/conda/pacman/clib等等等等, xrepo 是 xmake 官方出的一个万包之包式的包描述方案, 在安装 xmake 时已经自带. 远程包不包含源代码, 只包含如何获得源代码. xmake 式的远程包通常只包含一个xmake.lua文件.


综合来讲, 其关系和工作流程是这样的:

xmake 可以使用很多包管理器的包描述来辅助某个 xmake 项目的编译. 在谈到用 xrepo 安装一个包时, 是指如下过程:

  1. 在包索引中获取某个包的描述
  2. 按照元信息描述方案(xrepo/homebrew/vcpkg/conan/conda/pacman/clib), 解析出元信息
  3. 根据元信息下载其源代码
  4. 根据元信息里的编译方案(xmake/cmake/makefile/autoconf)将源代码编译成 lib 文件

第一步中有一些包描述是用 xmake 式语法写出的, 即是 xmake 远程包, 也可能是 vcpkg 式描述的. 第四步中的编译流程可能明面上是 xmake 的, 实际上是 xmake 调用其他编译方案的, 也可能是纯 xmake 的或者其他编译方案的.

例如

  1. zlib 是一个古典的项目, 官方钦定编译流程是./configure; make; make install, 写得又臭又长, 有四百余行. 其在 xmake 远程包里对应的安装是这样的, 下载官方源代码, 把一个三十余行的 xmake.lua 文件写入源代码根目录, 将其视为一个普通的 xmake 项目预编译 lib 文件. 这个流程我们简称为xrepo-xmake系.
  2. 7z 是一个古典的项目, 官方钦定的官方流程是make, 在 xmake.lua 里关于此包的安装流程为, 直接调用make -j -f makefile.gcc, 可视为xrepo-make系.
  3. autoconf 是一个可以使用 autoconf 编译的软件, 因此在 xmake 远程包里安装一步里, 只简单写了import("package.tools.autoconf").install(package), 使用 autoconf 安装, 这个流程我们简称为xrepo-autoconf系.
  4. apache-arrow 是一个最近几年出现的内存分析开发平台, 使用的编译方案为 cmake, 这个包在 xrepo 中还不存在, 不过可以使用 xrepo 调用 vcpkg 来安装, 因此这个流程可以称为vcpkg-cmake系.

从零开始的项目实战

创建和编译可执行文件

标准起手式 xmake create great-project 来创建一个普通的 xmake 项目, 运行 xmake 将会自动检测工具链->下载依赖包->编译, 我分别在 Windows 和 WSL 中运行 xmake. 相关命令如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ xmake create great-project
$ cd great-project
$ xmake
checking for platform ... linux
checking for architecture ... x86_64
[ 25%]: ccache compiling.release src/main.cpp
[ 50%]: linking.release great-project
[100%]: build ok!
---
checking for Microsoft Visual Studio (x64) version ... 2019
[ 25%]: compiling.release src\main.cpp
[ 50%]: linking.release great-project.exe
[100%]: build ok!
$ tree .
.
├── build
│ ├── linux
│ │ └── x86_64
│ │ └── release
│ │ └── great-project
│ └── windows
│ └── x64
│ └── release
│ └── great-project.exe
├── src
│ └── main.cpp
└── xmake.lua

8 directories, 4 files
$ ./build/linux/x86_64/release/great-project
hello world!
$ ./build/windows/x64/release/great-project.exe
hello world!

以下默认相关操作都在双系统下进行.

修改为库项目

对 xmake.lua 作如下修改

1
2
3
4
5
6
7
8
9
10
@@ -1,8 +1,9 @@
add_rules("mode.debug", "mode.release")

target("great-project")
- set_kind("binary")
- add_files("src/*.cpp")
+ set_kind("static")
+ add_headerfiles("src/great-project.h")
+ add_files("src/great-project.cpp")

并创建 great-project.h, great-project.cpp, 内容为:

1
2
3
4
5
6
7
8
9
10
11
// great-project.h
double solve(int x, int y);

// great-project.cpp
#include "great-project.h"


double solve(int x, int y) {
return 1.0 * y / x;
}

之后有

1
2
3
4
5
6
7
8
9
10
$ xmake
[ 25%]: ccache compiling.release src/great-project.cpp
[ 50%]: archiving.release libgreat-project.a
[100%]: build ok!

$ nm -C build/linux/x86_64/release/libgreat-project.a

great-project.cpp.o:
0000000000000000 T solve(int, int)

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
> xmake
[ 25%]: compiling.release src\great-project.cpp
[ 50%]: archiving.release great-project.lib
[100%]: build ok!
> dumpbin /SYMBOLS build\windows\x64\release\great-project.lib
Microsoft (R) COFF/PE Dumper Version 14.29.30038.1
Copyright (C) Microsoft Corporation. All rights reserved.


Dump of file build\windows\x64\release\great-project.lib

File Type: LIBRARY

COFF SYMBOL TABLE
000 01057556 ABS notype Static | @comp.id
001 80010190 ABS notype Static | @feat.00
002 00000001 ABS notype Static | @vol.md
003 00000000 SECT1 notype Static | .drectve
Section length 2F, #relocs 0, #linenums 0, checksum 0
005 00000000 SECT2 notype Static | .debug$S
Section length D4, #relocs 0, #linenums 0, checksum 0
007 00000000 SECT3 notype Static | .text$mn
Section length 15, #relocs 0, #linenums 0, checksum A8C8E0
009 00000000 SECT3 notype () External | ?solve@@YANHH@Z (double __cdecl solve(int,int))
00A 00000000 UNDEF notype External | _fltused
00B 00000000 SECT4 notype Static | .chks64
Section length 20, #relocs 0, #linenums 0, checksum 0

String Table Size = 0x14 bytes

Summary

20 .chks64
D4 .debug$S
2F .drectve
15 .text$mn

都可以观察到 solve(int,int)

使用土法导入库

另在库同级之处创建一个项目 great-loader, 并书写如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$ tail -n 100 src/main.cpp xmake.lua
==> src/main.cpp <==
#include <iostream>
#include <great-project.h>


using namespace std;

int main(int argc, char** argv)
{
cout << "solve(2, 42) = " << solve(2, 42) << endl;
return 0;
}

==> xmake.lua <==
add_rules("mode.debug", "mode.release")

target("great-loader")
set_kind("binary")
add_includedirs("../great-project/src/")
if is_os("windows") then
add_linkdirs("../great-project/build/windows/x64/release/")
elseif is_os("linux") then
add_linkdirs("../great-project/build/linux/x86_64/release/")
end
add_links("great-project")

add_files("src/*.cpp")

土法土在手动add_linkdirs, 然后手动add_links.
之后使用xmake; xmake run 已经可以看到正确结果了.

此时有一个小技巧, 智能感知工具不会识别 xmake.lua, 但是能识别 CMakeLists, 因此可以运行xmake project -k cmake来生成 cmake 文件帮助补全.

打包为本地库

xmake package即可, 但是个人感觉没必要使用本地库, 因此略过.

使用远程包

按照官方文档可以直接运行打包命令

1
2
$ xmake package -f remote

之后得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- build/packages/g/great-project/xmake.lua
package("great-project")
set_description("The great-project package")

add_urls("https://github.com/myrepo/foo.git")
add_versions("1.0", "<shasum256 or gitcommit>")

on_install(function (package)
local configs = {}
if package:config("shared") then
configs.kind = "shared"
end
import("package.tools.xmake").install(package, configs)
end)

on_test(function (package)
-- TODO check includes and interfaces
-- assert(package:has_cfuncs("foo", {includes = "foo.h"})
end)


原则上, 使用远程包需要两个基础信息, 元信息描述方案存在哪? 源代码和编译方案存在哪? 一般来讲, 元信息在xmake 官方 Github 仓库, 源代码和编译方案放在作者的仓库或项目主页.

现在就是要做一下填空题了, 首先要填add_urls, 这里面可以填 git 链接, 也可以填某些下载链接.

在测试时, 元信息(上面要填空的一大段)也可以放到项目的xmake.lua文件中, 毕竟放到远端修改也挺麻烦的, 另外, 在 Linux 下, git 仓库也可以使用 file:// 从本地克隆, 因此两个东西都可以放到本地去.

在 great-project 中运行

1
2
3
4
git init
git add .
git commit -m "init"

给 add_urls 填入新增的 git 仓库的地址, 由于 file 协议里无法出现.., 因此将相对路径转为绝对路径, add_urls("../great-project")应为add_urls("file:///mnt/c/Users/myuan/project/xmake-package-example/great-project"). (Windows 下可以老老实实建一个 Github 仓库然后把 url 放进去)

将其放入 great-loader 项目中的 xmake.lua 中去, 得到如下 xmake.lua 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@@ -1,14 +1,28 @@
add_rules("mode.debug", "mode.release")

+package("great-project")
+ set_description("The great-project package")
+
+ add_urls("file:///mnt/c/Users/myuan/project/xmake-package-example/great-project")
+
+ on_install(function (package)
+ local configs = {}
+ if package:config("shared") then
+ configs.kind = "shared"
+ end
+ import("package.tools.xmake").install(package, configs)
+ end)
+
+ on_test(function (package)
+ -- TODO check includes and interfaces
+ -- assert(package:has_cfuncs("foo", {includes = "foo.h"})
+ end)
+package_end()
+
+add_requires("great-project")
+

target("great-loader")
set_kind("binary")
- add_includedirs("../great-project/src/")
- if is_os("windows") then
- add_linkdirs("../great-project/build/windows/x64/release/")
- elseif is_os("linux") then
- add_linkdirs("../great-project/build/linux/x86_64/release/")
- end
- add_links("great-project")
-
+ add_packages("great-project")
add_files("src/*.cpp")

target 下面清爽了好多, 重新运行

1
2
3
4
5
6
7
$ xmake 
[ 25%]: ccache compiling.release src/main.cpp
[ 50%]: linking.release great-loader
[100%]: build ok!
$ xmake run
solve(2, 42) = 21

简单打包 apache-arrow

首先按照官方文档的 cmake 编译流程测试, 完全没有任何额外需要的配置. 复制一份 great-loader, 起名为 arrow-test, C++ 对应的 CMakeLists 在 cpp 目录下, 因此 xmake.lua 文件修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@@ -1,17 +1,16 @@
add_rules("mode.debug", "mode.release")

+package("apache-arrow")
+ set_description("Apache arrow")
-package("great-project")
- set_description("The great-project package")

+ add_urls("https://github.com/apache/arrow.git")
- add_urls("file:///mnt/c/Users/myuan/project/xmake-package-example/great-project")

on_install(function (package)
local configs = {}
if package:config("shared") then
configs.kind = "shared"
end
+ os.cd("cpp")
+ import("package.tools.cmake").install(package, configs)
- import("package.tools.xmake").install(package, configs)
end)

on_test(function (package)
@@ -20,10 +19,10 @@
end)
package_end()

+add_requires("apache-arrow")
-add_requires("great-project")


target("great-loader")
set_kind("binary")
+ add_packages("apache-arrow")
- add_packages("great-project")
add_files("src/*.cpp")

至此已经可以简单使用该包了, 从 arrow 的官方示例里找一个arrow/cpp/examples/arrow/row_wise_conversion_example.cc, 编译可正常通过, 也可正常运行.

理论上现在只要把package("apache-arrow")package_end()之间的东西提交到 xmake-repo 仓库里去, 别人就可以直接使用此包了.

更多选择的 apache-arrow

在示例中, 一旦尝试更多功能(比如 parquet)就会发现找不到头文件了, 查看 arrow 文档可知, 官方提供了非常多的编译选项来打开或关闭小功能.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
-DARROW_COMPUTE=ON: Computational kernel functions and other support

-DARROW_CSV=ON: CSV reader module

-DARROW_CUDA=ON: CUDA integration for GPU development. Depends on NVIDIA CUDA toolkit. The CUDA toolchain used to build the library can be customized by using the $CUDA_HOME environment variable.

-DARROW_DATASET=ON: Dataset API, implies the Filesystem API

-DARROW_FILESYSTEM=ON: Filesystem API for accessing local and remote filesystems

-DARROW_FLIGHT=ON: Arrow Flight RPC system, which depends at least on gRPC

-DARROW_GANDIVA=ON: Gandiva expression compiler, depends on LLVM, Protocol Buffers, and re2

-DARROW_GANDIVA_JAVA=ON: Gandiva JNI bindings for Java

-DARROW_HDFS=ON: Arrow integration with libhdfs for accessing the Hadoop Filesystem

-DARROW_HIVESERVER2=ON: Client library for HiveServer2 database protocol

-DARROW_JSON=ON: JSON reader module

-DARROW_ORC=ON: Arrow integration with Apache ORC

-DARROW_PARQUET=ON: Apache Parquet libraries and Arrow integration

-DARROW_PLASMA=ON: Plasma Shared Memory Object Store

-DARROW_PLASMA_JAVA_CLIENT=ON: Build Java client for Plasma

-DARROW_PYTHON=ON: Arrow Python C++ integration library (required for building pyarrow). This library must be built against the same Python version for which you are building pyarrow. NumPy must also be installed. Enabling this option also enables ARROW_COMPUTE, ARROW_CSV, ARROW_DATASET, ARROW_FILESYSTEM, ARROW_HDFS, and ARROW_JSON.

-DARROW_S3=ON: Support for Amazon S3-compatible filesystems

-DARROW_WITH_BZ2=ON: Build support for BZ2 compression

-DARROW_WITH_ZLIB=ON: Build support for zlib (gzip) compression

-DARROW_WITH_LZ4=ON: Build support for lz4 compression

-DARROW_WITH_SNAPPY=ON: Build support for Snappy compression

-DARROW_WITH_ZSTD=ON: Build support for ZSTD compression

-DARROW_WITH_BROTLI=ON: Build support for Brotli compression


要想给 CMake 传递参数, 应当首先配置 add_configs, 之后使用 on_install 中的 config. 一个从官方包里拔出来的使用方式如下:

1
2
3
4
5
6
7
on_install("macosx", "linux", "windows", function (package)
local configs = {"-DCMAKE_CXX_STANDARD=17"}
table.insert(configs, "-DCMAKE_BUILD_TYPE=" .. (package:debug() and "Debug" or "Release"))
table.insert(configs, "-DBUILD_SHARED_LIBS=" .. (package:config("shared") and "ON" or "OFF"))
import("package.tools.cmake").install(package, configs, {buildir = os.tmpfile() .. ".dir"})
end)

至于在添加包时添加要求, 可以在此处添加

1
2
add_requires("fmt", {configs = {cxflags = "-fPIC"}})

之后可以通过

1
2
package:config(config_name)

来获取配置, 因此可得如下更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@@ -4,12 +4,18 @@
set_description("Apache arrow")

add_urls("https://github.com/apache/arrow.git")
+ add_configs("components", {description = "开关可选组件, 组件用逗号分隔, 可选内容参考https://arrow.apache.org/docs/developers/cpp/building.html#optional-components", default = "csv,parquet"})

on_install(function (package)
local configs = {}
+
if package:config("shared") then
configs.kind = "shared"
end
+ for name in string.gmatch(package:config("components"), "[%w|_]+") do
+ table.insert(configs, string.format("-DARROW_%s=ON", string.upper(name)))
+ end
+
os.cd("cpp")
import("package.tools.cmake").install(package, configs)
end)
@@ -20,7 +26,7 @@
end)
package_end()

-add_requires("apache-arrow")
+add_requires("apache-arrow", {configs = {components = "parquet,csv,compute"}})


target("great-loader")

至此已经可以指定各个组件了, 按照惯例还应该在 on_test 中添加一个测试用例, 保证安装成功, 给编译添加 debug release 选项等等, 这些已经与本文核心关联不大了, 忽略. 当前的 xmake.lua 内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
add_rules("mode.debug", "mode.release")

package("apache-arrow")
set_description("Apache arrow")

add_urls("https://github.com/apache/arrow.git")
add_configs("components", {description = "开关可选组件, 组件用逗号分隔, 可选内容参考https://arrow.apache.org/docs/developers/cpp/building.html#optional-components", default = "csv,parquet"})

on_install(function (package)
local configs = {}

if package:config("shared") then
configs.kind = "shared"
end
for name in string.gmatch(package:config("components"), "[%w|_]+") do
table.insert(configs, string.format("-DARROW_%s=ON", string.upper(name)))
end

os.cd("cpp")
import("package.tools.cmake").install(package, configs)
end)

on_test(function (package)
-- TODO check includes and interfaces
-- assert(package:has_cfuncs("foo", {includes = "foo.h"})
end)
package_end()

add_requires("apache-arrow", {configs = {components = "parquet,csv,compute"}})


target("great-loader")
set_kind("binary")
add_packages("apache-arrow")
add_files("src/*.cpp")


文件结构为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
$ tree .
.
├── arrow-test
│ ├── build
│ │ └── linux
│ │ └── x86_64
│ │ └── release
│ │ └── great-loader
│ ├── CMakeLists.txt
│ ├── src
│ │ └── main.cpp
│ └── xmake.lua
├── great-loader
│ ├── build
│ │ └── linux
│ │ └── x86_64
│ │ └── release
│ │ └── great-loader
│ ├── CMakeLists.txt
│ ├── source.tmp
│ │ └── great-project
│ │ ├── src
│ │ │ ├── great-project.cpp
│ │ │ └── great-project.h
│ │ ├── xmake copy.lua
│ │ └── xmake.lua
│ ├── src
│ │ └── main.cpp
│ └── xmake.lua
├── great-project
| ├── .git
│ ├── build
│ │ └── packages
│ │ └── g
│ │ └── great-project
│ │ └── xmake.lua
│ ├── src
│ │ ├── great-project.cpp
│ │ └── great-project.h
│ └── xmake.lua
└── readme.md

27 directories, 23 files

这里不能正常展示 markdown 的 diff 代码块, 可查看正常 diff 在 github

GitHub - myuanz/getting-started-with-xmake-package
Contribute to myuanz/getting-started-with-xmake-package development by creating an account on GitHub.

论多元宇宙中许愿机的种类与限制

摘自知乎 假面
原链接 https://www.zhihu.com/question/28031471/answer/112417564
此处仅作备份

实际上,无论是什么形式的许愿机,都必须遵循能量守恒定律。

假设你许愿"实现一百个愿望",那么可能性会有以下几种

1:许愿机会拒绝你的许愿(能量不足)

2:会实现你的愿望,但实际上当你许下超出范围的愿望时,如毁灭世界时,会被拒绝。即,当你只能许一个愿时,你可以毁灭世界,但变成了一百个,就不行了。(分割能量配额)

3:同样实现你的愿望,但并不会限制你之后的许愿,这是因为许愿机的能量过于强大,人类所能想象的愿望,它都可以无限制的实现,这种许愿机暂时只存在于想象中。(超维度)

4:许愿机会给予你一百份名为"愿望"的物品,或一张写着"实现一百个愿望"的纸条(曲解原意)

5:许愿机会选择包括但不限于幻觉,入梦,记忆修改等手段,让你认为你已经实现了愿望。(恶意实现)

无论是怎样形式的许愿机,其结果都是修改现有世界的现象,这无疑是需要能量的,也无法逃脱等价交换的原则。

我们可以看一下,在各种各样的世界里,出现的各种许愿机。

————————————

1:七色花

首先,是题主提到的,苏联童话中的《七色花》


这是一个数百字的童话,名为珍妮的迷路女孩得到了一朵七色花,并用其许下——回家、修复花瓶、去北极、回家、好多玩具、让小男孩可以走路七个愿望。

这七个愿望所实现的结果,在异界观念里是微不足道的,不过是幻影移形,修复如初,飞来咒,以及治愈术。

七色花作为童话类的许愿机,并不清楚其实现愿望的机制,但如果只是小女孩一类的愿望,莫说一百,一千一万也不过尔尔。

与其说这是许愿机,不如说是一种多功能的魔法卷轴,实际上无论是金钱,权力,美色,大部分都能通过法术来实现,如点石成金,摄魂取魄,魅惑人类等。

如果选择再要一朵七色花,那么可能它会召唤出另外一朵可能存在的七色花来,也可能将花瓣分割成七份,而效力下降。

2:神灯

然后,我们再来看阿拉伯故事集《天方夜谭》又名一千零一夜里的许愿机,也是地球历史上第一个明确的许愿机,阿拉丁神灯,虽然我不是很懂为什么阿拉丁是个中国人。


由于故事原文知者甚少,我们选择以家喻户晓的迪士尼动画《阿拉丁》里的神灯为例。

故事中阿拉丁许下了三个愿望,一是成为王子,二是解救自身,三为解放精灵。

反派的贾方则许愿成为国王与大法师,放逐阿拉丁,及成为精灵。

但精灵许愿有三个限制
1:不可杀人
2:不可使别人相爱
3:不可起死回生

从贾方得到精灵法力却被束缚,而精灵被解放后似乎也没有再施展奇迹,就能明白,神灯的精灵其实只是负责接受愿望的器灵,真正实现愿望的魔力来源于神灯。

在原故事中,除去神灯外,还存在另一个微型许愿机,它是一枚魔戒,其能力只能为阿拉丁带来一笔金钱,当阿拉丁向魔戒求助时,魔戒表示它的法力不足以与神灯对抗。可以看出来,许愿需要法力,而法力是存在量多少的差别的。

而神灯作为一个高级许愿机,尽管能带来荣华富贵和强大力量,但不能随意扭曲人心,也不能改变生死,其能力并不是无限的。

在迪士尼的作品《王国之心》中,帮助了阿拉丁的主人公得到了许愿的机会,但精灵却表示无能为力。

3:龙珠


龙珠是《龙珠》世界中的许愿机,由娜美克星人制造,集齐七颗龙珠即可召唤神龙实现愿望。

龙珠的制作是基于星球本身能量以及的"神"担当的实力,初期的地球神龙只能实现一个愿望,并有一年的冷却期,而新神上任后,就可以实现三个愿望,根据愿望范围智能调节冷却时间,可以起死回生,但一个愿望只能使一人复活。

那美克星的神龙由于所在星球远比地球广大,实现愿望的能力更强,一个愿望能复活复数的人,冷却期也更短。

尽管龙珠神通广大,甚至可以重生被毁灭的星球,但其能力并不是无限的,也需要冷却的时间。

而且神龙虽然万能,也无法许愿击败弗利萨,毁灭沙鲁,消灭布欧,因为这些怪物都比神龙的创造者要强太多了。

4::基拉祈

属于《精灵宝可梦》位面
祈愿神奇宝贝。每千年苏醒一次,只会苏醒七天,使用不管是什么愿望都能实现的力量,又称为七夕的许愿星。

千年只苏醒七天,可能是因为积蓄许愿力量需要极长的时间。

而在剧场版《七夜的許願星》中,反派的前火岩队队员试图通过基拉祈召唤固拉多以征服世界,无独有偶,在特别篇中,水舰队首领水梧桐也试图通过基拉祈召唤盖欧卡,实现没有蛀牙。

但两者召唤出来的,都是只有传说宝可梦外形的力量团块,徒有其表的怪物。

看得出来,基拉祈作为许愿机,也不是无所不能的。

5:祈愿术(Wish)

DnD体系,龙与地下城及其众多衍生宇宙中的一类高阶法术,如有限许愿术,许愿术,奇迹术等。

祈願術是法師或者術士能夠施展的最強力的法術。只需要簡單地大聲說出你的願望,你就可以將現實變得更符合你的心意。但是就算是祈願術也有其極限。

你可以嘗試使用祈願術產生比上述效果更強力的效果,但是這麼做非常危險。(祈願術可能會完全按照字面理解你的意圖,其結果卻可能無法滿足你的要求或者只能部分滿足你的要求,具體由GM決定。)

这种法术的施法要求极高,实现机制复杂,甚至有可能需要巨大代价,施法者很难保证其结果是自己理想的,在相关的记载里,存在大量曲解愿意,甚至造成恶性后果的案例。

使用这种法术时,务必慎言慎行。

6:根源之涡(圣杯/两仪式)根源,属于Type-Moon,型月世界中的概念,位于世界外侧。

一切的「因」,各种现象起始之处。由于只要有「因」就能产生「果」,因此以存在来说,这即是「究极的知识」

圣杯,是一种大型的魔法仪式系统,首创于魔术界御三家,玛奇里,远坂,及爱因兹贝伦,仪式地点为日本的冬木市,目前已被解体,但其情报公开后,诞生了诸多的亚种。


通过积蓄六十年魔力,召唤并投入七位英灵的魂魄,最终得到超巨量的魔力,打开通往根源的道路,原本的目的是实现第三法的天之杯,在第三次仪式中,圣杯被英灵——此世之恶 Angra Mainyu 污染,成为了会恶意曲解人意,肆意歪曲地诠释愿望的可怕许愿机。

同样的,作为许愿机,圣杯需要超过半个世纪的时间从灵脉中汲取魔力,更需要七位英雄的灵魂作为素材,其目的不过是打开通往根源的道路,以得到实现一切愿望的可能性。

但它并不能实现超越人智的事物,只能通过许愿者所能想到的方式去实现愿望,在被污染后,更是会采用恶意的思考方式,若你希望世界和平,那结果便是人类灭绝。

圣杯实现愿望的机制尚未明确,尽管其目的是为了打开通往根源的道路,但是否借助根源来实现愿望则并未表明,因为为此而聚集的巨大魔力本身,就已经足以实现人类的大部分愿望了。

尽管根源身处世界外侧,看似是拥有无限力量的许愿机,但明显它是不可能做到“再造一个根源”这种事情的。

而两仪式,是退魔家族两仪家的次女,人为的双重人格者,一阴一阳为两仪,在两仪家的误打误撞下,拥有了名为【】的起源,其肉身拥有人格,和根源之涡相连,因此几乎可以做到任何事。

自称拥有可以改变世界,实现一切的力量,但并没有使用过。

已为人母。

7:丘比

最后我们来谈一谈《魔法少女小圆》宇宙中的丘比——孵化者[Incubator]。


丘比属于外星文明的一种,为阻止宇宙的熵增加,避免热寂的到来,它们开发出了将“感情”变换为“能量”的技术。

由于它们本身并不具有感情,因此选中感情丰富的人类种族进行试验。自人类史以来,丘比就一直活跃于文明的背面,与人类中感情波动最为剧烈与丰富的青春期少女签订契约,利用她们,摄取其心灵由“希望”变为“绝望”所产生的庞大能量。

丘比诱骗少女们签订契约的筹码,即为“愿望”本身。

少女们许下愿望,自身的灵魂化为宝石,她们得到战斗的魔力,许下的愿望会赋予她们特殊的能力。

但魔力的消耗与负面的感情都会导致灵魂的劣化与污染,年幼的少女们不得不面对残酷的现实与庞大的压力,而她们渴求的[希望],终将会转化为[绝望]。

在上述众多同类中,丘比是唯一以许愿作为手段,并不是为了实现愿望而存在的许愿机。

也是性质最为恶劣的。

但丘比本身并不作为愿望的实现者,也不作为能量的提供者,更无法影响愿望导致的结果。

它们只是利用该宇宙中存在的独特规则定律,得到让人许愿的能力,而愿望的实现与结果都是取决于许愿者的少女本身的“资质”。

这种资质的本质是什么并不明确,但根据结果来看,背负的因果也是资质的一种。

换句话说,丘比只负责“听取”和”传达“,愿望的实现与代价的收取,则交由宇宙本身。

因此,它们既无法阻止许愿,也无法得知许愿的结果与少女们得到的能力。

在该宇宙中,名为鹿目圆的少女许下了改变世界因果规律的伟大愿望,并得以实现,这是由于她的爱慕者,晓美焰为了她进行了多次的时间回溯,将多个平行世界的因果连接在鹿目圆身上,使她拥有了无与伦比的潜质,可以创造新世界的力量。

P ID 21548933

------------------------------
结语

在上述的多种许愿机中,或多或少都存在各种各种的限制与缺陷,并不是万能,也不是没有代价的,甚至由此引发了许多的悲剧,许愿机看似是破坏平衡的存在,但万事万物皆有其道理,所谓万能的许愿机,不过也是天秤的一端,只是天秤的另一头过于宽广,我们难以目视罢了。

愿望的实现是复杂的,哪怕是一个简单的愿望,也可能涉及到世界的改变,而世间的一切是守恒的,许愿机作为一种改变现有世界现象的奇迹,将不可能化为可能,将偶然化作必然,超越人智与人力的存在,其背后必定存在极度复杂的规律与法则,不当的使用,会导致莫大的代价与惨痛的结果,请务必谨记这一点。--------《论多元宇宙中许愿机的种类与限制》
贤者、炼金术师——梅莝·旧世沃 著

二零二零初夏随笔

今天温度不是很高, 我没开空调, 一直开着窗.

天此时已经快黑了, 我设置了太阳下山点自动电脑开启护眼模式, 空气稍稍有些热, 但还不到出汗的程度.
终于到了日落点, 屏幕开始昏暗, 开始泛黄, 我没有开屋子里的灯, 看起来淡黄色的两个屏幕是整个屋子最大的两个发光体, 柔和而温暖, 同时又仿佛太阳已经从天地消失, 世界也只有这两个发光体.
下一个瞬间, 风来了, 穿堂风自窗入, 于堂出, 清爽临身, 带动门吱呀吱呀, 而后万籁俱寂.

一段关于大括号的往事

今天看见了一个手写的大括号, 突然想起来了一段往事:

我中考成绩一般般, 高一刚入学的时候, 在 B 类班. 班主任姓练, 是年级主任, 有一天遗落了一张中考成绩排行榜在讲台上, 我大概看了一下这些名字.

到分文理后, 我到了 A 班. 有一次有道题, 周围的人都不太会, 唯独我的邻桌说有个很棒的思路, 他成绩一直是很靠后的, 而且非常腼腆又沉默, 我当时有些惊奇, 然后请教了他.

那次应该是下午的课, 下一节是体育, 他趁着课间在讲题, 说着说着人走差不多了, 到讲完的时候, 他突然有些伤感地让我听他说一会话, 下午让人醉醺醺的, 我点了头, 我与他并不怎么熟, 但是权当感谢人了.

他说他其实是当时中考成绩第五进来的, 学校里有人专门打电话过去的, 我当时刚好回想到, 他的名字在那个表里出现过, 我等弱鸡自然是要赞几句的, 之后他有些刹不住话茬了.

他说他当时很兴奋, 感觉未来大有可为, 他又自学了一些高中数学, 学完了四本, 还做了一些竞赛题, 做了一些高考题, 感觉很简单的样子, 金黄的未来仿佛在向他招手.

他说他曾经还练习了大括号的写法, 他当时专门写了好几个大括号给我看, 的确很好看, 像汉字草书, 形散却优美, 我的大括号也是形散, 但是十分之丑, 毫无定型. 我甚至觉得他还有教我写大括号的念头, 只是当时我想这有何所谓的, 难道大括号用得很多吗? 我夸了两句, 就停下来了.

他继续说, 他说他后来堕落下去了, 是因为一个什么原因, 可是原因我现在忘了, 他说他难以摆脱这个原因, 说到这里他又黯淡下去了. 我顺着他的话头说了几句礼节性的车轱辘话, 上课铃响后我们便跑到了操场.

他或许是不太适应新环境, 或许是其他原因, 成绩掉落了很多, 这让他抬不起头, 他无法在这个曾经让他获得欢欣的排序体系下继续获得正向情绪了.

我未加过他, 现在也已经忘了他的名字, 他的样子, 只是隐隐约约记得他挺瘦. 但是这段情节我记得很深, 起初是因为这件事的奇异, 后来是因为我意识到, 这是一个少年的心气, 只是已经被压制得难以显现了.

那么, 可能有不那么让人压抑的排序吗? 或者让排序差的人也能舒服地活着? 我不知道.

手把手教你识破杜甫诗中的阴谋心机

摘自

张佳玮

张佳玮 NBA篮球文学 话题的优秀回答者

杜甫有一首诗:

“两个黄鹂鸣翠柳,一行白鹭上青天。

窗含西岭千秋雪,门泊东吴万里船”。

看来是写春景,其实仔细分析,可以分析出许多心机来。


甲方:

时值安史之乱。唐玄宗跑路离开了长安,留下太子守灵武。太子自立为皇帝,就是后来的唐肃宗了。而唐玄宗当时在蜀中,一时还不知道这件事,所以唐朝一度两个皇帝并存。

杜甫当时人在蜀中。两个黄鹂,是嘲讽唐有二主,父子离心。

加上杜甫自己被唐肃宗欺负过,所以这里也可以说是寄托了杜甫对唐肃宗的不满。

一行白鹭上青天。

话说杜甫是公元760年到成都的,此前一年,史思明在河北大胜唐的九节度使合军攻击,一时势头无二。于是史思明志得意满,称了“应天皇帝”一飞冲天。这么看,杜甫是因为对唐肃宗不满,转而歌颂反贼史思明。一行白鹭上青天。

窗含西岭千秋雪。

当时唐朝反抗史思明,大家都在努力向上,为啥只有杜甫去看什么千秋雪?难道不是在暗示冰冻三尺非一日之寒,暗示唐朝长年积弊,翻身无望吗?

门泊东吴万里船。

话说,三国时费祎辞别诸葛亮去东吴,说“万里之行始于此桥”,这是东吴万里的典故。

而唐朝当时东南,正是永王李璘起兵未遂,连李白也遭到了连累。所以杜甫写什么门泊东吴万里船,明显是因为李白的缘故,对当时已被搞定的永王李璘怀有怜悯之心,有意勾连东南,割据一方。

总而言之,这诗可以分析出杜甫的种种不堪用心来:

腹诽唐朝、歌颂反贼史思明、同情反王李璘、妄图割据东南,真是用心险恶之极!


乙方:

时值安史之乱。安禄山和史思明谋反。

安禄山被儿子安庆绪杀了。史思明接着闹事,但随后也会被自己儿子史朝义接管。

所以这两个反贼,那都是螳螂捕蝉黄雀在后。

两个黄鹂鸣翠柳,是说他俩都叽叽喳喳,成不了气候。

既然反贼都成不了气候,那当然一行白鹭上青天,是在歌颂当时肃宗领导的李唐王朝绵绵不绝,早晚还要一飞冲天。

窗含西岭千秋雪,是说唐朝积累深厚,无论什么大风大浪,都无法轻易将之摧毁。

门泊东吴万里船,那是用费祎与诸葛亮的典故,暗示蜀地与东吴一家;坐拥南方,平了李璘之后,自然可以合力北上,还于旧都。

总而言之,这首诗综合一看,可以分析出杜甫的一片赤诚来:

嘲弄安史反贼,歌颂大唐天子,赞美大唐的深厚底蕴,鼓舞大唐南方人民,积极投入反安史、卫大唐的斗争中去。


那,上面甲乙两种截然不同的说法,该信哪一种呢?

我个人建议:

哪个都别信。

都他妈是我瞎编的。

列这两段,不过是想证明下面这件事:

只要无限扩大分析面,望文生义疯狂联想,一个春景诗,也可以罗织出花儿般的罪名来。

没有谁逃得过这么折腾。

甚至,只要善于联想、善编辞藻,一首诗可以解读出两种截然不同的倾向来。

但凡把这种咬文嚼字瞎联想的做派搁到唐朝,估计能把杜甫检举得,一首诗都流传不下来吧?

实际上,我还听过第三种说法呢:


丙方:

当时岑参到成都来访问杜甫。杜甫家里只有几个鸡蛋、一把韭菜和一块豆腐了。

怎么待客呢?

有了。

岑参进了杜甫草堂。杜甫先给他端来一盘绿油油的春韭,上搁两个蛋黄。“两个黄鹂鸣翠柳!”

岑参高兴坏了,正吃呢,杜甫又上一盘:青色盘子,一溜蛋白。“一行白鹭上青天!”

接着是一盘豆腐,“窗含西岭千秋雪!”

岑参吃饱喝足了,杜甫最后上了碗蛋花汤,汤上漂浮着些蛋壳:“门泊东吴万里船!”

岑参抚掌大笑,宾主尽欢而散。

有可能这么简单吗?

有可能的哟。


我谨以亲身经历保证:

身为作者的自己,经常是想不了那么多的。

许多联想,都是后来人自己附会上的。

不信,看下面这个图。

我觉得今天北京有点热,为什么评论这么欢乐?

我觉得今天北京有点热,为什么评论这么欢乐?

Chris

Chris中南红文化集团股份有限公司 娱乐营销事业部总经理

今天一位微博博主(ID:荣大一姐)发了个小感慨“我觉得今天北京有点热”,结果许多人纷纷“杠精”附体,质疑她说这话到底是什么意思。

(ps. 只有看懂了网友回复的人,才明白为什么“杠精”俩字打了引号。)

微博是这样的:

我觉得今天北京有点热(不是说热就一点好处也没有。没有给其他地方的热洗地的意思。不是故意忽略北京之前冷和以后还会冷的事实。没有暗示因为热就嫌弃北京。热有热的原因,冷有冷的缘由,认同各有各的合理之处。知道“热”是一种个人感受,但是今天38度。只是自己活动范围内有点热,不包含北京其他地方的感受。不包含家里开空调的感受。不是故意不呆在空调房内,但确实不得不外出。理解天气不可控。不是地域黑或者其他城市的粉。只是单纯表达所在地一公里内因为天气不可控且气温38度有不得不在外面呆着的理由而无法用空调降温所以感觉有点热)。

有网友质疑博主“造谣”:

“北京热不热,以新华社通稿为准”

”人民日报没有说热,一切请以官方通报为准,不传谣,不信谣“

”北京不热,全微博除了博主没有一个说北京热的,博主在造谣。“

“就是,个人随意发布热不热的消息,容易引起社会混淆,万一新华社说不热,这就算造谣了。”

“今天38度”—— 这话不对呀,今天从早到晚有24个小时,难道一直都是38度?另外,你说38度就38度?用哪个温度计量的?什么牌子、哪个厂家、有没有合格证生产许可证?有没有经过计量检定?“

”不传谣 不信谣 中国加油“

有网友质疑博主“双标”:

“华盛顿热你怎么不说,双标狗”

”你怎么不说一下美国纽约热不热?你说了北京热就是别有用心,纽约热你怎么不说?!”

”你这是给指控全球变暖递刀“

”万一被外国出版了怎么办!“

”我就问你一句!你是不是在给西方乳(辱)化(华)媒体递子弹!“

“为什么一点热就要说出来,你不知道会被其他国家作为攻击的证据吗?”

“非洲比我们更热,你怎么不说?为什么单单挑北京说热?你立场站哪里了?你的动机就是抹黑。”

“太长不看。但以前就发现你屁股歪,想必不是什么好话。”

“通篇不知所云,不知道发这种东西做什么。举报举报”

“同样的气温,要换成纽约他们就喊天气凉爽了。臭公知、恨国谠,成天传播负能量,除了抱怨你们还做过什么?你们下过一滴雨吗?尽特么添乱。”

有网友提醒博主要注意自己的身份:

”你作为公众人物,公开评论天气,不想想会在社会上造成多大影响吗?(逃生狗头)“

”地区的气象是一个国家的机密,建议发布信息之前和有关部门联系,得到允许后再决定发不发。你不要觉得麻烦,这对国家安全有好处。你也不要害怕,心里没鬼害怕什么“

”全民奔小康了,还会热?说明你不够努力,买不起空调罢了,不要把整个北京人民拖下水。请你自己撸起袖子加油干。“

“你这是主观感受,脱离客观现实,我身边的人都没有觉得热,为什么就你觉得,现在还是春天怎么可能热,你不喜欢北京,那你走啊。”

”知道是个人感受还说出来,你38从哪里得知的呢,你亲自去气象部门看了吗,你自己亲自量出来的吗?坐在家里写几个字就想代表北京儿人儿?“

”躲在家里说外面热,你去亲身体验过吗?都是听说吧,又是你的朋友说,你的同事说,你都没出去有什么资格说?“

有网友质疑博主的动机别有用心:

“你居然只说北京热,北京也有那么多天气舒适的日子你就视而不见了?我看你就是屁股坐歪了,专门勾结外地人唱衰北京。居然还发表在外地人居多的微博上,还是瞬间就能看到,是外地人给你约的稿,专门来黑北京的吧。对了,别忘了微博的老板就是个外地人。”

”那么多风和日丽的天气你怎么不说,偶然出现38度你就迫不及待的出来带节奏,我看你是跟省外勾结,居心叵测想攻击诋毁bj的宜居,打压bj的房价。“

”北京那么多积极正面的地方都不提,为什么只提天气热?我看你就是屁股坐歪了,说什么都别有用心。别妄图洗!

”这么多地方比北京热多了,你怎么不说。你这是为别人说北京热递刀子。就盯着北京热,怎么不说因为热,商场特地开空调降温的努力,不平衡一下。天热和周围人抱怨一下就好,为什么上微博发,就是想让北京以外的人嘲笑北京。北京怎么你了,你要这样陷害北京…..“

有网友质疑博主不顾全大局:

“你这段文字一看了就让人阴郁,情绪低落。明明现在大好春光,复工复产,各行各业都在为建设北京做贡献。这些你不写,偏偏写一个毫不重要的天气热,给外地人看了,又要成为抹黑北京的证据。你这种人,就是吃北京的饭,砸北京的锅。”

”这个博主,早不说北京热,晚不说北京热,偏偏在这个时候喊起北京热了?什么意思?里应外合配合外网说北京热呗?博主夹带私货还以为别人看不出来,说不定就是港灿娃民仑子反串的,楼下当心不要被带节奏了。“

“我觉得你视北京市自奥运会以来的建城结果于不顾,你是影响北京安定团结的罪人,你是破坏北京向前发展的元凶”

有网友建议博主从自身查找原因:

“为什么38°你就觉得热了?你看人家骆驼,40°以下还以为是在北极。你不从自己身上找原因?”

”你觉得热就去北京改变它,你冷,你所在的地方就冷了!“

“你觉得热就应该努力让自己成为一台空调,为北京制冷,而不是发出这种无病呻吟!”

”如果你决定北京热,那你就去种树、去种草,去改变这座城市的温度!北京不需要你这个键盘侠!北京的温度应该以北京气象台为准,你是气象台吗?张口就来?!

有网友希望博主“滚出”北京:

“说热就说热,干嘛说北京热。那么大博主,伤害北京粉丝。起码说之前考虑一下。你可以某地热,点出北京不好了,失望”

“我们在北京的都很积极的配合脱衣服,你觉得热算什么?热的问题是北京能解决的吗?挂北京算什么?有本事你去射太阳啊”

你为什么要解释这么多?你如果真正爱北京就应该对北京人有起码的信任,解释这么多是不是觉得北京人连你一句话都理解不了?自我感觉不要太良好哦!北京就是北京人,你这么不信任北京可以不要在北京待,滚回你老家去。

“非我北京人,其心必异。老祖宗说的果然没错,北京提供了这么好的环境给你们,你竟然说热,真是白眼狼”

地域(狱)使者们也纷纷“表示不满”:

“你的意思就是你不在乎天津呗,天津做错了什么你要这样???”

“我们东北怎么招你们了,39摄氏度不配被看到?”

“为什么你只说北京热?难道宜昌就不热了?我们不是首都,难道就不是中华人民共和国神圣不可侵犯领土的一部分?”

“博主这是地狱(域)歧视”

有网友模仿环球时报总编辑“胡锡进体”回复:

“最近互联网上出现了“我觉得今天北京有点热”的声音,老胡也看了天气预报,知道大家因为天气热难免心情烦躁发出一些抱怨,这是群众朴素的感受,我相信说这句话的网友本身并没有恶意。但是老胡忍不住啰嗦几句,北京是个有2000多万人口的大城市,南北温度差别非常大,要让气温照顾到每个人的感受显然是不可能的。应当说,这些年来情况是在逐渐改善的,我们不能忽视老天爷在这方面的努力。最后老胡不主张大家把注意力放在天气热上,全世界抗疫斗争已经进入攻坚阶段,希望大家把宝贵的媒体资源留给那些。。。”

“你说北京今天热是中国内部的一件事,它有其当时的场景和逻辑。但你说北京热如果被拿到美国和西方去扩散,就是另一回事了,它一定会被世界环境组织捕捉到。很有可能的是,在未来的风浪中,中国经济,包括那些为了建设北京不遗余力的普通人,都将会为了你这句话埋单。”

然后网友也一本正经的支持“老胡”的看法,并为这条评论“点赞”:

“老胡中肯”

“有内味了,胡味十足”

“哈哈哈哈哈,胡言胡语。”

“兄弟,接班人就是你了”

当然还有一条意味深长的评论:

“系统1GB,补丁100GB”

看完所有评论,有的人笑哭了:

“太有内味儿了……虽然知道大家是调侃,还是忍不住头皮发麻了……”

“啊哈哈恍恍惚惚笑精神了”

“屁股坐哪儿真是个世纪难题 现在也没搞明白”

“现在发微博都那么小心了”

”哈哈哈哈哈哈笑死,又感觉很悲哀“

“评论给看笑了,笑着笑着就哭了。”

结语

这就是目前互联网生存状态,好笑又悲凉。

我们的社会,需要多点包容和理解,“我不赞成你但我捍卫你说话权利”。

一段怪异的梦


一切从我某次献血开始.
有一个机构要整理人类基因, 向我发了邀请函, 我献了血.

是年冬, 我开始发觉自己的记忆出现了问题, 我隐约记得我出过海, 九死一生, 但还有一些报酬未给, 我每一次想要去要报酬, 都会痛苦至不能自已, 陷入癫狂.

多次之后, 我终究还是忘记了这段事情.

次年, 除掉出海这件事, 我也开始频繁忘记其他, 我记不清我有两个还是三个室友, 我记不清那里发生过什么, 我记不清\(\sin \frac{\pi}{6}\). 整个学校也出了问题, 出现各种各样的异常事件, 每一次我将要意识到异常, 都会被某种奇怪的力量将异常感从脑中压下.

在某一次刺激中, 我突然意识到了一些事情, 我决心不能就此遗忘下去, 我开始强行清醒, 我看到并记起了越来越多古怪的事情.

入夜常有无形怪物从外闯入, 吸食人身. 常有同学莫名消失, 其过去存在的痕迹仍在, 人不见了, 没有人意识到问题. 走廊上常有血迹, 一滩一摊, 同学们在上面走来走去, 没有感觉.

我决心去采集血液验下DNA, 但是每一次将要行动, 都会被未知的力量阻拦, 有学校突然开会, 一一点名, 有突然考试, 有血液突然消失, 甚至最后一次, 我终于到了那一滩血液旁, 我突然茫然而立, 突然不知道我要干什么, 采集基因?那是什么?为什么我手上有一个试管?试管? 试管是什么!

又过了一段不记得多久的时间, 异形吸食愈加频繁.

我童年曾经相信自己拥有特殊的力量, 我是神子, 我可以通过祈祷控制未来走向, 我万法不侵. 后来慢慢长大, 自知其幼稚, 也慢慢扔下.

某次吸食怪着身, 我的记忆翻滚在脑中翻滚在吸食怪体表, 我想起了那些咒语, 我念了出来, 我全身发麻, 进入清明梦, 吸食怪灰飞烟灭, 在梦里我竟可以思考和回忆. 我便以此抵抗.

我开始确认这是一件恶劣的模因感染事件, 不仅感染人, 也「异化」世界. 我尽力调查.

但是每日的异常简直要把我逼疯, 我每天晚上都要听着怪物沙沙地拖着地移动, 听着奇怪的声音在其他人身上传来, 勉力抵抗和净化怪物, 我看着我的同学越来越少, 走路时小心翼翼绕过已经覆盖了大部分走廊的血迹.

我决定回家, 依然是各种凭空出现的阻拦, 甚至最后直接下了大雪, 路上只剩下一条小道. 列车停运, 退票. 我疑心只是幻觉, 冬天怎么会那么长呢? 我艰难地往校外行, 可是, 又像验血那次一样——甚至过之, 我刚走出了宿舍群, 就忘记了我的目标. 大雪纷飞, 茫然而立, 天地独我一人.

学校要办一个活动, 面向全校, 我认为可能是一次机会. 我撑到了那次活动举办, 主持人是我高中语文老师, 教了我三年, 是我最感到舒服的老师之一. 我疑心那也是幻觉.

活动尚未开始, 只是开始播放一些音乐, 我周围的人就开始起变化, 他们变得凶恶, 竟有长出了兔耳鹿角猴尾, 他们在地上爬行, 撕咬争斗.

在体育馆时, 那会馆甚至有弥漫的一体的东西压在所有人身上, 像水, 其压迫感又如无形的吸食怪果冻, 黏黏腻腻.

我在角落里坚持到活动开始, 只是一开始, 我那语文老师说了几句话, 肉眼可见的, 台下异常的变化速率增大, 底下成了修罗场, 如动物园, 如斗兽场, 昔日行动迟缓的他们, 竟如此凶狠. 我仓皇而逃.

环境里的限制越来越大了, 人们走路的时候必行于路右3/8, 一板一眼机械地行走. 食堂只留了几个菜目, 形状固定, 味如塑料, 食堂里的人津津有味, 并且总是按照阿基米德螺线扩张食用. 白天天上永远是像画上去的白云和太阳, 晚上则是繁星点点而无月. 眼前的东西难以区分, 手中的手机会变成蛋挞, 还会在下一瞬间变成锤子, 我再也无法从屏幕里获取外界信息. 那些变化的人全部消失, 我自身难保, 难以调查.

我于下午四点二十一突然醒来, 花了好一会才意识到我从一点钟躺下打算午睡四十分钟, 我的闹钟未响, 一直睡到现在.

外面下了大雨, 天色已晚, 我一阵庆幸, 幸亏这不是现实.


注: 今天考图形学的时候, 我真的忘掉了\(\sin \frac{\pi}{6}\)是多少, 脑子里划过了数值分析里学到的各种奇技淫巧, 没一个好算的, 后来恍然大悟用几何法做了证明.

舌头受伤后的发音映射

最近舌头受了伤, 导致我说话发生了一些改变, 感觉挺有趣的, 因而尝试着标一下音. 顺序来自维基, 基于我自己的日常发音有所调整.

如果现读音有多项, 按频率顺序依次降低.

元音变化不大, 所以只写了几个感觉变化大的.

有些字并不一定按照这个表来读, 给出两个例子:

野路子写法

华为员工二百五十一天

hɤa ʋei ðyan kuŋ ə pai ʋu ɦɤ ɤi kʰiɛn

计算机图形学

ki kʰyan ki ku kʰiŋ/ɦiŋ kyie/kyæ

请听题:

  1. 我舌头伤到了哪里? (50分)
  2. 我最可能是怎么伤到的? (50分)

完全使用浏览器扩展完成的爬虫

在给一个初学者讲爬虫的时候, 我为其演示了直接使用Chrome Console获取大部分数据, 之后打算顺着这条路走下去, 没想到坑真的是多. 但是话已经说下去了, 因而我必须用Chrome所有内置的功能完成一个爬虫. 遂得此篇.

最初我打算用Sources里的Snippets来做, 写完了才发现刷新了代码就会结束掉, 因此必须得保证代码能够持续进行.

一个很自然的想法就是使用window.open或者iframe, 但是由于索引页和目标页不在同一个子域名下, 因此除非跳转到其子域, 否则无法使用. 但是一旦跳转, 当前索引数据就会消失. 自然的想法是使用LocalStorage, 然而LocalStorage也有同源策略问题.

那么就只好写浏览器扩展了, 浏览器中content-script是注入到页面的, 但是只有一个这个也不行, 注入到页面的时候是在当前域名的空间下的, 因此还需要一个background来储存网页链接和爬取的数据.

所以文件结构为:

1
2
3
4
5
.
├── js
│ ├── background.js
│ └── content-script.js
└── manifest.json

但是事情怎么可能这么简单就过了呢? 由于页面加载需要时间, 循着用Python写爬虫的经验, 我随手写了一个

1
2
3
4
5
6
7
8
9
get_elem_by_xpath = (xpath) => {

elems = document.evaluate(xpath, document).iterateNext();
return elems? elems.textContent || elems: ''
}
// 强行Python命名风格

while(!get_elem_by_xpath(elem_xpath));

来等待元素出现, 该元素出现即网页加载完成. 然后Chrome就僵死了, 标签无法直接关闭, 控制台啥都看不到, 要关只能任务管理器.

我最初完全没有怀疑它的原因, 只是以为配置文件写错或者什么其他, de到最后才发现, 这行代码会把整个网页给带崩, 我初步怀疑document.evaluate是否可能代价太大, 以至于网页拿不到任何CPU资源来加载.

快要睡觉了才意识到content-script是在网页加载之处注入进去了, 因此就是网页内的事情, 单线程. 而我当时就像是魔怔一样想着二者是异步的. 于是我又尝试了阻塞等待时间, 简直在玩行为艺术.

意识到问题后, 就用setTimeout完成了异步递归, 大致就是:

1
2
3
4
5
6
7
8
f = (args..) => {
if(!get_elem_by_xpath(elem_xpath)){
setTimeout(f, ..);
} else {
..
}
}

而且其中比较有趣的是, location.href=...就和return差不多, 页面离开那么所有本页代码重置, 后面就不再运行.

background交互使用chrome.runtime.sendMessage, 那边接收使用chrome.runtime.onMessage.addListener, 监听器接受三个参数request, sender, sendResponse, 第三个用于返回数据. 此间可以直接传递JS对象, 不需要序列化和反序列化.

总的来讲, 整个流程就是:

  1. 如果当前是索引页, 到2, 如果是待爬页, 到5, 否则结束.
  2. 打开索引页, 程序获取urls, 发送给background暂存
  3. background获得urls, 如果urls为空, 结束, 否则到4
  4. url = urls.pop(), 把新的urls发送给给background记录, 之后跳转到url.
  5. 如果目标元素不存在, 1s后到5, 否则下一步.
  6. 获取元素信息并保存, 到3.

// 本篇采用yosoro书写