package codegen import ( "fmt" goast "go/ast" "go/scanner" "go/token" "git.sr.ht/~mna/snow/pkg/ast" "git.sr.ht/~mna/snow/pkg/semantic" ) // Exec parses and generates code for the provided Snow source files. // Generated files are located in the specified output directory. // // It returns the list of corresponding generated files. The error, if non-nil, // is guaranteed to be a scanner.ErrorList. func Exec(outDir string, files ...string) ([]string, error) { if len(files) == 0 { return nil, nil } // parse and type-check all files in a single compilation unit unit, err := semantic.Run(semantic.AllPasses, files...) if err != nil { return nil, err } return ExecUnit(outDir, unit) } // ExecAST generates code from already parsed files for the provided AST files. // Generated files are located in the specified output directory. // // It returns the list of corresponding generated files. The error, if non-nil, // is guaranteed to be a scanner.ErrorList. func ExecAST(outDir string, fset *token.FileSet, files ...*ast.File) ([]string, error) { if len(files) == 0 { return nil, nil } // type-check all files in a single compilation unit unit, err := semantic.RunAST(semantic.AllPasses, fset, files...) if err != nil { return nil, err } return ExecUnit(outDir, unit) } // ExecUnit generates code from already typechecked files for the provided unit. // Generated files are located in the specified output directory. // // It returns the list of corresponding generated files. The error, if non-nil, // is guaranteed to be a scanner.ErrorList. func ExecUnit(outDir string, unit *semantic.Unit) ([]string, error) { // Code generation works in steps: // 1. do name mangling of all declarations that require it // 2. extract all external imports required per file // 3. generate the in-memory, position-less Go AST nodes, associated to Snow nodes // 4. build the Go AST tree from the Snow-associated nodes so that relative // position of nodes respects the snow source // 5. walk the Go AST and assign valid positions to nodes // 6. use the go/printer package to generate Go code from the AST // 1. do name mangling of all declarations that require it resolver := mangle(unit) // 2. extract all external imports required per file fileImps := imports(unit) // 3. generate the in-memory, position-less Go AST nodes, associated to Snow nodes fnImps := make(map[*semantic.Fn]_import) for _, imps := range fileImps { for _, imp := range imps { fnImps[imp.AppliedTo] = imp } } t := &translator{ resolver: resolver, fileImports: fileImps, fnImports: fnImps, } semantic.Walk(t, unit) // 4. build the Go AST tree from the Snow-associated nodes so that relative // position of nodes respects the snow source tb := &treebuilder{ filesList: t.filesList, } semantic.Walk(tb, unit) gofiles := tb.gofiles // 5. walk the Go AST and assign valid positions to nodes fset := token.NewFileSet() for i, f := range unit.Files { pos := &positioner{ base: fset.Base(), } goast.Walk(pos, gofiles[i]) // offset + 1 because EOF has a position too filename := unit.FileSet.File(f.Pos()).Name() fpos := fset.AddFile(filename, -1, pos.offset+1) if !fpos.SetLines(pos.lines) { panic(fmt.Sprintf("SetLines failed for %s; lines: %v", filename, pos.lines)) } } // 6. use the go/printer package to generate Go code from the AST var el scanner.ErrorList w := &writer{dir: outDir, fset: fset} outFiles := make([]string, len(gofiles)) for i, f := range gofiles { filename := fset.File(f.Pos()).Name() outFile, err := w.writeFile(filename, f) if err != nil { el.Add(token.Position{Filename: filename}, err.Error()) } outFiles[i] = outFile } return outFiles, el.Err() }