阅读视图

发现新文章,点击刷新页面。
🔲 ☆

2023 新年快乐!

3202 年啦!新年快乐~


2022 年好像没有整出什么像样的活,甚至最后一个月直接开摆了,真是糟糕。

想了一下,2022 年真的是什么事情都没有做,但是却好像忙了一年,这是为什么呢x

2022 年花了特别多时间来 emo, 也没有发生什么特别开心的事情,倒是有一堆不开心的事情。后半年的课真的好忙,感觉根本没有时间用来做自己感兴趣的事情,不过我也还是抽出很多时间贴了很多瓷砖。

关于写代码的话 2022 年确实没有干什么,值得一提的也就最后几个月花了挺多时间阅读了 kernel 的代码,加深了对 linux 内核空间的了解。2022 年写了一些 Rust 代码,大部分时间还是在写 Nix 和 TypeScript。最后几天对 risc-v 产生了点兴趣,正在啃 risc-v 的规范。2022 年了解到了 User Mode Linux, 对这个很感兴趣,也了解到曾经有个项目是 umlwin32, 但是现在没办法运行了,也不怎么能修好他,感觉比较可惜。对了,2022 年还做了一件曾经很想做的事情,感兴趣可以看看 BV1m14y1H7EE

吉他的话 2022 年学了一些新曲子,有无题、流行的云、君と僕、ナユタ、Brand New Wings, 但是还是特别菜,弹的稀烂。不过我还是会坚持弹下去。

2022 年最后那段时间各科事情都特别多,再然后阳了,那段时间就没有怎么贴瓷,附一张瓷砖图:

瓷砖

对于 2023 年的话,希望能整点什么大活,再像 2022 年这样的话就比较无趣了。还有几天就是 20 岁生日了,对于生日我倒是没有什么特别的想法,那就希望 2023 年能有更多的时间做喜欢的事情以及希望有人能喜欢我吧。

🔲 ☆

TypeScript 类型体操,实现编程语言的解析并求值

之前看到了 TypeScript 类型体操天花板,用类型运算写一个 Lisp 解释器 这篇文章,感到非常震撼。这两天在研究一些文本解析的问题突然想起了这篇文章,然后自己也想糊一个。

众所周知,TypeScript 的类型系统是图灵完备的,于是我们可以用 ts 的类型系统来做到一些奇妙的事情,比如上面那篇用类型系统实现的 lisp 解释器。而我想要做一个更通用的东西。

先看一下效果:

type E<I extends string> = Eval<Parse<producers, table, I>>type T = E<`(1 + 2) * 3 + 4 * 5`>//   ^? type T = 29

代码在 Anillc/yatcc

先糊一个词法分析器,

type LexTrim<I extends string> = I extends `${' ' | '\t' | '\n'}${infer R}` ? LexTrim<R> : Itype Num = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'type LexNumber<I extends string> = I extends `${infer N extends Num}${infer R}`  ? [`${N}${LexNumber<R>[0]}`, LexNumber<R>[1]]  : I extends `.${infer R}`    ? [`.${LexNumber<R>[0]}`, LexNumber<R>[1]]    : ['', I]type LexString<I extends string, In extends false | string = false, Ignore extends boolean = false> = In extends false  ? I extends `${infer S extends "'" | '"' | '`'}${infer R}` ? LexString<R, S> : ['', I]  : Ignore extends true    ? I extends `${infer L}${infer R}` ? [`${L}${LexString<R, In>[0]}`, LexString<R, In>[1]] : ['', I]    : I extends `\\${infer R}`      ? LexString<R, In, true>      : I extends `${In}${infer R}`        ? ['', R]        : I extends `${infer L}${infer R}`          ? [`${L}${LexString<R, In>[0]}`, LexString<R, In>[1]]          : nevertype Id =  | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g'  | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n'  | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u'  | 'v' | 'w' | 'x' | 'y' | 'z' | 'A' | 'B'  | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I'  | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P'  | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W'  | 'X' | 'Y' | 'Z' | '_'type LexId<I extends string, F extends boolean = true> = F extends true  ? I extends `${infer L extends Id}${infer R}` ? [`${L}${LexId<R, false>[0]}`, LexId<R, false>[1]] : ['', I]  : I extends `${infer L extends Id | number }${infer R}` ? [`${L}${LexId<R, false>[0]}`, LexId<R, false>[1]] : ['', I]type Equal<A, B> = [A] extends [B] ? true : falseexport type Lex<I extends string> =  LexTrim<I> extends infer Trimmed extends string    ? Trimmed extends '' ? []    : LexNumber<Trimmed> extends [infer N extends string, infer R extends string] ? Equal<N, ''> extends false ? [{ type: 'num', value: ToNumber<N> }, ...Lex<R>]    : LexString<Trimmed> extends [infer S extends string, infer R extends string] ? Equal<S, ''> extends false ? [{ type: 'str', value: S }, ...Lex<R>]    : LexId<Trimmed>     extends [infer I extends string, infer R extends string] ? Equal<I, ''> extends false ? [{ type: 'id' , value: I }, ...Lex<R>]    : Trimmed extends `${infer L}${infer R}` ? [{ type: L }, ...Lex<R>]  : never : never : never : never : never

