GK
type: channel
link: http://blog.kurfuerst.online/
description: Your description here.
items: kitex源码阅读（二）
items: kitex源码阅读（一）
items: PVE使用群晖网络ups服务器实现停电自动关机
items: 自建zerotier-planet根服务器
items: Zerotier Moon节点部署
items: Git多人协作
items: Vite配置代理解决跨域问题
items: 使用docker-nginx搭建http服务器
items: Lecture-4 语法分析
items: Lecture-3 ANTLR

kitex源码阅读（二）
type: item
date: 2025-10-27T10:05:00+00:00
description: 上一篇中，我们梳理了server.Server启动、运行、退出的全流程。我们知道server.Server通过调用remotesvr.Server提供的接口进行数据传输，而不需要关心连接具体如何建立、数据如何封装的问题。这篇我们就来分析一下remotesvr.Server是如何与客户端建立连接并交互的。remote.ServerTransHandler与客户端建立连接、传输数据等工作，remotesvr.Serve是调用了remote.TransServer提供的方法实现的：// remotesvr.Server type server struct { opt *remote.ServerOption listener net.Listener transSvr remote.TransServer sync.Mutex }而remote.TransServer又是依赖ServerTransHandler实现的这些功能：// remote.TransServer type TransServerFactory interface { NewTransServer(opt *ServerOption, transHdlr ServerTransHandler) TransServer } // NewTransServer implements the remote.TransServerFactory interface. func (f *gonetTransServerFactory) NewTransServer(opt *remote.ServerOption, transHdlr remote.ServerTransHandler) remote.TransServer { return &transServer{ opt: opt, transHdlr: transHdlr, lncfg: trans.NewListenConfig(opt), } }以下是ServerTransHandler提供的方法：// TransHandler is similar to the handler role in netty // Transport can be refactored to support pipeline, and then is able to support other extensions at conn level. type TransHandler interface { // read() write() TransReadWriter // 关闭连接 OnInactive(ctx context.Context, conn net.Conn) // 处理请求出错 OnError(ctx context.Context, err error, conn net.Conn) // 请求处理完成后调用，调用先前构建的中间件调用链进行进一步处理 OnMessage(ctx context.Context, args, result Message) (context.Context, error) SetPipeline(pipeline *TransPipeline) } // ServerTransHandler have some new functions. type ServerTransHandler interface { TransHandler // 新建连接 OnActive(ctx context.Context, conn net.Conn) (context.Context, error) // 收到请求 OnRead(ctx context.Context, conn net.Conn) error }可见remote.ServerTransHandler是整个流程的核心，与客户端的连接基本是由它来负责的，那接下来就看一下remote.ServerTransHandler创建的过程：func (s *server) newSvrTransHandler() (handler remote.ServerTransHandler, err error) { // 工厂模式创建 transHdlrFactory := s.opt.RemoteOpt.SvrHandlerFactory transHdlr, err := transHdlrFactory.NewTransHandler(s.opt.RemoteOpt) if err != nil { return nil, err } // 判断是否实现 InvokeHandleFuncSetter 接口 if setter,…

