Improved error handling

This commit is contained in:
Eduard Urbach 2024-06-15 18:42:31 +02:00
parent 57f1da10fe
commit 4776b4c14c
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
7 changed files with 71 additions and 60 deletions

View File

@ -7,14 +7,16 @@ import (
"git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/arch/x64"
"git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/config"
"git.akyoto.dev/cli/q/src/build/fs"
"git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/build/token"
"git.akyoto.dev/cli/q/src/errors"
"git.akyoto.dev/go/color/ansi" "git.akyoto.dev/go/color/ansi"
) )
// Function represents a function. // Function represents a function.
type Function struct { type Function struct {
Name string Name string
File *File File *fs.File
Head token.List Head token.List
Body token.List Body token.List
Variables map[string]*Variable Variables map[string]*Variable
@ -74,19 +76,24 @@ func (f *Function) Compile() {
f.Assembler.Return() f.Assembler.Return()
if config.Verbose { if config.Verbose {
fmt.Println() f.PrintAsm()
ansi.Bold.Println(f.Name + ".asm") }
}
for _, x := range f.Assembler.Instructions { // PrintAsm shows the assembly instructions.
ansi.Dim.Print("│ ") func (f *Function) PrintAsm() {
fmt.Print(x.Mnemonic.String()) fmt.Println()
ansi.Bold.Println(f.Name + ".asm")
if x.Data != nil { for _, x := range f.Assembler.Instructions {
fmt.Print(" " + x.Data.String()) ansi.Dim.Print("│ ")
} fmt.Print(x.Mnemonic.String())
fmt.Print("\n") if x.Data != nil {
fmt.Print(" " + x.Data.String())
} }
fmt.Print("\n")
} }
} }
@ -116,8 +123,7 @@ func (f *Function) CompileInstruction(line token.List) error {
value := line[2:] value := line[2:]
if len(value) == 0 { if len(value) == 0 {
return fmt.Errorf("error to be implemented") return errors.New(errors.MissingAssignmentValue, f.File, line[1].After())
// return errors.New(errors.MissingAssignmentValue, f.File.Path, f.File.Tokens, f.Cursor)
} }
if config.Verbose { if config.Verbose {

View File

@ -6,7 +6,7 @@ import (
"strings" "strings"
"sync" "sync"
"git.akyoto.dev/cli/q/src/build/directory" "git.akyoto.dev/cli/q/src/build/fs"
"git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/build/token"
"git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/errors"
) )
@ -38,7 +38,7 @@ func scan(files []string, functions chan<- *Function, errors chan<- error) {
} }
if stat.IsDir() { if stat.IsDir() {
err = directory.Walk(file, func(name string) { err = fs.Walk(file, func(name string) {
if !strings.HasSuffix(name, ".q") { if !strings.HasSuffix(name, ".q") {
return return
} }
@ -86,7 +86,7 @@ func scanFile(path string, functions chan<- *Function) error {
tokens := token.Tokenize(contents) tokens := token.Tokenize(contents)
file := &File{ file := &fs.File{
Tokens: tokens, Tokens: tokens,
Path: path, Path: path,
} }
@ -118,7 +118,7 @@ func scanFile(path string, functions chan<- *Function) error {
return nil return nil
} }
return errors.New(errors.ExpectedFunctionName, path, tokens, i) return errors.New(errors.ExpectedFunctionName, file, tokens[i].Position)
} }
// Function parameters // Function parameters
@ -138,7 +138,7 @@ func scanFile(path string, functions chan<- *Function) error {
groupLevel-- groupLevel--
if groupLevel < 0 { if groupLevel < 0 {
return errors.New(errors.MissingGroupStart, path, tokens, i) return errors.New(errors.MissingGroupStart, file, tokens[i].Position)
} }
i++ i++
@ -152,11 +152,11 @@ func scanFile(path string, functions chan<- *Function) error {
if tokens[i].Kind == token.EOF { if tokens[i].Kind == token.EOF {
if groupLevel > 0 { if groupLevel > 0 {
return errors.New(errors.MissingGroupEnd, path, tokens, i) return errors.New(errors.MissingGroupEnd, file, tokens[i].Position)
} }
if paramsStart == -1 { if paramsStart == -1 {
return errors.New(errors.ExpectedFunctionParameters, path, tokens, i) return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position)
} }
return nil return nil
@ -167,7 +167,7 @@ func scanFile(path string, functions chan<- *Function) error {
continue continue
} }
return errors.New(errors.ExpectedFunctionParameters, path, tokens, i) return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position)
} }
// Function definition // Function definition
@ -187,7 +187,7 @@ func scanFile(path string, functions chan<- *Function) error {
blockLevel-- blockLevel--
if blockLevel < 0 { if blockLevel < 0 {
return errors.New(errors.MissingBlockStart, path, tokens, i) return errors.New(errors.MissingBlockStart, file, tokens[i].Position)
} }
i++ i++
@ -201,11 +201,11 @@ func scanFile(path string, functions chan<- *Function) error {
if tokens[i].Kind == token.EOF { if tokens[i].Kind == token.EOF {
if blockLevel > 0 { if blockLevel > 0 {
return errors.New(errors.MissingBlockEnd, path, tokens, i) return errors.New(errors.MissingBlockEnd, file, tokens[i].Position)
} }
if bodyStart == -1 { if bodyStart == -1 {
return errors.New(errors.ExpectedFunctionDefinition, path, tokens, i) return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position)
} }
return nil return nil
@ -216,7 +216,7 @@ func scanFile(path string, functions chan<- *Function) error {
continue continue
} }
return errors.New(errors.ExpectedFunctionDefinition, path, tokens, i) return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position)
} }
functions <- &Function{ functions <- &Function{

View File

@ -1,4 +1,4 @@
package build package fs
import "git.akyoto.dev/cli/q/src/build/token" import "git.akyoto.dev/cli/q/src/build/token"

View File

@ -1,4 +1,4 @@
package directory package fs
import ( import (
"syscall" "syscall"

View File

@ -1,16 +1,16 @@
package directory_test package fs_test
import ( import (
"testing" "testing"
"git.akyoto.dev/cli/q/src/build/directory" "git.akyoto.dev/cli/q/src/build/fs"
"git.akyoto.dev/go/assert" "git.akyoto.dev/go/assert"
) )
func TestWalk(t *testing.T) { func TestWalk(t *testing.T) {
var files []string var files []string
directory.Walk(".", func(file string) { fs.Walk(".", func(file string) {
files = append(files, file) files = append(files, file)
}) })
@ -19,6 +19,6 @@ func TestWalk(t *testing.T) {
} }
func TestNonExisting(t *testing.T) { func TestNonExisting(t *testing.T) {
err := directory.Walk("does-not-exist", func(file string) {}) err := fs.Walk("does-not-exist", func(file string) {})
assert.NotNil(t, err) assert.NotNil(t, err)
} }

View File

@ -11,6 +11,11 @@ type Token struct {
Bytes []byte Bytes []byte
} }
// After returns the position after the token.
func (t Token) After() int {
return t.Position + len(t.Bytes)
}
// String creates a human readable representation for debugging purposes. // String creates a human readable representation for debugging purposes.
func (t Token) String() string { func (t Token) String() string {
return fmt.Sprintf("%s %s", t.Kind, t.Text()) return fmt.Sprintf("%s %s", t.Kind, t.Text())

View File

@ -5,60 +5,60 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"git.akyoto.dev/cli/q/src/build/fs"
"git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/build/token"
) )
// Error is a compiler error at a given line and column. // Error is a compiler error at a given line and column.
type Error struct { type Error struct {
Path string Err error
Line int File *fs.File
Column int Position int
Err error Stack string
Stack string
} }
// New generates an error message at the current token position. // New generates an error message at the current token position.
// The error message is clickable in popular editors and leads you // The error message is clickable in popular editors and leads you
// directly to the faulty file at the given line and position. // directly to the faulty file at the given line and position.
func New(err error, path string, tokens []token.Token, cursor int) *Error { func New(err error, file *fs.File, position int) *Error {
var ( return &Error{
lineCount = 1 Err: err,
lineStart = -1 File: file,
) Position: position,
Stack: Stack(),
for i := range cursor {
if tokens[i].Kind == token.NewLine {
lineCount++
lineStart = int(tokens[i].Position)
}
} }
var column int
if cursor < len(tokens) {
column = tokens[cursor].Position - lineStart
} else {
lastToken := tokens[len(tokens)-1]
column = lastToken.Position - lineStart + len(lastToken.Text())
}
return &Error{path, lineCount, column, err, Stack()}
} }
// Error generates the string representation. // Error generates the string representation.
func (e *Error) Error() string { func (e *Error) Error() string {
path := e.Path path := e.File.Path
cwd, err := os.Getwd() cwd, err := os.Getwd()
if err == nil { if err == nil {
relativePath, err := filepath.Rel(cwd, e.Path) relativePath, err := filepath.Rel(cwd, e.File.Path)
if err == nil { if err == nil {
path = relativePath path = relativePath
} }
} }
return fmt.Sprintf("%s:%d:%d: %s\n\n%s", path, e.Line, e.Column, e.Err, e.Stack) line := 1
column := 1
lineStart := -1
for _, t := range e.File.Tokens {
if t.Position >= e.Position {
column = e.Position - lineStart
break
}
if t.Kind == token.NewLine {
lineStart = t.Position
line++
}
}
return fmt.Sprintf("%s:%d:%d: %s\n\n%s", path, line, column, e.Err, e.Stack)
} }
// Unwrap returns the wrapped error. // Unwrap returns the wrapped error.