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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,16 @@
package directory_test
package fs_test
import (
"testing"
"git.akyoto.dev/cli/q/src/build/directory"
"git.akyoto.dev/cli/q/src/build/fs"
"git.akyoto.dev/go/assert"
)
func TestWalk(t *testing.T) {
var files []string
directory.Walk(".", func(file string) {
fs.Walk(".", func(file string) {
files = append(files, file)
})
@ -19,6 +19,6 @@ func TestWalk(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)
}

View File

@ -11,6 +11,11 @@ type Token struct {
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.
func (t Token) String() string {
return fmt.Sprintf("%s %s", t.Kind, t.Text())

View File

@ -5,60 +5,60 @@ import (
"os"
"path/filepath"
"git.akyoto.dev/cli/q/src/build/fs"
"git.akyoto.dev/cli/q/src/build/token"
)
// Error is a compiler error at a given line and column.
type Error struct {
Path string
Line int
Column int
Err error
Stack string
Err error
File *fs.File
Position int
Stack string
}
// New generates an error message at the current token position.
// The error message is clickable in popular editors and leads you
// directly to the faulty file at the given line and position.
func New(err error, path string, tokens []token.Token, cursor int) *Error {
var (
lineCount = 1
lineStart = -1
)
for i := range cursor {
if tokens[i].Kind == token.NewLine {
lineCount++
lineStart = int(tokens[i].Position)
}
func New(err error, file *fs.File, position int) *Error {
return &Error{
Err: err,
File: file,
Position: position,
Stack: Stack(),
}
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.
func (e *Error) Error() string {
path := e.Path
path := e.File.Path
cwd, err := os.Getwd()
if err == nil {
relativePath, err := filepath.Rel(cwd, e.Path)
relativePath, err := filepath.Rel(cwd, e.File.Path)
if err == nil {
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.