kitex源码阅读（一）
type: item
date: 2025-10-10T13:10:00+00:00
description: 我们可以从官方的示例kitex-example中的hello入手服务初始化main函数不出意外应该是这样的：package main import ( "log" api "github.com/cloudwego/kitex-examples/hello/kitex_gen/api/hello" ) func main() { svr := api.NewServer(new(HelloImpl)) err := svr.Run() if err != nil { log.Println(err.Error()) } }先创建Server对象，再调用svr.Run()运行程序。让我们看一下api.NewServer创建Server对象的过程// NewServer creates a server.Server with the given handler and options. func NewServer(handler api.Hello, opts ...server.Option) server.Server { var options []server.Option options = append(options, opts...) options = append(options, server.WithCompatibleMiddlewareForUnary()) svr := server.NewServer(options...) if err := svr.RegisterService(serviceInfo(), handler); err != nil { panic(err) } return svr } func RegisterService(svr server.Server, handler api.Hello, opts ...server.RegisterOption) error { return svr.RegisterService(serviceInfo(), handler, opts...) }这是kitex框架生成的代码，它先创建了一个server对象，然后调用RegisterService()在对象上注册相应的服务。这个服务可以看作一个回调对象：当有请求时，kitex框架负责接受数据并解码，封装成请求体，再调用相应的服务与方法进行业务逻辑处理。RegisterService()会调用s.svcs.addService()方法，将服务添加到server.svcs属性中。svcs是一个services对象，通过一个knownSvcMap map[string]*service存储相应的服务：func (s *services) addService(svcInfo *serviceinfo.ServiceInfo, handler interface{}, registerOpts *RegisterOptions) error { // unknown service // 处理未知服务，确保只能有一个未知服务 if registerOpts.IsUnknownService { if s.unknownSvc != nil { return errors.New("multiple unknown services cannot be registered") } s.unknownSvc = &unknownService{svcs: map[string]*service{}, handler: handler} return nil } svc := newService(svcInfo, handler) // 处理回退服务，确保只能有一个回退服务 if registerOpts.IsFallbackService { if s.fallbackSvc != nil { return fmt.Errorf("multiple fallback services cannot be registered. [%s] is already registered as a fallback service", s.fallbackSvc.svcInfo.ServiceName) } s.fallbackSvc = svc } // 处理普通服务 // 防止重复注册 if _, ok := s.knownSvcMap[svcInfo.ServiceName]; ok { return fmt.Errorf("service [%s] has already been registered", svcInfo.ServiceName) } s.knownSvcMap…

PVE使用群晖网络ups服务器实现停电自动关机
type: item
date: 2025-07-02T16:18:00+00:00
description: 背景目前有一台山特ups，ups只有一个typeb口，但需要同时给多台设备（两台nas一台pve软路由）供电，且家里停电时ups能够通知所有设备自动关机。由于群晖的网络ups协议用的就是nut，所以将群晖作为nutServer，在pve上安装nut进行配置即可。配置PVE中nut客户端nut在各类linux系统中都可以用默认的包管理系统进行安装apt install nutnut配置修改/etc/nut/nut.conf，将MODE设置为netclientMODE=netclient修改/etc/nut/upsmon.conf中的MONITOR等配置项MONITOR <system> <powervalue> <username> <password> ("master"|"slave") #<system>设置为ups@服务器地址或域名，这里是ups@192.168.1.107 #<powervalue>设置为1 #<username>群晖UPS服务器的默认用户名是monuser #<password>群晖UPS服务器的默认密码是secret #("master"|"slave")，客户机设置为slaveMONITOR ups@192.168.1.107 1 monuser secret slave有可能需要设置RUN_AS_USER选项以解决权限问题RUN_AS_USER root通过upsc ups@<UPS_IP>测试连接情况启动nutClientsystemctl start nut-client #启动网络ups客户端服务 systemctl status nut-client #检查服务状态，如有错误再根据提示进行排查 systemctl enable nut-client #设置网络ups客户端服务自启动编写自动关机脚本默认情况下nut会在收到LOWBATT时执行关机操作，即在ups低电量时关机，相关的配置在/etc/nut/upsmon.conf中POWERDOWNFLAG /etc/killpower SHUTDOWNCMD "/sbin/shutdown -h +0"该配置文件中还有message相关配置# -------------------------------------------------------------------------- # NOTIFYMSG - change messages sent by upsmon when certain events occur # # You can change the default messages to something else if you like. # # NOTIFYMSG <notify type> "message"而我希望软路由会在停电3分钟后自动关机，这就需要使用upssched自定义。在upsmon中设置触发条件通知upssched，并由upssched完成后续工作。配置upsmon首先在upsmon.conf中添加NOTIFYCMD /sbin/upssched，即在发生事件时运行upssched服务。设置触发条件：NOTIFYFLAG ONBATT SYSLOG+WALL+EXEC NOTIFYFLAG ONLINE SYSLOG+WALL+EXEC NOTIFYFLAG LOWBATT SYSLOG+WALL+EXEC触发条件各个选项的含义在upsmon.conf的注释中有详细描述配置upssched随后在/etc/nut/upssched.conf中实现所需功能：CMDSCRIPT /etc/nut/upssched-cmd PIPEFN /etc/nut/upssched.pipe LOCKFN /etc/nut/upssched.lock # 断电使用电池开始180秒后，执行upssched-cmd脚本，带shutdown_self参数 AT ONBATT * START-TIMER shutdown_self 180 # 来电后，停止shutdown_all计时 AT ONLINE * CANCEL-TIMER shutdown_self # 来电后，执行脚本，带start_all参数 AT ONLINE * EXECUTE start_all # 低电量，执行脚本，带shutdown_self参数 AT LOWBATT * EXECUTE shutdown_selfCMDSCRIPT指定需要执行的脚本，随后我们设置触发条件以及传入脚本的参数。AT语句需要在前三行之后，且需要保证PIPEFN及LOCKFN命令中的文件NUT应有读取写入及操作的权限，若权限不正确则会报错Failed to conn…