LexTrim 这个类型用于去除字符串前的空白字符,LexNumber LexString LexId 用于解析数字、字符串和标识符,Lex 把他们组合起来。

然后糊语法分析器。LR 是一种解析上下文无关文法的算法,可以预先把解析文本所需要的所有可能的状态保存到表中,再使用一个状态机来解析他。分为 Action 表和 Goto 表,Action 表中保存移近和规约的规则,Goto 表保存规约过后应该往栈中加入的状态。简单糊一个 LR 表生成器过后,就可以愉快地用类型写语法分析器啦。

export type Parse<  P extends Record<number, Producer>,  T extends Table,  S extends string[],  B extends (Node | Token)[],  F extends boolean,  Tokens,> = F extends true ? B[0] :  Tokens extends Token[]    ? GetAction<T, S, Tokens> extends [infer Type extends number, infer Action extends number]      ? Type extends 0 ?        Parse<P, T, [...S, ToString<Action>], [...B, Tokens[0]], false, Tail<Tokens>>      : Type extends 1 ?        Parse<          P, T,          [...Pop<S, P[Action]['tokens']>[0],            ToString<T[Last<Pop<S, P[Action]['tokens']>[0], string>][P[Action]['name']][1]>],          [...Pop<B, P[Action]['tokens']>[0], { id: Action, nodes: Pop<B, P[Action]['tokens']>[1] }],          P[Action]['name'] extends 'G' ? true : false, Tokens        >      : never : never    : never

完事之后还需要对抽象语法树求值。

type CastEval<N, T> = Eval<N> extends T ? Eval<N> : never type Eval<N> = N extends Node  ? N['id'] extends 1 ? Eval<N['nodes'][0]>  // @ts-ignore  : N['id'] extends 2 ? Add<CastEval<N['nodes'][0], number>, CastEval<N['nodes'][2], number>>  : N['id'] extends 3 ? Subtract<CastEval<N['nodes'][0], number>, CastEval<N['nodes'][2], number>>  : N['id'] extends 4 ? Eval<N['nodes'][0]>  : N['id'] extends 5 ? Multiply<CastEval<N['nodes'][0], number>, CastEval<N['nodes'][2], number>>  : N['id'] extends 6 ? DividedBy<CastEval<N['nodes'][0], number>, CastEval<N['nodes'][2], number>>  : N['id'] extends 7 ? Eval<N['nodes'][0]>  : N['id'] extends 8 ? Eval<N['nodes'][1]>  : N['id'] extends 9 ? N['nodes'][0] extends NumberToken ? N['nodes'][0]['value'] : never  : never : N

TypeScript 已经觉得递归太深不开心了,我们这里让他闭嘴。加减乘除直接抄了上面的 lisp 解释器的加减乘除。然后我们就得到了一个类型系统上的计算器。

类型体操

🔲 ☆

Nix, Nixpkgs, NixOS 从入门到入土

nix 是个非常棒的工具,这篇文章将会讲解如何入门 nix。

注:在学习 nix 之前,请保证你已经有了一定的 Linux 使用经验,如果对本文一些内容不太理解,你可能暂时还不太适合使用 nix。


1. nix 是什么

