util.go
Go
1package util
2
3import (
4 "fmt"
5 "os"
6 "path/filepath"
7 "runtime"
8 "strings"
9
10 "github.com/xplshn/gbc/pkg/config"
11 "github.com/xplshn/gbc/pkg/token"
12)
13
14const (
15 colorRed = "\033[31m"
16 colorYellow = "\033[33m"
17 colorReset = "\033[0m"
18 colorGray = "\033[90m"
19 colorBoldGray = "\033[1;90m"
20 formatItalic = "\033[3m"
21)
22
23type SourceFileRecord struct { Name string; Content []rune }
24
25var sourceFiles []SourceFileRecord
26
27func SetSourceFiles(files []SourceFileRecord) { sourceFiles = files }
28
29func findFileAndLine(tok token.Token) (string, int, int) {
30 if tok.FileIndex < 0 || tok.FileIndex >= len(sourceFiles) {
31 return "<unknown>", tok.Line, tok.Column
32 }
33 return filepath.Base(sourceFiles[tok.FileIndex].Name), tok.Line, tok.Column
34}
35
36func callerFile(skip int) string {
37 _, file, _, ok := runtime.Caller(skip)
38 if !ok { return "<unknown>" }
39 return filepath.Base(file)
40}
41
42func printSourceContext(stream *os.File, tok token.Token, isError bool, msg, caller string) {
43 if tok.FileIndex < 0 || tok.FileIndex >= len(sourceFiles) || tok.Line <= 0 {
44 return
45 }
46
47 content := sourceFiles[tok.FileIndex].Content
48 lines := strings.Split(string(content), "\n")
49
50 start := tok.Line - 2
51 if start < 0 {
52 start = 0
53 }
54 end := tok.Line + 1
55 if end > len(lines) {
56 end = len(lines)
57 }
58 lineNumWidth := len(fmt.Sprintf("%d", end))
59 linePrefix := strings.Repeat(" ", 3)
60
61 for i := start; i < end; i++ {
62 lineNum := i + 1
63 line := strings.ReplaceAll(lines[i], "\t", " ")
64 isErrorLine := lineNum == tok.Line
65
66 var gutter string
67 if isErrorLine {
68 gutter = boldGray(fmt.Sprintf("%s%*d | ", linePrefix, lineNumWidth, lineNum))
69 } else {
70 gutter = gray(fmt.Sprintf("%s%*d | ", linePrefix, lineNumWidth, lineNum))
71 }
72
73 fmt.Fprintf(stream, " %s%s\n", gutter, line)
74
75 if isErrorLine {
76 colPos := caretColumn(line, tok.Column)
77 caretLine := strings.Repeat(" ", colPos-1) + "^"
78 if tok.Len > 1 {
79 caretLine += strings.Repeat("~", tok.Len-1)
80 }
81
82 caretGutter := boldGray(strings.Repeat("-", lineNumWidth) + " | ")
83 var caretColored, msgColored, callerColored string
84 if isError {
85 caretColored, msgColored = red(caretLine), italic(msg)
86 } else {
87 caretColored, msgColored = yellow(caretLine), italic(msg)
88 }
89 callerColored = italic(gray(fmt.Sprintf("(emitted from %s)", boldGray(caller))))
90 fmt.Fprintf(stream, " %s%s%s %s %s%s\n", linePrefix, caretGutter, caretColored, msgColored, callerColored, colorReset)
91 }
92 }
93 fmt.Fprintln(stream)
94}
95
96func caretColumn(line string, col int) int {
97 if col < 1 {
98 col = 1
99 }
100 runes := []rune(line)
101 pos := 0
102 for i := 0; i < col-1 && i < len(runes); i++ {
103 if runes[i] == '\t' {
104 pos += 4
105 } else {
106 pos++
107 }
108 }
109 return pos + 1
110}
111
112func italic(s string) string { return formatItalic + s + colorReset }
113func gray(s string) string { return colorGray + s + colorReset }
114func boldGray(s string) string { return colorBoldGray + s + colorReset }
115func red(s string) string { return colorRed + s + colorReset }
116func yellow(s string) string { return colorYellow + s + colorReset }
117
118func Error(tok token.Token, format string, args ...interface{}) {
119 msg := fmt.Sprintf(format, args...)
120 if tok.FileIndex < 0 || tok.FileIndex >= len(sourceFiles) || tok.Line <= 0 {
121 fmt.Fprintf(os.Stderr, "gbc: %serror:%s %s\n", colorRed, colorReset, msg)
122 os.Exit(1)
123 }
124
125 filename, line, col := findFileAndLine(tok)
126 caller := callerFile(2)
127
128 fmt.Fprintf(os.Stderr, "%s:%d:%d: %serror%s:\n", filename, line, col, colorRed, colorReset)
129 printSourceContext(os.Stderr, tok, true, msg, caller)
130 os.Exit(1)
131}
132
133func Warn(cfg *config.Config, wt config.Warning, tok token.Token, format string, args ...interface{}) {
134 if !cfg.IsWarningEnabled(wt) {
135 return
136 }
137 msg := fmt.Sprintf(format, args...) + fmt.Sprintf(" [-W%s]", cfg.Warnings[wt].Name)
138
139 if tok.FileIndex < 0 || tok.FileIndex >= len(sourceFiles) || tok.Line <= 0 {
140 fmt.Fprintf(os.Stderr, "gbc: %swarning:%s %s\n", colorYellow, colorReset, msg)
141 return
142 }
143
144 filename, line, col := findFileAndLine(tok)
145 caller := callerFile(2)
146
147 fmt.Fprintf(os.Stderr, "%s:%d:%d: %swarning%s:\n", filename, line, col, colorYellow, colorReset)
148 printSourceContext(os.Stderr, tok, false, msg, caller)
149}
150
151// AlignUp rounds n up to the next multiple of a
152// a must be a power of 2
153func AlignUp(n, a int64) int64 {
154 if a == 0 {
155 return n
156 }
157 return (n + a - 1) &^ (a - 1)
158}