自建zerotier-planet根服务器
type: item
date: 2023-10-11T12:28:00+00:00
description: 自建根服务器对于 zerotier 根服务器在国外，高延迟且连接不稳定的问题，此前尝试过自己部署 moon 节点，不过虚拟局域网内的设备想要通过 moon 节点直连还是需要先通过根服务器与 moon 节点取得联系（仅仅是取得联系即可，对带宽延迟都没有太大要求，所以之前处在勉强可用的状态）然而最近 zerotier 的官方根服务器似乎彻底被墙死，moon 节点也几乎不可用。为了继续正常使用 zerotier，开始考虑自建 zerotier 根服务器的方案。最终找到了基于 ztncui 优化而来的 zerotier-planet 项目，简单而言就是用 docker 部署一个独立的 zerotier 虚拟网络。部署这个项目的全部准备只有在你的公网服务器上安装好 docker 和 docker-composejonnyan404 已经写好了 zerotier-planet 的 docker 配置脚本，我们只需要从 gitee 上 clone 下来即可git clone https://gitee.com/Jonnyan404/zerotier-planet然后进入相应文件夹cd zerotier-planet修改配置文件vi docker-compose.yml填写你的服务器的公网 ip 地址然后就可以通过 docker-compose 启动 dockerdocker-compose up -d此时可以通过 http://ip:4000 访问后台页面（记得开放服务器 4000 端口）后面的操作就和 zerotier 部署一样了替换 planet 文件此时你自建的根服务器已经可以正常运行了，但是如果你使用 zerotier-cli peers 查看虚拟局域网中的设备，你会发现你自己的根服务器是以leaf的身份运行的，而官方的根服务器依旧在设备列表中占据了 planet 的位置。虽然完全不影响使用，但是看着终归有些不爽。这时候你需要重新编译并替换局域网中所有设备的 planet 文件查看认证信息进入 docker 挂载的 zerotier-one 目录查看 identity.public 和 authtoken.secret 中记录的认证信息，记录下来编译 planet 文件下面的步骤可以在任意一台机子上完成，只需要最后编译好的 planet 文件如果不想在云服务器上编译，完全可以在自己的 linux 虚拟机中进行先下载 zerotier 源码git clone https://github.com/zerotier/ZeroTierOne进入源码目录[root@cloud zerotier-one]# ls authtoken.secret controller.d identity.public identity.secret networks.d planet zerotier-cli zerotier-idtool zerotier-one ZeroTierOne zerotier-one.pid zerotier-one.port zerotier-one.te进入目录ZeroTierOne[root@cloud zerotier-one]# cd ./ZeroTierOne [root@cloud ZeroTierOne]# ls artwork controller Dockerfile.ci java make-mac.mk one.cpp RELEASE-NOTES.md tcp-proxy zeroidc attic ...进入attic/world[root@cloud ZeroTierOne]# cd attic/world/ [root@cloud world]# ls build.sh current.c25519 mkworld mkworld.cpp planet previous.c25519 README.md world.c修改 mkworld.cpp[root@cloud ZeroTierOne]# vim mkworld.cpp找到其中的 roots.push_back，这里的四个就是默认的根服务器 将他们都注释掉，添加你自己的：将之前记下的 identity.public 中的字符串写入 Identity 中InetAddress写服务器公网ip，端口为9993 roots.push_back(World::Root()); roots.back().identity = Identity("identity.public中的字符串"); roots.back().stableEndpoints.push_back(InetAddress("公网IP/9993"));回到 world 目录下，执…

