package main import ( "context" "errors" "fmt" "io/fs" "os" "os/exec" "path/filepath" "runtime/debug" "strings" "time" "gitee.com/u-language/u-language/ucom/ast" "gitee.com/u-language/u-language/ucom/ast2" "gitee.com/u-language/u-language/ucom/cast" "gitee.com/u-language/u-language/ucom/cast2" "gitee.com/u-language/u-language/ucom/config" "gitee.com/u-language/u-language/ucom/errcode" "gitee.com/u-language/u-language/ucom/format" idebug "gitee.com/u-language/u-language/ucom/internal/debug" "gitee.com/u-language/u-language/ucom/internal/errutil" "gitee.com/u-language/u-language/ucom/internal/utils" "gitee.com/u-language/u-language/ucom/ir2" "gitee.com/u-language/u-language/ucom/parser" "github.com/spf13/cobra" ) var ( build string //编译地址 mode string //模式 out string //输出文件名 Thread bool = true cok int err error buildmode int // if ==1 lex -> ast , if==2 lex2 -> ast2 buildCmd = &cobra.Command{ Use: "build", Short: "编译源文件地址", Long: "编译源文件地址", Run: func(cmd *cobra.Command, args []string) { if len(args) != 1 { cmd.PrintErr("build 支持处理的不是一个源文件就是一个目录下的所有.u源文件") os.Exit(2) } build = args[0] defer Recover() Build(build, out, buildmode) }, } donectx context.Context cancelfunc context.CancelFunc rootcmd = &cobra.Command{ Use: "", Short: "U语言编译器", } build2Cmd = &cobra.Command{ Use: "build2", Short: "编译源文件地址", Long: "编译源文件地址", Run: func(cmd *cobra.Command, args []string) { if len(args) != 1 { cmd.PrintErr("build 支持处理的不是一个源文件就是一个目录下的所有.u源文件") os.Exit(2) } build = args[0] defer Recover() Build2(build, out) }, } ) func init() { donectx, cancelfunc = context.WithCancel(context.Background()) out, err = filepath.Abs(out) utils.MustErr(err) rootcmd.AddCommand(buildCmd) rootcmd.AddCommand(versionCmd) rootcmd.AddCommand(fmtCmd) rootcmd.AddCommand(build2Cmd) buildFlag(buildCmd) buildFlag(build2Cmd) //报错 go errcode.Handle(donectx, func(err errcode.ErrInfo) { fmt.Println(err.String()) }, func() { os.Exit(2) }) buildCmd.Flags().IntVar(&buildmode, "buildmode", 1, "设置编译模式") } func buildFlag(c *cobra.Command) { c.Flags().BoolVarP(&Thread, "p", "p", true, "是否使用多核") c.Flags().StringVarP(&mode, "mode", "m", "c", `目标文件类型 c : c语言文件 fasm : fasm汇编文件(实验性的,目前暂停开发)`) c.Flags().StringVar(&out, "o", "a"+utils.Ext, "输出文件名") c.Flags().CountVarP(&cok, "c", "c", "是否只输出C文件") } func main() { defer Recover() if idebug.Debug { //调试时操作 fmt.Println("Thread:", Thread) idebug.TraceStart() idebug.PprofStart() } err = rootcmd.Execute() if err != nil { rootcmd.Help() os.Exit(2) return } } // SaveCFile 将C抽象语法树表示的C源代码保存到文件中,并调用C工具链编译 func SaveCFile(toc *cast.UtoC, file string, outfile string) bool { tmp, err := os.MkdirTemp("", "c*") utils.MustErr(err) file = filepath.Join(tmp, file) fd, err := os.Create(file) utils.MustErr(err) _, err = fd.WriteString(toc.C()) utils.MustErr(err) fd.Close() clist := append([]string(nil), file) if toc.InAutoFree { clist = append(clist, utils.StdOne["mempool"].CFile...) } if _CCBuild(tmp, clist, outfile) { //调用C工具链生成可执行文件 utils.MustErr(os.RemoveAll(tmp)) return true } fmt.Println("C文件在", tmp) return false } // _CCBuild 调用C工具链将file编译为 func _CCBuild(tmp string, clist []string, outfile string) bool { if cok != 0 { fmt.Println("C文件在", tmp) os.Exit(0) } CCall := strings.Split(config.CC, "||") for _, cc := range CCall { arg := append([]string(nil), clist...) arg = append(arg, "-lpthread", "-o", filepath.Join(tmp, "a"+utils.Ext), "-I") arg = append(arg, utils.StdOne["mempool"].HDir...) if Exec(cc, arg) { utils.MustErr(utils.MoveFile(filepath.Join(tmp, "a"+utils.Ext), outfile)) return true } } fmt.Println("尝试从所有c工具链生成二进制文件都失败了,没有删除c源文件") return false } var outputPrint = false func Exec(path string, args []string) bool { cmd := exec.Command(path) cmd.Args = append(cmd.Args, args...) str, err := cmd.CombinedOutput() if outputPrint && err != nil { fmt.Println(string(str)) } if err == nil { return true } else { var exiterr *exec.ExitError if errors.As(err, &exiterr) { fmt.Printf("调用C工具链 %s 生成二进制失败,退出值=%d\n", path, exiterr.ExitCode()) } else if errors.Is(err, exec.ErrNotFound) { fmt.Printf("C工具链 %s 没找到\n", path) } else if errors.Is(err, fs.ErrNotExist) { fmt.Printf("C工具链 %s 没找到\n", path) } } return false } func FindC(dir string) []string { var ret = make([]string, 0) err := filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error { if err != nil { return err } if info.IsDir() && path != dir { return filepath.SkipDir } if filepath.Ext(info.Name()) == ".c" { ret = append(ret, path) } return nil }) utils.MustErr(err) return ret } func Recover() { if idebug.Debug { //调试时操作 idebug.PprofClose() idebug.TraceClose() } if err := recover(); err != nil { if errv, ok := err.(error); ok { switch { case errors.Is(errv, os.ErrNotExist): //如果文件不存在 fmt.Println(build, "不存在") os.Exit(2) case errutil.IsCompileErr(errv): //如果是编译器错误 debug.PrintStack() fallthrough case errors.Is(errv, parser.NoUFileErr), errors.Is(errv, parser.ImportLimit), errors.Is(errv, parser.FileNoExt), errors.Is(errv, parser.PathNotFound): //如果是预定义错误 fmt.Println(errv.Error()) os.Exit(2) case errors.Is(errv, format.ErrorAfterLex): cancelfunc() return } } if idebug.Debug { fmt.Println(err) debug.PrintStack() } else { fmt.Println("发生不能识别的错误") } os.Exit(2) } } // SaveCFileBuildMode2 将C语言转换器表示的C源代码保存到文件中,并调用C工具链编译 func SaveCFileBuildMode2(toc *cast2.UtoC, file string, outfile string) bool { tmp, err := os.MkdirTemp("", "c*") utils.MustErr(err) file = filepath.Join(tmp, file) fd, err := os.Create(file) utils.MustErr(err) _, err = fd.WriteString(toc.C(true)) utils.MustErr(err) fd.Close() clist := append([]string(nil), file) // if toc.InAutoFree { // clist = append(clist, utils.StdOne["mempool"].CFile...) // } if _CCBuild(tmp, clist, outfile) { //调用C工具链生成可执行文件 utils.MustErr(os.RemoveAll(tmp)) return true } fmt.Println("C文件在", tmp) return false } // Build 将U代码构建 // - path是U代码路径 // - out是输出路径 // - buildmode是编译模式 func Build(path string, out string, buildmode int) { if buildmode == 2 { tree, err := parser.ParserAutoBuildMode2(path, Thread, errcode.DefaultErrCtx) utils.MustErr(err) if errcode.Errbol() { //代码有错误 cancelfunc() time.Sleep(1000 * time.Second) //代码有错误不应继续执行 } if tree == nil { //如果是空文件 return } switch mode { case "c": //c模式 switch tree := tree.(type) { case *ast2.Tree: toc := &cast2.UtoC{} toc.Parser(tree) //转换为C抽象语法树 cancelfunc() if idebug.Debug && config.Debug_PrintCast == "on" { //调试时操作 fmt.Println(toc.C(true)) } else { SaveCFileBuildMode2(toc, "out.c", out) } case *ast2.Package: p := cast2.NewPackage(Thread, tree, true) if idebug.Debug && config.Debug_PrintCast == "on" { //调试时操作 fmt.Printf("%+v\n", p) } else { tmp, err := os.MkdirTemp("", "c*") utils.MustErr(err) paths, err := p.OupputC(tmp) clist := []string{paths} utils.MustErr(err) // if p.WithAutoFree() { // paths = append(paths, utils.StdOne["mempool"].CFile...) // } if _CCBuild(tmp, clist, out) { utils.MustErr(os.RemoveAll(tmp)) } else { fmt.Println("C文件在", tmp) } } } case "fasm": //fasm模式 panic(errutil.NewErr2(errutil.CompileErr, "build --buildmode=2 不支持fasm")) } return } //解析获得抽象语法树,并进行语义检查 iface, err := parser.ParserAuto(path, Thread, errcode.DefaultErrCtx) utils.MustErr(err) if errcode.Errbol() { //代码有错误 cancelfunc() time.Sleep(1000 * time.Second) //代码有错误不应继续执行 } if utils.IsNil(iface) { //如果是空文件 return } cancelfunc() switch mode { case "c": //c模式 switch iface.(type) { case *ast.Tree: toc := cast.NewUtoC() ast := iface.(*ast.Tree) toc.Parser(ast, false) //转换为C抽象语法树 if idebug.Debug && config.Debug_PrintCast == "on" { //调试时操作 fmt.Printf("%+v\n", toc.C()) } else { SaveCFile(toc, "out.c", out) } case *ast.Package: p := cast.NewPackage(Thread) astp := iface.(*ast.Package) p.AddUastSlice(astp, nil) if idebug.Debug && config.Debug_PrintCast == "on" { //调试时操作 fmt.Printf("%+v\n", p) } else { tmp, err := os.MkdirTemp("", "c*") utils.MustErr(err) paths, err := p.OupputC(tmp) utils.MustErr(err) if p.WithAutoFree() { paths = append(paths, utils.StdOne["mempool"].CFile...) } if _CCBuild(tmp, paths, out) { utils.MustErr(os.RemoveAll(tmp)) } else { fmt.Println("C文件在", tmp) } } } case "fasm": //fasm模式 CallAmd64(iface.(*ast.Tree)) } } // Build2 将U代码构建,使用ir2 // - path是U代码路径 // - out是输出路径 func Build2(path string, out string) { //解析获得ir,并进行语义检查 result, err := parser.ParserAuto2(path, Thread, errcode.DefaultErrCtx) if err != nil { if err.Error() != "11" { //如果是未知的错误 cancelfunc() panic(err) } else { //如果是代码解析完抽象语法树发现有错误 cancelfunc() return } } cancelfunc() switch mode { case "c": switch result.(type) { case *ir2.File: f := result.(*ir2.File) toc := cast.NewIr2ToC(f.PackageName, f.HaveInitFunc) cast.PaeserIr2(toc, f) //转换为C抽象语法树 if idebug.Debug && config.Debug_PrintCast == "on" { //调试时操作 fmt.Printf("%+v\n", toc.C()) } else { SaveCFile2(toc, "out.c", out) } } case "fasm": //fasm模式 panic(errutil.NewErr2(errutil.CompileErr, "build2不支持fasm")) } } // SaveCFile 将C抽象语法树表示的C源代码保存到文件中,并调用C工具链编译 func SaveCFile2(toc *cast.Ir2ToC, file string, outfile string) bool { tmp, err := os.MkdirTemp("", "c*") utils.MustErr(err) file = filepath.Join(tmp, file) fd, err := os.Create(file) utils.MustErr(err) _, err = fd.WriteString(toc.C()) utils.MustErr(err) fd.Close() clist := append([]string(nil), file) // if toc.InAutoFree { // clist = append(clist, utils.StdOne["mempool"].CFile...) // } if _CCBuild(tmp, clist, outfile) { //调用C工具链生成可执行文件 utils.MustErr(os.RemoveAll(tmp)) return true } fmt.Println("C文件在", tmp) return false }