Improved error handling
This commit is contained in:
parent
57f1da10fe
commit
4776b4c14c
@ -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 {
|
||||||
|
@ -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{
|
||||||
|
@ -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"
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package directory
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"syscall"
|
"syscall"
|
@ -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)
|
||||||
}
|
}
|
@ -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())
|
||||||
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user