Zerotier Moon节点部署
type: item
date: 2023-04-21T08:53:00+00:00
description: 相关命令查询节点zerotier-cli peers加入网络sudo zerotier-cli join xxxxxxxxxxxxxxxx离开网络zerotier-cli leave xxxxxxxxxxx加入 moonzerotier-cli orbit xxxxxxx yyyyyyyy离开 moonzerotier-cli deorbit xxxxxxxxZerotier Moon服务器搭建zerotier的体验也符合木桶效应，直连的长板很长，走中转的短板很短，在一些时候会带来很多不好的体验。zerotier允许用户自定义中转服务器，让被迫走中转时的速度和延迟都比较让人可以接受。同时，购买的VPS通常都是BGP网络，也就是同时接入多个运营商，使用中转服务器可以一定程度上提高跨运营商组网的体验。什么是Moon服务器Moon服务器就是上面说的中转服务器，在zerotier中，每个客户端叫做LEAF，官方的节点叫做PLANET，个人的节点叫做MOON。搭建Moon服务器首先购买一台vps可以想办法开一个阿里云/腾讯云/华为云学生机（或者新用户优惠）VPS推荐安装zerotier执行官方一键安装脚本curl -s https://install.zerotier.com | sudo bash安装完成后执行zerotier-cli检查是否安装成功接着先把vps加入zerotier网络，xxxxx为zerotier的Network ID，可以在后台看到。zerotier-cli join xxxxx加入以后去后台允许一下，接着进行moon相关配置，先导出moon.jsoncd /var/lib/zerotier-one zerotier-idtool initmoon identity.public > moon.json使用nano编辑文件nano moon.json生成的文件大概是这样的，只需要修改stableEndpoints，格式为”vps的公网IP/端口号”，如果vps有v6地址也可以一起写上："stableEndpoints": ["47.xx.xx.xx/9993","2004:xxxx:xxxx::xxx/9993"]编辑完成后按ctrl+X退出并选择Y保存设置。下一步生成moon文件：zerotier-idtool genmoon moon.json输入命令后会在该目录下面生成一个类似于 000000xxxxx. moon 的文件，接着我们让这个文件生效：# 创建一个存放刚刚生成文件的目录 mkdir moons.d # 把文件移动到该目录 mv 000000xxxxx.moon moons.d/重新启动 zerotier 的服务器, 由于使用命令安装时会自动注册为服务，所以使用该命令重启:service zerotier-one restart这一步做完还需要修改防火墙，打开9993端口。如果使用的是aliyun的机器还需要在aliyun的控制台放行9993端口。客户端连接Moon服务器不管是哪个平台首先都需要知道Moon节点的id，这个id可以通过在vps执行zerotier-cli info获得。下面简称id。群辉在 Zerotier 的根目录下新建文件夹 moons.d，将上文中生成的 moon 文件复制到该目录下。或者假设已经接入了zerotier局域网。在控制面板打开ssh，连接ssh以后执行zerotier-cli orbit id id后面两个 id 输入 moon 节点的 id，要输入两次！显示 200 orbit OK 说明加入成功。（不过这种方法不推荐，因为依赖于planet根服务器，如果无法连接根服务器则无法缓存moon文件）PC 端首先下载zerotier客户端并安装，打开后输入网络ID并Join Network之后与群晖类似首先运行 services. msc, 找到服务 ZeroTier One，并且在属性内找到其对应的可执行文件路径，如 C:\ProgramData\ZeroTier\One\zerotier-one_x64.exe，在该目录下新建文件夹 moons.d，将 moon 文件复制到该目录下。或者在命令行中输入：zerotier-cli orbit id id，其中 id 为私有根服务器的 ID （不推荐，理由同上）移动端由于官方提供的 app 不支持 moon，这里使用kaaass的ZerotierFixkaaass/ZerotierFix: An unofficial Zerotier Android client patched from official client (github.com)