根据 nix 在 GitHub 仓库中的定义,nix 是一个用于 Linux 和其他的 Unix 的强大的包管理器,他使包管理可靠和可重现。官网中列出了 nix 的以下特性:

  • 可重现 (reproducible)
  • 声明式 (declarative)
  • 可靠 (reliable)

我将会在这篇文章中向你讲解如何使用 nix。

一些刚开始学习的人可能会搞混 Nix, Nixpkgs, NixOS,请记住他们是不同的东西。

2. 安装 nix

这个小节中讲述的 nix 安装方法是在使用 NixOS 以外的发行版的情况下的安装方法。如果你正在使用 NixOS,可以参考之后的 NixOS 部分。

nix 与你系统上已经有的东西并不冲突,即使你系统里有 apt, pacman,也能愉快地安装 nix。nix 的安装过程会新建一个 /nix 文件夹并建立几个符号链接。

安装 nix

如果你想要让多个用户可以运行 nix, 可以使用以下命令安装:

$ sh <(curl -L https://nixos.org/nix/install) --daemon

如果你想要单用户运行 nix 或者你没有可用的 systemd (比如 wsl1 环境), 可以使用以下命令安装:

$ sh <(curl -L https://nixos.org/nix/install) --no-daemon

如果你的网络情况不是很好,可以尝试将 https://nixos.org/nix/install 更换为 https://mirrors.tuna.tsinghua.edu.cn/nix/latest/install

使用以上命令时不应该使用 root 账户,他会调用 sudo 来创建需要的文件,请保证你的系统里有 sudo。如果你需要使用 root 安装,请参考附录中的安装方法。 由于 nix#6882,现在已经可以直接使用 root 安装 nix。

更换 Channel 和 Binary Cache

安装完 nix 之后我们可能想要给 nix 换一下源。我们需要更换 Channel 和 Binary Cache 两个东西的源。我将会在之后讲解 Channel 和 Binary Cache 的作用。

$ nix-channel --add https://mirrors.tuna.tsinghua.edu.cn/nix-channels/nixpkgs-unstable nixpkgs$ nix-channel --update

如果你是多用户安装 nix, 请修改 /etc/nix/nix.conf,如果你是单用户安装 nix, 请修改 ~/.config/nix/nix.conf:

substituters = https://mirror.sjtu.edu.cn/nix-channels/store https://mirrors.tuna.tsinghua.edu.cn/nix-channels/store https://anillc.cachix.org https://cache.nixos.org/trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=

修改完成后如果你是多用户安装 nix, 请重启 nix-daemon:

# systemctl restart nix-daemon

这样就安装好 nix 了。

3. nix 初体验

$ nix-shell -p hello[nix-shell:~]$ helloHello, world![nix-shell:~]$ exit$ hellobash: hello: command not found

可以看到我们通过 nix-shell 得到了一个 hello 程序,退出后就没有了。如果想要知道能使用哪些包,可以到 https://search.nixos.org 查询。

你可能会在别的地方看到类似于 nix-env -iA nixpkgs.hello 的安装指令。我不建议使用 nix-env,他会导致你使用的包在不同的 nixpkgs commit 上等问题,之后会给出一些替代方案。

nix 做了什么?

在上面的例子中,我们使用 nix-shell 获得了一个带有 GNU Hello 的 shell 环境,退出后便没有 hello 了。

查看 /nix/store,可以看到类似以下的内容:

$ ls /nix/store0006xvyj2xdqj7sh41x5r9j911gwjykq-5fe5595a5a46eab069dcd2d6813c1b68cd9ade4f.drv000n05b5xig1av867dk9vcizi37rwai1-X11-1.10.2.drv000q965rl5mr33blq57538n906jj7222-readline63-008.drv000vv6vpv1ng2fkwmgh5k5i2qpcm2v2i-ruby2.7.6-creole-0.5.0.drv001gp43bjqzx60cg345n2slzg7131za8-nix-nss-open-files.patch001qi4pcc8pk2dkw4q6m4lvh9m2cbmv5-diff___diff_4.0.2.tgz.drv002mlfdwrf9d94ix738zgnncpd01hdnw-table___table_4.0.2.tgz.drv...

nix-shell 做的事情就是构建一个 hello,然后将 /nix/store 中的 hello 放到 shell 的 PATH 变量中,你就可以使用 hello 了。

观察 /nix/store 中文件的结构,会发现有两种文件,一种是 ${hash}-${name}.drv,一种是 ${hash}-${name}。前者是一个 derivation 的声明,后者是一个 derivation 的构建结果。

在 nix 中,其实没有包的概念,可以把 derivation 理解为别的构建系统中的包。nix 语言就是用来声明这些包的。一个 derivation 可以依赖别的 derivation,最后在 /nix/store 中形成「闭包」。

如果你好奇 hello 的 nix 表达式是在哪里定义的,可以查看 nixpkgs 中的定义

4. 深入 nix

特性

我们在第一小节中写出了 nix 的以下几个特性:

  • 可重现 (reproducible)
  • 声明式 (declarative)
  • 可靠 (reliable)

如果你曾经学习过 Haskell 等纯函数式语言,你可能知道 纯函数 这样一个概念。纯函数有以下特点

  1. 对于同样的输入,纯函数将会返回同样的输出。
  2. 纯函数中不会对外产生任何副作用,比方说一个纯函数不会修改外部的任何一个变量。

nix 的可重现性,便是尽力去实现纯函数的效果。你在 nix 表达式中声明了某个东西要怎么构建,nix 会提供同样的环境 (例如在构建环境中没有办法访问网络) 来构建他 (由于谁也没办法保证某个 Makefile 里生成了个随机数这种操作,所以 nix 只能去接近同样的构建环境)。同时,nix 也是一门函数式的语言,在达成一些条件的情况下他就是纯函数式的语言,在其中的任何求值都是「纯」的。这保证了 nix 的可重现性。在这样的情况下,nix 构建出来的文件 (整个 /nix/store) 应该是不可变的,因为他对应了一组输入,要保证输入和输出是对应的,如果输出可以随意变化,那会影响到别的构建,引起问题。

由于纯函数式的特性,我们写的 nix 代码比起命令式语言一行一行地执行,更像是在声明某个东西,所以 nix 也是声明式的,你可以通过 nix 文件来声明包的构建流程。你可以很轻松地分享你的构建流程或者开发环境给别人,因为他们都声明在 nix 文件中。

之前也讲到,nix store 中的文件格式是 ${hash}-${name} 这样的格式,这与 Linux 传统的目录结构 (FHS) 有所不同。在传统的结构中,二进制文件存放在 /usr/bin, /usr/lib 这些位置,一旦某个软件有更新就必须要覆盖之前的文件。这可能导致很多问题,比如 A B 软件都依赖 C,A 必须要低于 1.1.0 的 C,B 必须要高于 1.2.0 的 C,这时便会出现难以解决的依赖冲突问题。而在 nix 中不存在这样的问题,因为 nix store 中可以存放多个版本的 C。同时由于这个特性,我们更新某个软件并不会影响到别的软件,出现问题也能快速回滚到以前的文件,实现了可靠性。

Channel

Nixpkgs 是一个软件集合,包含了很多的软件,可以理解为其他包管理器中的官方源的目录,任何人都可以通过 Pull Request 的方式向其中新增包。其实 Nixpkgs 就是一个大型的 nix 表达式,里面声明了很多软件的构建流程以及一组打包软件用的工具和模板流程用于简化打包。

Channel 是一种用于管理 nix 表达式的东西,通过 nix-channel 可以做到从网络中获取 nix 表达式并完成更新等操作。这些下载下来的表达式被储存在 ~/.nix-defexpr

上文中提到的更换 Channel 便是将官方的 Nixpkgs 下载链接换到了别的地方,而我们通过 nix-shell 加载的软件,也是根据 Nixpkgs 中的定义获取的。

Binary Cache

之前也说到过 nix 的纯函数式特性,对于同样的输入,一定会输出同样的结果。那么 nix 表达式的执行结果也会是一样的。所以我们可以把同样的 derivation 的构建结果储存起来再提供给别人下载,这样就能大大减少 derivation 的构建时间。

需要注意的是,/nix/store 中的文件都应该被视作公开的,你不应该在其中放一些敏感数据。如果你需要存放敏感数据,之后会提供一些解决方案

Profile

Profile 是一个用于管理用户、系统环境的功能,它可以实现将 /nix/store 中的文件链接到 /nix/var/nix/profiles 中,并且进行版本管理。当你使用类似 nix-env -iA 安装程序的时候,便会更新 profile,产生新的 generation。可以发现,在安装 nix 的时候 /nix/var/nix/profiles/per-user/${username}/profile 就已经被链接到了 ~/.nix-profile,而在非 NixOS 环境中 ~/.bashrc 中也被加入了 source ~/.nix-profile/etc/profile.d/nix.sh,这样就能实现了运行 profile 中的程序。

不推荐手动管理 profile,原因已经在上文中讲过,从不同 commit 的 nixpkgs 中安装软件可能导致一些问题 (比如输入法无法使用)。

5. nix 语言

该部分单独写到了 nix 语言教程 中。

下面的内容将会假设你已经学会了 nix 语言。

TODO

🔲 ☆

2021 年末总结及 2022 新年快乐!

抓着 2021 年的尾巴(其实发布的时候已经 2022 了),写下了这篇年末总结。为什么会想写这篇年末总结了呢,因为想写所以学着别人写了(

今年也发生了好多事呀,高考、上大学什么的。可能一年里的很多事情我都已经忘了,就讲讲我印象比较深的事情吧。

今年学习了计算机网络,从最开始什么都不知道加入了 DN42 的群,到现在也学到了一点东西,认识了很多的人。高考之前我也没有在认真学习x。为了白嫖 44net 特意去考了业余无限垫的操作证,最后也没有嫖到。

高考过后来到了愉快的暑假。本来很多事情想在暑假做了,结果做了很多其他事情,本来打算暑假做的事情也没弄完啊,要是时间再多一些就好啦。暑假里考了科目一科目二,打了 osu,学了很多东西,其他干了什么事情我也不太记得了,反正感觉没有浪费一点时间。

到了大学之后也一如既往地每天干点自己喜欢的事情。再过几天就要期末考试了,大概会挂科吧(

上大学之后买了第一把吉他,之前用别人的吉他弹了好久。然后把押尾的卡农学完了,最近学了 like a star ,虽然能弹下来了,但是不流畅,32 分音符我还扫不到那么快啊,感觉需要锻炼一下手臂了。

快到九月的时候我知道了 NixOS,十一月的时候把所有机器都换上 NixOS 了,不得不说,nix 真的好玩。

今年在 GitHub 上也贡献了很多代码,修了很多 typo(

这一年认识了好多的人,学习了好多的新东西,总的来说感觉过的非常的充实,不知道 2022 年又如何呢。还是坚持自己喜欢的东西继续干下去吧!

祝大家元旦快乐,新年快乐~

今年也谢谢你呀


最后,虽然正文内容不多,还是来个超长的感谢名单吧 ww,忙活一年也要感谢大家一年的陪伴(排名不分先后,可能会有遗漏的,抱歉啦,想起来或者提醒我会加上去的x)

感谢奶黄黄、洛卡、花叶叶、森下下、a9、泷樱、夜蛍姐、mika、nono、四喵喵、yuiko、里奈、au、彩虹、南音、lotywe、无名酱、小布丁、小石头、假酒、好处、唯梦梦、.45、萤火以及洛卡群里的各位(太多就不一一列出来啦)

感谢北斗、露露噗、加酱、浅安、红月、朴儿、白茗、小喆、鹿标

感谢 moecast、ayumu、hertz、cola 等 telegram 上的朋友

感谢 shigma、il、一介、masnn、idlist、小鱼

感谢 lonay、douzi

感谢小猿、易姐、fa 酱

感谢各位高中同学、高中老师

感谢洛卡群各位,感谢仟酱群各位,感谢花叶叶群各位,感谢红月群各位,感谢 dn42 群各位,感谢 koishi 服务器的各位,感谢二刺螈研究会的各位

还要感谢家人

感谢陪我说话的每个人

🔲 ☆

新年快乐!

已经1202年啦www,新年快乐!

新的一年首先希望能考上好的学校,然后想要填好多好多的坑,想要好多好多的star,想要更多地届到鹿乃,也祝大家心想事成!

2020年虽然发生了很多事情,但是整体来讲还是过得挺快乐的一年,认识了好多好多的人,参与了合唱企划,摸了好多的鱼,最后在昨天QQ还被封号了唔,www

期待今年会发生什么样的事情XD

❌