~mna/snow

ref: 89925d5a5697fa80aaf2c7eb2ac78833a61b7436 snow/pkg/codegen/codegen.go -rw-r--r-- 3.7 KiB
89925d5aMartin Angers pkg/codegen: start building the Go AST 1 year, 10 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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{
		nodeToGoAST: t.nodeToGoAST,
	}
	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()
}