Git多人协作
type: item
date: 2023-04-16T11:07:00+00:00
description: 规范分支管理main 分支： 主分支，最终的、稳定的、经过测试没有 bug 的、可部署于生产环境的分支 只能由 release 和 hotfix 分支合并，任何情况下都不能直接修改代码feature/hotfix 分支： 功能模块开发分支，对应于一个特定的功能模块 该分支以 dev 分支为基线，完成开发工作后再合并到 main 分支 命名规则：feature/NAME 或 feature-NAME 热修复分支，当 main 分支部署到生产环境后发生紧急状况，需要及时处理时，该分支负责热修复，即在保证应用不下线的条件下，对 bug 进行紧急修复 该分支以 main 分支为基线，修复 bug 后，合并到 main 分支部署上线，同时也合并到 dev 分支保持最新进度 命名规则： hotfix/NAME 或 hotfix-NAMErelease 分支： 预发布分支，在发布正式版本前进行全面测试和修复 该分支以 main 分支为基线，将需要发布的新功能分支合并到该分支，并进行测试 经过测试没有问题之后，合并到 main 分支部署上线，同时也合并到 dev 分支保持最新进度 命名规则：release/NAME 或 release-NAME分支名称分支职责基线分支合并分支main主分支--feature/hotfix功能模块开发/热修复分支mainmain,releaserelease预发布分支mainmainmain 分支贯穿于整个项目的生命周期hotfix/feature、release 分支都是临时分支，分别负责热修复、功能模块开发、预发布多人协作任务划分主仓库 A：管理 main 分支，review 提交的代码，处理冲突开发者仓库 B：承担主要开发任务初始状态新建主仓库 A，开发人员 fork 仓库 A使用 git clone 将远程仓库 clone 到本地git clone https://github.com/Grosser-Kurfuerst/Cesium_Demo.git和 fork 的主仓库地址进行关联git remote add upstream https://github.com/MisakaBryant/Cesium_Demo.git//源项目地址，upstream是远程仓库别名，可以为任意名字在本地仓库中创建需要的分支，例如 feature 分支，并与远程分支绑定（可以省略绑定，push时会自动创建）git checkout -b feature origin/feature此时可以使用 git branch 查看本地分支与主仓库版本保持同步在开始新的工作前需要拉取当前分支最新的版本使用 git remote -v 命令查看本地仓库连接的远程仓库origin https://github.com/Grosser-Kurfuerst/Cesium_Demo.git (fetch) origin https://github.com/Grosser-Kurfuerst/Cesium_Demo.gitt (push) upstream https://github.com/MisakaBryant/Cesium_Demo.git (fetch) upstream https://github.com/MisakaBryant/Cesium_Demo.git (push)切换到 dev 分支，获取并合并 upstream 代码git checkout dev git pull upstream devgit pull 命令相当于 fetch 之后再 merge，与以下命令等价git fetch upstream dev git merge upstream/dev在本地完成合并之后记得push到自己的远程仓库开发功能模块在开发阶段，应当尽量避免发生冲突，同时保持 commit 历史的干净整洁，善用 git rebase 命令可以保证 commit 历史更加整洁在 main 的基础上创建 feature-measureDistance 分支，并且切换到该分支git checkout -b feature-measureDistance将该分支 push 到远程仓库，若远程仓库没有该分支则自动创建git push origin feature-measureDistance:feature-measureDistance开发完成后进行 commitgit commit -m "measureDistance finished"push 到远程仓库git push origin feature-measureDistance提交 pull request，与主仓库合并将 feature 分支合并到 main 分支，提交 pu…

