diff --git a/src/build/Function.go b/src/build/Function.go index 18131fd..cc5e787 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -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 { diff --git a/src/build/Scan.go b/src/build/Scan.go index 87f6033..0a2c903 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -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{ diff --git a/src/build/File.go b/src/build/fs/File.go similarity index 91% rename from src/build/File.go rename to src/build/fs/File.go index 760bfcc..53d49c2 100644 --- a/src/build/File.go +++ b/src/build/fs/File.go @@ -1,4 +1,4 @@ -package build +package fs import "git.akyoto.dev/cli/q/src/build/token" diff --git a/src/build/directory/Walk.go b/src/build/fs/Walk.go similarity index 98% rename from src/build/directory/Walk.go rename to src/build/fs/Walk.go index e2cc495..78d6043 100644 --- a/src/build/directory/Walk.go +++ b/src/build/fs/Walk.go @@ -1,4 +1,4 @@ -package directory +package fs import ( "syscall" diff --git a/src/build/directory/Walk_test.go b/src/build/fs/Walk_test.go similarity index 62% rename from src/build/directory/Walk_test.go rename to src/build/fs/Walk_test.go index 00c663f..ae55b3d 100644 --- a/src/build/directory/Walk_test.go +++ b/src/build/fs/Walk_test.go @@ -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) } diff --git a/src/build/token/Token.go b/src/build/token/Token.go index 7c10db2..c01d302 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -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()) diff --git a/src/errors/Error.go b/src/errors/Error.go index c06bd20..ad434d3 100644 --- a/src/errors/Error.go +++ b/src/errors/Error.go @@ -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.