From 57f1da10fe49570dc9939c89f40cde9db5bd695d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Jun 2024 14:46:44 +0200 Subject: [PATCH] Improved instruction parser --- src/build/Compile.go | 26 +++++--- src/build/File.go | 9 +++ src/build/Function.go | 114 +++++++++++++++++++++--------------- src/build/Scan.go | 6 ++ src/build/Variable.go | 1 + src/build/token/Token.go | 7 +++ src/errors/CompileErrors.go | 5 ++ 7 files changed, 114 insertions(+), 54 deletions(-) create mode 100644 src/build/File.go create mode 100644 src/errors/CompileErrors.go diff --git a/src/build/Compile.go b/src/build/Compile.go index a40ab2c..f6f1736 100644 --- a/src/build/Compile.go +++ b/src/build/Compile.go @@ -6,7 +6,6 @@ import ( // Compile compiles all the functions. func Compile(functions <-chan *Function, errors <-chan error) (map[string]*Function, error) { - wg := sync.WaitGroup{} allFunctions := map[string]*Function{} for functions != nil || errors != nil { @@ -25,17 +24,28 @@ func Compile(functions <-chan *Function, errors <-chan error) (map[string]*Funct continue } - wg.Add(1) - - go func() { - defer wg.Done() - function.Compile() - }() - allFunctions[function.Name] = function } } + wg := sync.WaitGroup{} + + for _, function := range allFunctions { + wg.Add(1) + + go func() { + defer wg.Done() + function.Compile() + }() + } + wg.Wait() + + for _, function := range allFunctions { + if function.Error != nil { + return nil, function.Error + } + } + return allFunctions, nil } diff --git a/src/build/File.go b/src/build/File.go new file mode 100644 index 0000000..760bfcc --- /dev/null +++ b/src/build/File.go @@ -0,0 +1,9 @@ +package build + +import "git.akyoto.dev/cli/q/src/build/token" + +// File represents a single source file. +type File struct { + Tokens token.List + Path string +} diff --git a/src/build/Function.go b/src/build/Function.go index 5fb3f25..18131fd 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -14,6 +14,7 @@ import ( // Function represents a function. type Function struct { Name string + File *File Head token.List Body token.List Variables map[string]*Variable @@ -24,32 +25,60 @@ type Function struct { // Compile turns a function into machine code. func (f *Function) Compile() { if config.Verbose { - ansi.Underline.Println(f.Name) + ansi.Bold.Println(f.Name) } - for _, line := range f.Lines() { - if len(line) == 0 { + start := 0 + groupLevel := 0 + + for i, t := range f.Body { + if start == i && (t.Kind == token.NewLine || t.Kind == token.BlockStart || t.Kind == token.BlockEnd) { + start = i + 1 continue } - if config.Verbose { - ansi.Dim.Print("[ ] ") - fmt.Println(line) - } + switch t.Kind { + case token.NewLine: + if groupLevel > 0 { + continue + } - err := f.compileInstruction(line) + if start != -1 { + instruction := f.Body[start:i] + err := f.CompileInstruction(instruction) - if err != nil { - ansi.Red.Println(err) - return + if err != nil { + f.Error = err + return + } + + start = -1 + } + + start = i + 1 + + case token.GroupStart: + groupLevel++ + + case token.GroupEnd: + groupLevel-- + + case token.BlockStart: + // Add scope + + case token.BlockEnd: + // Remove scope } } f.Assembler.Return() if config.Verbose { + fmt.Println() + ansi.Bold.Println(f.Name + ".asm") + for _, x := range f.Assembler.Instructions { - ansi.Dim.Print("[asm] ") + ansi.Dim.Print("│ ") fmt.Print(x.Mnemonic.String()) if x.Data != nil { @@ -61,27 +90,44 @@ func (f *Function) Compile() { } } -// compileInstruction compiles a single instruction. -func (f *Function) compileInstruction(line token.List) error { - switch line[0].Kind { - case token.Keyword: +// CompileInstruction compiles a single instruction. +func (f *Function) CompileInstruction(line token.List) error { + if config.Verbose { + ansi.Dim.Print("│ ") + fmt.Println(line) + } + + if line[0].Kind == token.Keyword { switch line[0].Text() { case "return": f.Assembler.Return() } - case token.Identifier: - if len(line) >= 2 && line[1].Kind == token.Define { + return nil + } + + if line[0].Kind == token.Identifier { + if len(line) < 2 { + return fmt.Errorf("error to be implemented") + } + + if line[1].Kind == token.Define { name := line[0].Text() 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) + } + if config.Verbose { - ansi.Dim.Printf("[var] ") - fmt.Println(name, value) + ansi.Dim.Printf("├── var ") + fmt.Println(name, "=", value) } f.Variables[name] = &Variable{ - Value: line[2:], + Name: name, + Value: value, IsConst: true, } @@ -115,7 +161,7 @@ func (f *Function) compileInstruction(line token.List) error { variable, exists := f.Variables[name] if !exists { - return fmt.Errorf("Unknown identifier '%s'", name) + panic("Unknown identifier " + name) } if !variable.IsConst { @@ -142,30 +188,6 @@ func (f *Function) compileInstruction(line token.List) error { return nil } -// Lines returns the lines in the function body. -func (f *Function) Lines() []token.List { - var ( - lines []token.List - start = 0 - i = 0 - ) - - for i < len(f.Body) { - if f.Body[i].Kind == token.NewLine { - lines = append(lines, f.Body[start:i]) - start = i + 1 - } - - i++ - } - - if i != start { - lines = append(lines, f.Body[start:i]) - } - - return lines -} - // String returns the function name. func (f *Function) String() string { return f.Name diff --git a/src/build/Scan.go b/src/build/Scan.go index 318c598..87f6033 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -86,6 +86,11 @@ func scanFile(path string, functions chan<- *Function) error { tokens := token.Tokenize(contents) + file := &File{ + Tokens: tokens, + Path: path, + } + var ( i = 0 groupLevel = 0 @@ -216,6 +221,7 @@ func scanFile(path string, functions chan<- *Function) error { functions <- &Function{ Name: tokens[nameStart].Text(), + File: file, Head: tokens[paramsStart:bodyStart], Body: tokens[bodyStart : i+1], Variables: map[string]*Variable{}, diff --git a/src/build/Variable.go b/src/build/Variable.go index 14cece9..46190e2 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -4,6 +4,7 @@ import "git.akyoto.dev/cli/q/src/build/token" // Variable represents a variable in a function. type Variable struct { + Name string Value token.List IsConst bool } diff --git a/src/build/token/Token.go b/src/build/token/Token.go index 78d6005..7c10db2 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -1,5 +1,7 @@ package token +import "fmt" + // Token represents a single element in a source file. // The characters that make up an identifier are grouped into a single token. // This makes parsing easier and allows us to do better syntax checks. @@ -9,6 +11,11 @@ type Token struct { Bytes []byte } +// String creates a human readable representation for debugging purposes. +func (t Token) String() string { + return fmt.Sprintf("%s %s", t.Kind, t.Text()) +} + // Text returns the token text. func (t Token) Text() string { return string(t.Bytes) diff --git a/src/errors/CompileErrors.go b/src/errors/CompileErrors.go new file mode 100644 index 0000000..2c5ac46 --- /dev/null +++ b/src/errors/CompileErrors.go @@ -0,0 +1,5 @@ +package errors + +var ( + MissingAssignmentValue = &Base{"Missing assignment value"} +)