Vite配置代理解决跨域问题
type: item
date: 2023-04-15T16:51:00+00:00
description: 跨域是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的，是浏览器对 JavaScript 实施的安全限制vite 跨域代理把对某一段 URL 的访问直接转换成另一个真实的后端 API 地址，这样前后端就视为使用了相同的域名、协议和端口，就避免了跨域的问题，还能避免繁琐的反复编写域名server. proxy 为开发服务器配置自定义代理规则官方示例export default defineConfig({ server: { proxy: { // 字符串简写写法 '/foo': 'http://localhost:4567', // 选项写法 '/api': { target: 'http://jsonplaceholder.typicode.com', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') }, // 正则表达式写法 '^/fallback/.*': { target: 'http://jsonplaceholder.typicode.com', changeOrigin: true, rewrite: (path) => path.replace(/^\/fallback/, '') }, // 使用 proxy 实例 '/api': { target: 'http://jsonplaceholder.typicode.com', changeOrigin: true, configure: (proxy, options) => { // proxy 是 'http-proxy' 的实例 } }, // Proxying websockets or socket.io '/socket.io': { target: 'ws://localhost:3000', ws: true } } } })选择使用 proxy 解决：server: { base: './', proxy: { '/api': { target: 'http://172.26.19.244:5000/', // 代理目标地址 ws: true, // 是否开启websocket支持 changeOrigin: true, // 是否允许跨域 rewrite: (path) => path.replace(/^\/api/, ''), //路径重写规则 } } }如果需要向 http://172.26.19.244:5000发起请求 ，则 target 就填 http://172.26.19.244:5000 ，在所有请求的地方用“/api”替换 http://172.26.19.244:5000 例如：url: "/api/3dtiles"

使用docker-nginx搭建http服务器
type: item
date: 2023-04-15T16:50:00+00:00
description: 配置 dockerdocker 中下载 latest 镜像 下载完成后在【映像】标签页找到下载好的nginx，双击创建容器 网络就使用桥接即可，设置一个未被占用的本地端口 将需要共享的文件夹挂载到 /home 目录，并且 x 需要共享的文件夹必须要有 everyone 的权限，不然到时候 web 打开是 403 错误 配置完成后运行容器修改 nginx 配置因为 docker 的终端机里没有 vim，所以选择 ssh 连到 nas 进行修改先用 docker ps 命令查看一下 nginx 容器有没有在运行 如图第一个就是把 nginx 的配置文件复制到群晖里，修改完再复制进去docker cp nginx1:/etc/nginx/conf.d/default.conf . // 复制到当前目录 vi default.conf 在对应位置添加charset utf-8; autoindex on; autoindex_exact_size off; autoindex_localtime on;然后将 location 内的 root 与 index 注释掉，添加 root /home; 复制回去 docker cp default.conf nginx-CesiumData:/etc/nginx/conf.d/default.conf 最后重启容器 docker restart nginx-CesiumData现在就可以通过nas的ip地址与对应端口访问文件了

Lecture-4 语法分析
type: item
date: 2023-04-06T15:37:00+00:00
description: 0. 引言0.1 语法结构程序设计语言源程序的构成：语法结构一个实例0.2 文法一种用于描述程序设计语言语法的表示方法，能够自然地描述程序设计语言构造的层次化语法结构 1. 语法分析器输入：词法分析器输出的此法单元序列输出：语法树表示语法分析器功能： 语法分析器的类型： 类型检查，语义分析，翻译生成中间代码等往往和语法分析过程交错完成，实践中往往和语法分析放入一个模块，图上用“前端的其余部分”表示上述活动2. 上下文无关法 (Context Free Grammar, CFG)上下文无关文法G是一个四元组G=(T,N,P,S)G=(T,N,P,S):1. T是**终结符号 Terminal**集合, 对应于词法分析器产生的词法单元 2. N是**非终结符号 Non-terminal**集合 3. P 是**产生式 Production**集合 终结符：不可以再进行推导，是不可拆分的最小元素非终结符：可以进行拆分的元素2.1 什么是上下文文法的分类上下文有关文法上下文无关文法参考2.2 推导产生式又可称为重写规则（Rewriting rule）推导即是将某个产生式的左边替换成它的右边每一步推导需要选择替换哪个非终结符号，以及使用哪个产生式 推导的一般性定义 2.2.1 句型/句子/语言2.2.2 推导的问题推导：从上层视角判断输入内容是否符合文法从推导的角度看，语法分析的任务：推导过程中可能遇到两个问题：每一步替换哪个非终结符号？若以这个非终结符号为头的产生式有多个，用哪个产生式的右部替换？非终结符号的替换顺序2.2.3 语法分析树推导的图形表示形式 有时允许树根不是开始符号（对应于某个短语）树的叶子组成的序列四根的文法符号的句型 2.2.4 推导和语法树对于推导中的每一个句型α1，都能构造出一个以α1 为结果的语法树2.3 二义性 程序设计语言的文法通常都应该是无二义性的但有些二义性的情况可以方便文法或语法分析器的设计2.4 文法及其生成的语言语言是由闻法得开始符号出发，能够推导得到的所有句子的集合 2.5 上下文无关文法和正则表达式上下文无关文法的表达能力更强每个正则表达式都可以用一个上下文无关文法来描述，反之不成立每个正则语言都是一个上下文无关语言， 反之不成立 3. 文法的设计在进行高效的语法分析之前，需要对文法做以下处理： 3.1 消除二义性二义性的例子： 一些二义性文法可以被改成等价的无二义性的文法 二义性的消除方法没有规律可循通常并不是通过改变文法来消除二义性3.2 消除左递归左递归的定义： 3.2.1 立即左递归的消除示例：3.2.2 消除多步左递归消除立即左递归的方法并不能消除因为两步或多步推导而产生的左递归 消除算法不能含有回路（P→P）不能含有ε为右部的产生式 内层循环的每一次迭代保证了Ai的产生式的右部首符号都比Aj更加靠后当内层循环结束时，Ai的产生式的右部首符号不先于Ai消除立即左递归就保证了Ai的产生式的右部首符号必然比Ai后。（不能有环和ε产生式）当外层循环结束时，所有的产生式都是如此。使用这些产生式当然不会产生左递归的情形示例：3.3 提取左公因子在推导的时候，不知道该如何选择 方法：例子：4. 自顶向下分析技术自顶向下分析可以被看作是为输入串构造语法分析树的问题，也可以看作一个寻找输入串的最左推导的过程最左推导：总是选择每个句型的最左非终结符进行替换问题：对于非终结符号 A，选择哪一个产生式 根据输入流中的下一个终结符，选择最左非终结符4.1 预测分析法处理 LL 文法时需要考虑是否有左递归、是否需要提取公因子预测分析技术：一种确定的、无回溯的分析技术，在每一步选择正确的产生式 比递归下降分析效率更高、准确性更高 可以向前选择多个产生式一般情况下只需要向前一个符号 LL (1)提取左公因子后不会有两个相同的符号LL (2)、LL (n) 效率会降低很多4.1.1 First 和 Follow 定义 First 和 Follow： 4.1.1.1 First函数First 函数的意义： First函数的计算：对于文法符号 X 的 FIRST (X)，通过不断应用下列规则，直到没有新的终结符号或者ε可以加入到任何 FIRST 集合为止 所有的 First (x) 中都有ε时才将ε加入 First (x1x2x3... xn) 中示例： 4.1.1.2 Follow 函数Follow 的计算： 示例：4.2 LL (1) 文法LL (1) 文法，第一个 L 表示自左向右扫描，第二个 L 指产生最左推导，而 1 则表示向前看一个输入符号 对于 LL (1) 文法，可以在自顶向下分析过程中，根据当前输入符号来确定使用的产生式 4.3 LL (1) 文法的预测分析技术4.3.1 预测分析程序构成 总控程序…

Lecture-3 ANTLR
type: item
date: 2023-04-06T15:35:00+00:00
description: 1. 语言的识别与表达 ANTLR通过 RE 描述一个网页可能会崩溃——编码代价过大，且容易出错1.1 ANTLRANTLR (ANother Tool for Language Recognition)一个结构化文本的处理工具一个语言翻译器自动化的生成 lexer 和 parserANTLR 的优势1.2 ANTLR 能够做什么帮我们基于语法定义，自动化的生成一个 parser使用自上而下的方式如何从”文本“得到”结构化的表示“（Syntax Tree） Parser 生成的是语法结构树，而不是抽象语法树1.2 ANTLR 的安装ANTLR 分为两部分：工具：用于生成lexer和parser运行时 (Runtime)：用于支持 ANTLR 的运行 1.3 ANTLR 的使用1.3.1 lexer 的使用1.3.2 g4 文件的通常编写规则keyword 写在前面1.3.3 语法的定义与构建以 Java 为例自顶向下的方法：优点：具有一定的理论知识适用情况：目标语言的结构清晰准确自底向上的方法：先定义最基础的元素优点：从元素到结构，可以真正意义上从头到尾设计一个语言适用情况：目标语言需求不是特别清晰1.3.4 ANTLR 使用的语言定义需要有一种语言用以描述”目标语言“的词法与语法特征——元语言 meta language不光是 anltr，runtime 部分也需要使用 g4