repos / gbc

GBC - Go B Compiler
git clone https://github.com/xplshn/gbc.git

gbc / pkg / util
xplshn  ·  2025-09-10

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}