前言#
文章从知乎那边同步过来的,所以时间上有些出入。。。主要是这知乎的文章编写不是完全按照markdown的语法导致迁移过来比较麻烦(其实是懒)。。
上周五看朋友圈看到了朋友分享的一个仓库:herlang。顿时笑晕在厕所(摸鱼中)
不得不说,xxn的逆天行为已经是众所周知了。
然后我也补充了一些个人的看法,比如let对应宝宝你是一个和微胖就是我调整的 doge。

然后我发现一个完美契合梗的issue:【bug】女性不能被定义

Women cannot be
defined!
本来打算不再关注这个项目,毕竟只是一个乐子项目。结果看到这搞得我热血沸腾,想给他实现了。
实现#
项目是基于rust的,大体原理就是基于AST调整关键字命名,比较简单。
宝宝你是一个 女性 = 微胖女性不能被定义,那么就是变量声明(VariableDeclaration),我们这里当前只实现了宝宝你是一个(let),大体结构如下。
{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"kind": "宝宝你是一个",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "女性"
},
"init": {
"type": "Identifier",
"name": "微胖"
}
}
]
}
]
}我们只关注定义部分。
我们回归代码,如果要让这个定义报错,那么需要在解析的过程,放在parse阶段比较合适,不然后续解析都会跟着出问题,所以需要扼杀在parse时解析声明的identifier阶段。
pub fn parse(&mut self) -> Program {
let mut program: Program = vec![];
while !self.current_token_is(Token::Eof) {
match self.parse_stmt() {
Some(stmt) => program.push(stmt),
None => {}
}
self.bump();
}
program
}
fn parse_stmt(&mut self) -> Option<Stmt> {
match self.current_token {
Token::Let => self.parse_let_stmt(),
// ...省略
}
}
fn parse_let_stmt(&mut self) -> Option<Stmt> {
// ...省略
let name = self.parse_ident()?;
// ...省略
// 女性是不能被定义滴
if &name.0.as_str() == "女性" {
self.errors.push(ParseError::HerUnexpectedToken {
got: format!("女性是不能被定义的!!!"),
});
return None;
};
self.bump();
// ...省略
}这样就搞定了,很简单。不过为了方便拓展,我们把和女性相关的词抽出来放到全局:
use std::collections::HashSet;
use std::sync::LazyLock;
pub static HER_KEY_WORDS: LazyLock<HashSet<&'static str>> =
LazyLock::new(|| HashSet::from_iter(["女性", "her", "女", "female", "woman", "girl", "lady"]));然后回来调整下写法
// 女性是不能被定义滴
if HER_KEY_WORDS.contains(&name.0.as_str()) {
self.errors.push(ParseError::HerUnexpectedToken {
got: format!("女性是不能被定义的!!!"),
});
return None;
};顺便补充下错误类型和文案:
#[derive(Debug, Clone)]
pub enum ParseError {
UnexpectedToken { want: Option<Token>, got: Token },
HerUnexpectedToken { got: String },
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ParseError::UnexpectedToken { want: w, got: g } => match w {
Some(w) => write!(
f,
"啊啊啊啊啊啊啊啊啊啊啊啊 Unexpected Token: expected {w:?}, got {g:?}"
),
None => write!(
f,
"啊啊啊啊啊啊啊啊啊啊啊啊 Unexpected Token: no prefix rule for {g:?}"
),
},
ParseError::HerUnexpectedToken { got: g } => {
write!(f, "啊啊啊啊啊啊啊啊啊啊啊啊 SyntaxError: {g:?}")
}
}
}
}这样就行了,然后就是写测试用例测试,不过这里我没搞(因为作者也没搞。。)
测试#
我们直接打包成wasm到浏览器上测试即可,作者已经帮我们搞好了。
{
"name": "herlang",
"version": "0.1.0",
"author": "迷渡 <justjavac@gmail.com>",
"license": "ISC",
"keywords": [
"herlang",
"小仙女",
"xxn"
],
"description": "HerLang 小仙女编程语言,使用 Rust 编写。",
"main": "index.js",
"scripts": {
"build": "cargo build --release --features=\"binaries\" --locked",
"wasm:build": "rustup target add wasm32-unknown-unknown && cargo build --bin wasm -Z unstable-options --profile tiny --target wasm32-unknown-unknown --features=wasm",
"wasm:copy:unix": "cp target/wasm32-unknown-unknown/tiny/wasm.wasm web/src/herlang.wasm",
"wasm:copy:win": "copy /Y target\\wasm32-unknown-unknown\\tiny\\wasm.wasm web\\src\\herlang.wasm",
"wasm": "npm run wasm:build && (npm run wasm:copy:unix || npm run wasm:copy:win)",
"start": "cd web && npm start",
"dev": "npm run wasm && npm start"
}
}我在根目录补充了些指令,直接跑npm run dev即可,打包完直接运行web环境。
然后在web里测试:
运行:

结果:


最后#
可惜的xxn连GitHub都不会上。。。达不到让她们破防的目的。
不过也就是图个乐,相信大部分女性都是正常人,不必放在心上。
