Golang编译器DIY,手搓 if err != nil { return err } 语法糖

server/2025/3/16 0:00:07/

前序

在go的社区里,下面这三行代码是被吐槽的最多的

if err != nil {return err
}

从代码之整洁美观的角度看,这样的写法也是让人不舒服的。尤其是 当有很多错误需要处理的时候,就会发现通篇都是这三行。
所以想着看看修改一下编译器,优化这一写法。
预期达到的效果如下:

func  Aaa(info string) (err error) {err = NewError(info)?//todo something
}

它等价于下面这段函数

func  Aaa(info string) (err error) {err = NewError(info)if err != nil {return}//todo something
}

思路

go的编译过程大概分为这么几个阶段:

  1. 扫描解析源文件
  2. 类型检查和AST生成
  3. 生成SSA中间代码,并进行一定优化
  4. 生成机器码

基于这个过程,我们的"?"号语法糖,只需要在编译器解析源码的时候,将?号扩展为if err != nil { return } 即可

拉取go的源代码

在正式开始开工之前,需要在本地先安装一个go的执行环境,并且尽量用最新的版本。然后clone go的源码包。
下面我们所有的操作,都相对于这个目录来完成。

git clone https://github.com/golang/go.git

注意,你本地已经安装的go的版本,尽量只比你要编译的版本小一个版本号。
比如我这里用go version go1.21.11 darwin/amd64编译go version go1.22.0 darwin/amd64

代码拉下来后,可以先编译一下,确定默认编译不会出问题。

  • all.bash 编译完成后,会自动进行测试,时间还是比较长的。并且给了多系统的命令文件 all.bat all.rc
  • make.bash 仅编译,我的电脑大约20s就能编译完成。
cd go/src//编译并测试
./all.bash//仅编译
./make.bash

编译完成后,可以在bin目录下看到go文件,运行下面的命令,打印版本号,表示编译成功。

  • 为了和本地环境区分开,需要指定GOROOT为我们下载的go源码的文件夹路径。
 GOROOT=<go path> bin/go version//输出:go version go1.22.0 darwin/amd64

增加 ? 标识符 和 具体语法节点

首先增加 ?号标识符,在syntax目录下,这个目录的主要功能就是做scanparser

  • 路径:src/cmd/compile/internal/syntax/tokens.go
  • 在token里面增加一个_RetErr用来表示?号。注释必须按照下面的格式写,自动生成需要。
 // go:generate stringer -type token -linecomment tokens.goconst (_    token = iota..._Semi      // ;_RetErr    // ?_Colon     // :...   
)

上边的注释 // go:generate stringer -type token -linecomment tokens.go表示我们需要go generate一下。

  • stringer 较新的版本中这个包是内置的,通常不需要安装,如果提示不存在,则手动安装一下。
//syntax目录下执行go generate tokens.go

查看token_string.go文件,如下图,将我们新加的token也生成上去了。
在这里插入图片描述

我们还需要创建一个?号对应的具体语法树的节点,因为我们这里做的是一个语法糖,所以直接组合一下if对应的IfStmt结构就可以了,如下:

IfStmt struct {Init SimpleStmtCond ExprThen *BlockStmtElse Stmt // either nil, *IfStmt, or *BlockStmt
stmt
}RetErrStmt struct {IfStmt
}

简单介绍一下if结构的几个字段的作用:

  • Init:在执行if条件判断前的代码块,如:if _,ok:=get();ok{ todo }中,_,ok:=get()部分就是Init,它可以为空。
  • Cond:判断条件,
  • Then:条件为真时,执行这个代码。
  • Else:条件为假时,执行的代码,可以为空。

语法解析

go的源码解析主要有两个结构体:

  • scanner :负责按照token维度扫描源文件
  • parser:将扫描的token组装成具体语法树

我们先增加对于?号标识符的扫描识别,src/cmd/compile/internal/syntax/scanner.go

func (s *scanner) next() {
...
case ';':s.nextch()s.lit = "semicolon"s.tok = _Semicase '?':s.nextch()s.tok = _RetErr
...
}

然后增加对?的语法解析,我们参考IfStmt的解析方法:

  • 路径:src/cmd/compile/internal/syntax/parser.go
  • stmtOrNil : 在这个函数中增加标识符的解析方法
  • retErr :实际上我们增加了一个IfStmt的语法糖。
func (p *parser) stmtOrNil() Stmt {
...
case _RetErr:return p.retErr()case _If:return p.ifStmt()
...
}func (p *parser) retErr() *RetErrStmt {if trace {defer p.trace("ifStmt")()}//判断条件:err != nilcondExpr := &Operation{X:  &Name{Value: "err"},Op: Neq,Y:  &Name{Value: "nil"},}condExpr.pos = p.pos()// 表示 Then 块: returnthenBlock := &BlockStmt{List: []Stmt{&ReturnStmt{//Results: &Name{Value: "err"},},},Rbrace: p.pos(),}//组装RetErrStmts := new(RetErrStmt)s.pos = p.pos()s.Cond = condExprs.Then = thenBlock//继续向下扫描p.next()//如果存在else则继续解析if p.got(_Else) {switch p.tok {case _If:s.Else = p.ifStmt()case _Lbrace:s.Else = p.blockStmt("")default:p.syntaxError("else must be followed by if or statement block")p.advance(_Name, _Rbrace)}}return s
}

边界判断

go对写法有比较严格的要求,尤其是换行和边际的判断,所以我们需要告诉编译器,?号不需要处理边际,因为已经解析成了其他的结构。

同样在parser.go文件中的stmtList函数:

