Implemented error messages
This commit is contained in:
parent
2d990b0bee
commit
9458253f31
@ -7,34 +7,20 @@ import (
|
|||||||
|
|
||||||
// Build describes a compiler build.
|
// Build describes a compiler build.
|
||||||
type Build struct {
|
type Build struct {
|
||||||
Files []string
|
Files []string
|
||||||
WriteExecutable bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new build.
|
// New creates a new build.
|
||||||
func New(files ...string) *Build {
|
func New(files ...string) *Build {
|
||||||
return &Build{
|
return &Build{
|
||||||
Files: files,
|
Files: files,
|
||||||
WriteExecutable: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run parses the input files and generates an executable file.
|
// Run parses the input files and generates an executable file.
|
||||||
func (build *Build) Run() error {
|
func (build *Build) Run() (map[string]*Function, error) {
|
||||||
functions, errors := Scan(build.Files)
|
functions, errors := Scan(build.Files)
|
||||||
allFunctions, err := Compile(functions, errors)
|
return Compile(functions, errors)
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !build.WriteExecutable {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
path := build.Executable()
|
|
||||||
code, data := Finalize(allFunctions)
|
|
||||||
return Write(path, code, data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Executable returns the path to the executable.
|
// Executable returns the path to the executable.
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package build_test
|
package build_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/build"
|
"git.akyoto.dev/cli/q/src/build"
|
||||||
@ -9,16 +10,17 @@ import (
|
|||||||
|
|
||||||
func TestBuild(t *testing.T) {
|
func TestBuild(t *testing.T) {
|
||||||
b := build.New("../../examples/hello")
|
b := build.New("../../examples/hello")
|
||||||
assert.Nil(t, b.Run())
|
_, err := b.Run()
|
||||||
|
assert.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSkipExecutable(t *testing.T) {
|
func TestExecutable(t *testing.T) {
|
||||||
b := build.New("../../examples/hello")
|
b := build.New("../../examples/hello")
|
||||||
b.WriteExecutable = false
|
assert.Equal(t, filepath.Base(b.Executable()), "hello")
|
||||||
assert.Nil(t, b.Run())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNonExisting(t *testing.T) {
|
func TestNonExisting(t *testing.T) {
|
||||||
b := build.New("does-not-exist")
|
b := build.New("does-not-exist")
|
||||||
assert.NotNil(t, b.Run())
|
_, err := b.Run()
|
||||||
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/build/directory"
|
"git.akyoto.dev/cli/q/src/build/directory"
|
||||||
"git.akyoto.dev/cli/q/src/build/token"
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
|
"git.akyoto.dev/cli/q/src/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Scan scans the directory.
|
// Scan scans the directory.
|
||||||
@ -86,46 +87,141 @@ func scanFile(path string, functions chan<- *Function) error {
|
|||||||
tokens := token.Tokenize(contents)
|
tokens := token.Tokenize(contents)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
i = 0
|
||||||
groupLevel = 0
|
groupLevel = 0
|
||||||
blockLevel = 0
|
blockLevel = 0
|
||||||
headerStart = -1
|
nameStart = -1
|
||||||
|
paramsStart = -1
|
||||||
bodyStart = -1
|
bodyStart = -1
|
||||||
)
|
)
|
||||||
|
|
||||||
for i, t := range tokens {
|
for {
|
||||||
switch t.Kind {
|
// Function name
|
||||||
case token.Identifier:
|
for i < len(tokens) {
|
||||||
if blockLevel == 0 && groupLevel == 0 {
|
if tokens[i].Kind == token.Identifier {
|
||||||
headerStart = i
|
nameStart = i
|
||||||
|
i++
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case token.GroupStart:
|
if tokens[i].Kind == token.NewLine {
|
||||||
groupLevel++
|
i++
|
||||||
|
continue
|
||||||
case token.GroupEnd:
|
|
||||||
groupLevel--
|
|
||||||
|
|
||||||
case token.BlockStart:
|
|
||||||
blockLevel++
|
|
||||||
|
|
||||||
if blockLevel == 1 {
|
|
||||||
bodyStart = i
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case token.BlockEnd:
|
if tokens[i].Kind == token.EOF {
|
||||||
blockLevel--
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if blockLevel == 0 {
|
return errors.New(errors.ExpectedFunctionName, path, tokens, i)
|
||||||
function := &Function{
|
}
|
||||||
Name: tokens[headerStart].Text(),
|
|
||||||
Head: tokens[headerStart:bodyStart],
|
// Function parameters
|
||||||
Body: tokens[bodyStart : i+1],
|
for i < len(tokens) {
|
||||||
|
if tokens[i].Kind == token.GroupStart {
|
||||||
|
groupLevel++
|
||||||
|
|
||||||
|
if groupLevel == 1 {
|
||||||
|
paramsStart = i
|
||||||
}
|
}
|
||||||
|
|
||||||
functions <- function
|
i++
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
if tokens[i].Kind == token.GroupEnd {
|
||||||
|
groupLevel--
|
||||||
|
|
||||||
|
if groupLevel < 0 {
|
||||||
|
return errors.New(errors.MissingGroupStart, path, tokens, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
|
||||||
|
if groupLevel == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokens[i].Kind == token.EOF {
|
||||||
|
if groupLevel > 0 {
|
||||||
|
return errors.New(errors.MissingGroupEnd, path, tokens, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if paramsStart == -1 {
|
||||||
|
return errors.New(errors.ExpectedFunctionParameters, path, tokens, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if groupLevel > 0 {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New(errors.ExpectedFunctionParameters, path, tokens, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function definition
|
||||||
|
for i < len(tokens) {
|
||||||
|
if tokens[i].Kind == token.BlockStart {
|
||||||
|
blockLevel++
|
||||||
|
|
||||||
|
if blockLevel == 1 {
|
||||||
|
bodyStart = i
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokens[i].Kind == token.BlockEnd {
|
||||||
|
blockLevel--
|
||||||
|
|
||||||
|
if blockLevel < 0 {
|
||||||
|
return errors.New(errors.MissingBlockStart, path, tokens, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
|
||||||
|
if blockLevel == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokens[i].Kind == token.EOF {
|
||||||
|
if blockLevel > 0 {
|
||||||
|
return errors.New(errors.MissingBlockEnd, path, tokens, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bodyStart == -1 {
|
||||||
|
return errors.New(errors.ExpectedFunctionDefinition, path, tokens, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if blockLevel > 0 {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New(errors.ExpectedFunctionDefinition, path, tokens, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
functions <- &Function{
|
||||||
|
Name: tokens[nameStart].Text(),
|
||||||
|
Head: tokens[paramsStart:bodyStart],
|
||||||
|
Body: tokens[bodyStart : i+1],
|
||||||
|
}
|
||||||
|
|
||||||
|
nameStart = -1
|
||||||
|
paramsStart = -1
|
||||||
|
bodyStart = -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,9 @@ const (
|
|||||||
// Invalid represents an invalid token.
|
// Invalid represents an invalid token.
|
||||||
Invalid Kind = iota
|
Invalid Kind = iota
|
||||||
|
|
||||||
|
// EOF represents the end of file.
|
||||||
|
EOF
|
||||||
|
|
||||||
// NewLine represents the newline character.
|
// NewLine represents the newline character.
|
||||||
NewLine
|
NewLine
|
||||||
|
|
||||||
@ -54,6 +57,7 @@ const (
|
|||||||
func (kind Kind) String() string {
|
func (kind Kind) String() string {
|
||||||
return [...]string{
|
return [...]string{
|
||||||
"Invalid",
|
"Invalid",
|
||||||
|
"EOF",
|
||||||
"NewLine",
|
"NewLine",
|
||||||
"Identifier",
|
"Identifier",
|
||||||
"Keyword",
|
"Keyword",
|
||||||
|
@ -35,6 +35,11 @@ func TestFunction(t *testing.T) {
|
|||||||
Bytes: []byte("}"),
|
Bytes: []byte("}"),
|
||||||
Position: 7,
|
Position: 7,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Kind: token.EOF,
|
||||||
|
Bytes: nil,
|
||||||
|
Position: 8,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +56,11 @@ func TestKeyword(t *testing.T) {
|
|||||||
Bytes: []byte("x"),
|
Bytes: []byte("x"),
|
||||||
Position: 7,
|
Position: 7,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Kind: token.EOF,
|
||||||
|
Bytes: nil,
|
||||||
|
Position: 8,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +87,11 @@ func TestArray(t *testing.T) {
|
|||||||
Bytes: []byte("]"),
|
Bytes: []byte("]"),
|
||||||
Position: 7,
|
Position: 7,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Kind: token.EOF,
|
||||||
|
Bytes: nil,
|
||||||
|
Position: 8,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,6 +108,11 @@ func TestNewline(t *testing.T) {
|
|||||||
Bytes: []byte("\n"),
|
Bytes: []byte("\n"),
|
||||||
Position: 1,
|
Position: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Kind: token.EOF,
|
||||||
|
Bytes: nil,
|
||||||
|
Position: 2,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,6 +129,11 @@ func TestNumber(t *testing.T) {
|
|||||||
Bytes: []byte("-456"),
|
Bytes: []byte("-456"),
|
||||||
Position: 4,
|
Position: 4,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Kind: token.EOF,
|
||||||
|
Bytes: nil,
|
||||||
|
Position: 8,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,6 +165,11 @@ func TestSeparator(t *testing.T) {
|
|||||||
Bytes: []byte("c"),
|
Bytes: []byte("c"),
|
||||||
Position: 4,
|
Position: 4,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Kind: token.EOF,
|
||||||
|
Bytes: nil,
|
||||||
|
Position: 5,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,6 +186,11 @@ func TestString(t *testing.T) {
|
|||||||
Bytes: []byte(`"World"`),
|
Bytes: []byte(`"World"`),
|
||||||
Position: 8,
|
Position: 8,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Kind: token.EOF,
|
||||||
|
Bytes: nil,
|
||||||
|
Position: 15,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,6 +202,11 @@ func TestStringMultiline(t *testing.T) {
|
|||||||
Bytes: []byte("\"Hello\nWorld\""),
|
Bytes: []byte("\"Hello\nWorld\""),
|
||||||
Position: 0,
|
Position: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Kind: token.EOF,
|
||||||
|
Bytes: nil,
|
||||||
|
Position: 13,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,6 +218,11 @@ func TestStringEOF(t *testing.T) {
|
|||||||
Bytes: []byte(`"EOF`),
|
Bytes: []byte(`"EOF`),
|
||||||
Position: 0,
|
Position: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Kind: token.EOF,
|
||||||
|
Bytes: nil,
|
||||||
|
Position: 4,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,6 +240,7 @@ func TestTokenText(t *testing.T) {
|
|||||||
|
|
||||||
func TestTokenKind(t *testing.T) {
|
func TestTokenKind(t *testing.T) {
|
||||||
assert.Equal(t, token.Invalid.String(), "Invalid")
|
assert.Equal(t, token.Invalid.String(), "Invalid")
|
||||||
|
assert.Equal(t, token.EOF.String(), "EOF")
|
||||||
assert.Equal(t, token.NewLine.String(), "NewLine")
|
assert.Equal(t, token.NewLine.String(), "NewLine")
|
||||||
assert.Equal(t, token.Identifier.String(), "Identifier")
|
assert.Equal(t, token.Identifier.String(), "Identifier")
|
||||||
assert.Equal(t, token.Keyword.String(), "Keyword")
|
assert.Equal(t, token.Keyword.String(), "Keyword")
|
||||||
|
@ -31,6 +31,7 @@ func Tokenize(buffer []byte) List {
|
|||||||
for i < len(buffer) {
|
for i < len(buffer) {
|
||||||
if buffer[i] == '"' {
|
if buffer[i] == '"' {
|
||||||
end = i + 1
|
end = i + 1
|
||||||
|
i++
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,6 +44,8 @@ func Tokenize(buffer []byte) List {
|
|||||||
buffer[start:end],
|
buffer[start:end],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
// Parentheses start
|
// Parentheses start
|
||||||
case '(':
|
case '(':
|
||||||
tokens = append(tokens, Token{GroupStart, i, groupStartBytes})
|
tokens = append(tokens, Token{GroupStart, i, groupStartBytes})
|
||||||
@ -121,6 +124,7 @@ func Tokenize(buffer []byte) List {
|
|||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tokens = append(tokens, Token{EOF, i, nil})
|
||||||
return tokens
|
return tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,11 +11,12 @@ import (
|
|||||||
// Build builds an executable.
|
// Build builds an executable.
|
||||||
func Build(args []string) int {
|
func Build(args []string) int {
|
||||||
b := build.New()
|
b := build.New()
|
||||||
|
writeExecutable := true
|
||||||
|
|
||||||
for i := 0; i < len(args); i++ {
|
for i := 0; i < len(args); i++ {
|
||||||
switch args[i] {
|
switch args[i] {
|
||||||
case "--dry":
|
case "--dry":
|
||||||
b.WriteExecutable = false
|
writeExecutable = false
|
||||||
|
|
||||||
case "--verbose", "-v":
|
case "--verbose", "-v":
|
||||||
config.Verbose = true
|
config.Verbose = true
|
||||||
@ -34,7 +35,20 @@ func Build(args []string) int {
|
|||||||
b.Files = append(b.Files, ".")
|
b.Files = append(b.Files, ".")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := b.Run()
|
result, err := b.Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if !writeExecutable {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
path := b.Executable()
|
||||||
|
code, data := build.Finalize(result)
|
||||||
|
err = build.Write(path, code, data)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
@ -6,7 +6,7 @@ import "fmt"
|
|||||||
func Help(args []string) int {
|
func Help(args []string) int {
|
||||||
fmt.Println("Usage: q [command] [options]")
|
fmt.Println("Usage: q [command] [options]")
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
fmt.Println(" build [directory]")
|
fmt.Println(" build [directory] [file]")
|
||||||
fmt.Println(" system")
|
fmt.Println(" system")
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package cli_test
|
package cli_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/cli"
|
"git.akyoto.dev/cli/q/src/cli"
|
||||||
@ -19,17 +17,16 @@ func TestCLI(t *testing.T) {
|
|||||||
{[]string{}, 2},
|
{[]string{}, 2},
|
||||||
{[]string{"invalid"}, 2},
|
{[]string{"invalid"}, 2},
|
||||||
{[]string{"system"}, 0},
|
{[]string{"system"}, 0},
|
||||||
{[]string{"build", "non-existing-directory"}, 1},
|
{[]string{"build", "invalid-directory"}, 1},
|
||||||
{[]string{"build", "../../examples/hello/hello.q"}, 1},
|
{[]string{"build", "--invalid-parameter"}, 2},
|
||||||
{[]string{"build", "../../examples/hello", "--invalid"}, 2},
|
{[]string{"build", "../../examples/hello", "--invalid"}, 2},
|
||||||
{[]string{"build", "../../examples/hello", "--dry"}, 0},
|
{[]string{"build", "../../examples/hello", "--dry"}, 0},
|
||||||
{[]string{"build", "../../examples/hello", "--dry", "--verbose"}, 0},
|
{[]string{"build", "../../examples/hello", "--dry", "--verbose"}, 0},
|
||||||
|
{[]string{"build", "../../examples/hello/hello.q", "--dry"}, 0},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Log(test.arguments)
|
t.Log(test.arguments)
|
||||||
directory, _ := os.Getwd()
|
|
||||||
fmt.Println(directory)
|
|
||||||
exitCode := cli.Main(test.arguments)
|
exitCode := cli.Main(test.arguments)
|
||||||
assert.Equal(t, exitCode, test.expectedExitCode)
|
assert.Equal(t, exitCode, test.expectedExitCode)
|
||||||
}
|
}
|
||||||
|
11
src/errors/Base.go
Normal file
11
src/errors/Base.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
// Base is the base class for errors that have no parameters.
|
||||||
|
type Base struct {
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error generates the string representation.
|
||||||
|
func (err *Base) Error() string {
|
||||||
|
return err.Message
|
||||||
|
}
|
67
src/errors/Error.go
Normal file
67
src/errors/Error.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
relativePath, err := filepath.Rel(cwd, e.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the wrapped error.
|
||||||
|
func (e *Error) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
32
src/errors/Error_test.go
Normal file
32
src/errors/Error_test.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package errors_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.akyoto.dev/cli/q/src/build"
|
||||||
|
"git.akyoto.dev/cli/q/src/errors"
|
||||||
|
"git.akyoto.dev/go/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestErrors(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
File string
|
||||||
|
ExpectedError error
|
||||||
|
}{
|
||||||
|
{"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters},
|
||||||
|
{"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
name := strings.TrimSuffix(test.File, ".q")
|
||||||
|
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
b := build.New(filepath.Join("testdata", test.File))
|
||||||
|
_, err := b.Run()
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Contains(t, err.Error(), test.ExpectedError.Error())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
11
src/errors/ScanErrors.go
Normal file
11
src/errors/ScanErrors.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
var (
|
||||||
|
MissingBlockStart = &Base{"Missing '{'"}
|
||||||
|
MissingBlockEnd = &Base{"Missing '}'"}
|
||||||
|
MissingGroupStart = &Base{"Missing '('"}
|
||||||
|
MissingGroupEnd = &Base{"Missing ')'"}
|
||||||
|
ExpectedFunctionName = &Base{"Expected function name"}
|
||||||
|
ExpectedFunctionParameters = &Base{"Expected function parameters"}
|
||||||
|
ExpectedFunctionDefinition = &Base{"Expected function definition"}
|
||||||
|
)
|
30
src/errors/Stack.go
Normal file
30
src/errors/Stack.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Stack generates a stack trace.
|
||||||
|
func Stack() string {
|
||||||
|
var (
|
||||||
|
final []string
|
||||||
|
buffer = make([]byte, 4096)
|
||||||
|
n = runtime.Stack(buffer, false)
|
||||||
|
stack = string(buffer[:n])
|
||||||
|
lines = strings.Split(stack, "\n")
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 6; i < len(lines); i += 2 {
|
||||||
|
line := strings.TrimSpace(lines[i])
|
||||||
|
space := strings.LastIndex(line, " ")
|
||||||
|
|
||||||
|
if space != -1 {
|
||||||
|
line = line[:space]
|
||||||
|
}
|
||||||
|
|
||||||
|
final = append(final, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(final, "\n")
|
||||||
|
}
|
1
src/errors/testdata/ExpectedFunctionDefinition.q
vendored
Normal file
1
src/errors/testdata/ExpectedFunctionDefinition.q
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
main()
|
1
src/errors/testdata/ExpectedFunctionParameters.q
vendored
Normal file
1
src/errors/testdata/ExpectedFunctionParameters.q
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
main
|
Loading…
Reference in New Issue
Block a user