func (p *parser) stmtList() (l []Stmt) {if trace {defer p.trace("stmtList")()}for p.tok != _EOF && p.tok != _Rbrace && p.tok != _Case && p.tok != _Default {s := p.stmtOrNil()p.clearPragma()if s == nil {break}l = append(l, s)//跳过RetErrStmt的检查if _, ok := s.(*RetErrStmt); ok {continue}//?;} 都属于正常的边界。if !p.got(_Semi) && p.tok != _RetErr && p.tok != _Rbrace {p.syntaxError("at end of statement")p.advance(_Semi, _Rbrace, _Case, _Default)p.got(_Semi) // avoid spurious empty statement}}return
}

至此,一个初步的 ? 号语法糖已经制作完成,用make.base重新编译项目。

测试

我么在这个go目录下,新建一个测试文件reterr.go,内容如下:

  • 因为源码中我们处理else的情况,所有理论上?else{}?else if xx {}也同样是支持的。
  • EasyRetError:简单返回错误,可以看到代码量明显减少
  • IfElseRetError和MulIfElseRetError:可以继续做else判断,并且不影响返回多个值
package mainimport ("errors""fmt"
)func NewError(text string) error {if text == "" {return nil} else {return errors.New(text)}
}
func EasyRetError(info string) (err error) {err = NewError(info)?return
}
func IfElseRetError(info string)(str string, err error){err = NewError(info)?else{return info,nil}
}
func MulIfElseRetError(info string,ok bool)(str string, err error){err = NewError(info)?else if ok{return "test",nil}else{return "success",nil}
}

在main函数中,我们写一下预期的结果,不符合预期则 panic。

func main() {var err errorvar info = ""//简单情况测试if err = EasyRetError("Err");err == nil {panic("EasyRetError.Err")}if err = EasyRetError("");err != nil {panic("EasyRetError.nil")}//if else 分支情况测试if info,err = IfElseRetError(info); err != nil || info != "" {panic("IfElseRetError.err not nil")}info = "test"if info,err = IfElseRetError(info); err == nil || info != "" {panic("IfElseRetError.info = test")}// 多分支测试info = "test"if _,err = MulIfElseRetError(info,true); err == nil {panic("MulIfElseRetError.err not nil")}if info,err = MulIfElseRetError("",true); err != nil || info != "test" {panic("MulIfElseRetError.test")}if info,err = MulIfElseRetError("",false); err != nil || info != "success" {panic("MulIfElseRetError.success")}fmt.Println("success")
}

用我们重新编译的go,运行上边的代码:

GOROOT=~/project/work/github/go bin/go run reterr.go

测试成功


http://www.ppmy.cn/server/175288.html

相关文章

复原IP地址 (leetcode 93

leetcode系列 文章目录 一、核心操作二、外层配合操作三、核心模式代码总结 一、核心操作 判断字段是否有效函数&#xff1a;首先start不能大于end当到最后一个收获层的时候&#xff0c;start已经是s.size了&#xff0c;但是end还是只能是s.size-1其次当字段不止一位时&#…

VSCode C/C++环境搭建指南

VSCode C/C环境搭建指南 一、环境搭建全流程&#xff08;Windows/Linux/macOS&#xff09; 1. 编译器安装与配置&#xff08;以Windows为例&#xff09; • MinGW-w64详细安装 • 访问 MinGW-w64官网&#xff0c;选择 x86_64-posix-seh 分支&#xff08;支持C23和多线程开发…

Driver Development Kit(驱动开发服务)

文章目录 一、Driver Development Kit 简介二、外设扩展驱动客户端开发指导一、Driver Development Kit 简介 Driver Development Kit(驱动开发套件)为外设驱动开发者提供高效、安全、丰富的外设扩展驱动开发解决方案C-API,支持外设驱动开发者为消费者带来外设即插即用的极…

JavaScript 性能优化实战指南

涵盖代码优化、内存管理、运行时效率提升等核心方向&#xff0c;通过实战代码示例分析常见性能陷阱及优化方案&#xff1a; 一、代码执行效率优化 1. 避免全局变量污染 <JAVASCRIPT> // ❌ 低效&#xff1a;全局查找耗时长 function sum(a, b) {return a b window.ta…

‌Visual Studio Code(VS Code)支持的编程语言

‌JavaScript‌&#xff1a;VS Code 原生支持 JavaScript&#xff0c;提供语法高亮、代码折叠、自动补全等功能。推荐使用ESLint和Prettier进行代码格式化和错误检查‌。 ‌TypeScript‌&#xff1a;作为 JavaScript 的超集&#xff0c;TypeScript 在 VS Code 中也得到原生支持…

功能仿真

1、仿真原理 1.1、 串行模拟并行思路分析 串行模拟并行仿真主要分为两种情况&#xff1a;独立的并行电路 有关联的并行电路。 独立的并行电路&#xff1a;若并行的电路之间是相互独立的&#xff0c;同时开始多件事情和逐个执行是完全一样的。 有关联的并行电路运行&am…

Tomcat+Servlet运行后出现404错误解决方案

TomcatServlet运行后出现404错误解决方案 一、错误效果复现 后续的解决方案&#xff0c;仅仅针对我遇到的情况。对不能涵盖大部分情况感到抱歉。 二、错误分析 先看看源代码&#xff1f; package com.example.secondclass.Servlet; import java.io.*; import jakarta.servl…

Pycharm实用技巧

一、Pycharm 参数注释 在 PyCharm 中&#xff0c;在方法下输入三引号&#xff08;"""&#xff09;就能自动生成参数注释&#xff1a;def input_combo_detail(self, scale, ptype_data, ptype_info, sku_info, unit_info, price, qty):""":param…