From 8b1998937261bcb5b712bc7ddb28abce1fdbeb43 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 30 Oct 2023 16:17:41 +0100 Subject: [PATCH] Added file scanner --- go.mod | 2 + go.sum | 2 + main_test.go | 8 ++-- src/asm/Address.go | 3 -- src/asm/Assembler.go | 14 ++----- src/asm/Assembler_test.go | 41 ++++++++++++++++++++ src/build/Build.go | 82 +++++++++++++++++++-------------------- src/build/Function.go | 3 ++ src/build/Scan.go | 63 ++++++++++++++++++++++++++++++ src/cli/Build.go | 31 +++++++++------ 10 files changed, 178 insertions(+), 71 deletions(-) create mode 100644 go.sum create mode 100644 src/asm/Assembler_test.go create mode 100644 src/build/Function.go create mode 100644 src/build/Scan.go diff --git a/go.mod b/go.mod index 2c2b156..e78e48b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module git.akyoto.dev/cli/q go 1.21 + +require git.akyoto.dev/go/assert v0.1.3 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9fc2547 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= +git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= diff --git a/main_test.go b/main_test.go index 4aac58f..ab4ac62 100644 --- a/main_test.go +++ b/main_test.go @@ -7,6 +7,7 @@ import ( "git.akyoto.dev/cli/q/src/cli" "git.akyoto.dev/cli/q/src/log" + "git.akyoto.dev/go/assert" ) func TestMain(m *testing.M) { @@ -29,16 +30,13 @@ func TestCLI(t *testing.T) { {[]string{"build", "examples/hello/hello.q"}, 1}, {[]string{"build", "examples/hello", "--invalid"}, 2}, {[]string{"build", "examples/hello", "--dry"}, 0}, + {[]string{"build", "examples/hello", "--dry", "--verbose"}, 0}, } for _, test := range tests { t.Log(test.arguments) exitCode := cli.Main(test.arguments) - - if exitCode != test.expectedExitCode { - t.Errorf("exit code %d (expected %d)", exitCode, test.expectedExitCode) - t.FailNow() - } + assert.Equal(t, exitCode, test.expectedExitCode) } } diff --git a/src/asm/Address.go b/src/asm/Address.go index 8e32c5a..14b073b 100644 --- a/src/asm/Address.go +++ b/src/asm/Address.go @@ -2,6 +2,3 @@ package asm // Address represents a memory address. type Address = uint32 - -// Index references an instruction by its position. -// type Index = uint32 diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index 552ff91..4ed7552 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -12,20 +12,17 @@ import ( // Assembler contains a list of instructions. type Assembler struct { Instructions []Instruction - // labels map[string]Index - Verbose bool } // New creates a new assembler. func New() *Assembler { return &Assembler{ Instructions: make([]Instruction, 0, 8), - // labels: map[string]Index{}, } } // Finalize generates the final machine code. -func (a *Assembler) Finalize() ([]byte, []byte) { +func (a *Assembler) Finalize(verbose bool) ([]byte, []byte) { code := make([]byte, 0, len(a.Instructions)*8) data := make(Data, 0, 16) pointers := []Pointer{} @@ -46,8 +43,10 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case SYSCALL: code = x64.Syscall(code) } + } - if a.Verbose { + if verbose { + for _, x := range a.Instructions { log.Info.Println(x.String()) } } @@ -63,11 +62,6 @@ func (a *Assembler) Finalize() ([]byte, []byte) { return code, data } -// AddLabel creates a new label at the current position. -// func (a *Assembler) AddLabel(name string) { -// a.labels[name] = Index(len(a.instructions)) -// } - // MoveRegisterData moves a data section address into the given register. func (a *Assembler) MoveRegisterData(reg register.ID, data []byte) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/asm/Assembler_test.go b/src/asm/Assembler_test.go new file mode 100644 index 0000000..f702742 --- /dev/null +++ b/src/asm/Assembler_test.go @@ -0,0 +1,41 @@ +package asm_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/linux" + "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/go/assert" +) + +func TestHello(t *testing.T) { + a := asm.New() + + hello := []byte("Hello\n") + a.MoveRegisterNumber(register.Syscall0, linux.Write) + a.MoveRegisterNumber(register.Syscall1, 1) + a.MoveRegisterData(register.Syscall2, hello) + a.MoveRegisterNumber(register.Syscall3, uint64(len(hello))) + a.Syscall() + + a.MoveRegisterNumber(register.Syscall0, linux.Exit) + a.MoveRegisterNumber(register.Syscall1, 0) + a.Syscall() + + code, data := a.Finalize(false) + + assert.DeepEqual(t, code, []byte{ + 0xb8, 0x01, 0x00, 0x00, 0x00, + 0xbf, 0x01, 0x00, 0x00, 0x00, + 0xbe, 0xa2, 0x00, 0x40, 0x00, + 0xba, 0x06, 0x00, 0x00, 0x00, + 0x0f, 0x05, + + 0xb8, 0x3c, 0x00, 0x00, 0x00, + 0xbf, 0x00, 0x00, 0x00, 0x00, + 0x0f, 0x05, + }) + + assert.DeepEqual(t, data, hello) +} diff --git a/src/build/Build.go b/src/build/Build.go index 80d16c5..80a0e66 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -4,10 +4,8 @@ import ( "bufio" "os" "path/filepath" - "strings" "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/directory" "git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/linux" @@ -30,42 +28,14 @@ func New(directory string) *Build { } } -var ( - hello = []byte("Hello\n") - world = []byte("World\n") -) - // Run parses the input files and generates an executable file. func (build *Build) Run() error { - // err := build.Compile() + code, data, err := build.Compile() - // if err != nil { - // return err - // } - - a := asm.Assembler{ - Instructions: make([]asm.Instruction, 0, 8), - Verbose: build.Verbose, + if err != nil { + return err } - a.MoveRegisterNumber(register.Syscall0, linux.Write) - a.MoveRegisterNumber(register.Syscall1, 1) - a.MoveRegisterData(register.Syscall2, hello) - a.MoveRegisterNumber(register.Syscall3, uint64(len(hello))) - a.Syscall() - - a.MoveRegisterNumber(register.Syscall0, linux.Write) - a.MoveRegisterNumber(register.Syscall1, 1) - a.MoveRegisterData(register.Syscall2, world) - a.MoveRegisterNumber(register.Syscall3, uint64(len(world))) - a.Syscall() - - a.MoveRegisterNumber(register.Syscall0, linux.Exit) - a.MoveRegisterNumber(register.Syscall1, 0) - a.Syscall() - - code, data := a.Finalize() - if !build.WriteExecutable { return nil } @@ -74,26 +44,54 @@ func (build *Build) Run() error { } // Compile compiles all the functions. -func (build *Build) Compile() error { +func (build *Build) Compile() ([]byte, []byte, error) { stat, err := os.Stat(build.Directory) if err != nil { - return err + return nil, nil, err } if !stat.IsDir() { - return &errors.InvalidDirectory{Path: build.Directory} + return nil, nil, &errors.InvalidDirectory{Path: build.Directory} } - directory.Walk(build.Directory, func(file string) { - if !strings.HasSuffix(file, ".q") { - return + functions, errors := Scan(build.Directory) + + for functions != nil || errors != nil { + select { + case err, ok := <-errors: + if !ok { + errors = nil + continue + } + + log.Info.Println(err) + + case function, ok := <-functions: + if !ok { + functions = nil + continue + } + + log.Info.Println(function) } + } - log.Info.Println(file) - }) + a := asm.New() - return nil + hello := []byte("Hello\n") + a.MoveRegisterNumber(register.Syscall0, linux.Write) + a.MoveRegisterNumber(register.Syscall1, 1) + a.MoveRegisterData(register.Syscall2, hello) + a.MoveRegisterNumber(register.Syscall3, uint64(len(hello))) + a.Syscall() + + a.MoveRegisterNumber(register.Syscall0, linux.Exit) + a.MoveRegisterNumber(register.Syscall1, 0) + a.Syscall() + + code, data := a.Finalize(build.Verbose) + return code, data, nil } // Executable returns the path to the executable. diff --git a/src/build/Function.go b/src/build/Function.go new file mode 100644 index 0000000..d3734e3 --- /dev/null +++ b/src/build/Function.go @@ -0,0 +1,3 @@ +package build + +type Function struct{} diff --git a/src/build/Scan.go b/src/build/Scan.go new file mode 100644 index 0000000..9475727 --- /dev/null +++ b/src/build/Scan.go @@ -0,0 +1,63 @@ +package build + +import ( + "os" + "path/filepath" + "strings" + "sync" + + "git.akyoto.dev/cli/q/src/directory" + "git.akyoto.dev/cli/q/src/log" +) + +// Scan scans the directory. +func Scan(path string) (<-chan *Function, <-chan error) { + functions := make(chan *Function, 16) + errors := make(chan error) + + go func() { + scanDirectory(path, functions, errors) + close(functions) + close(errors) + }() + + return functions, errors +} + +// scanDirectory scans the directory without channel allocations. +func scanDirectory(path string, functions chan<- *Function, errors chan<- error) { + wg := sync.WaitGroup{} + + directory.Walk(path, func(name string) { + if !strings.HasSuffix(name, ".q") { + return + } + + fullPath := filepath.Join(path, name) + wg.Add(1) + + go func() { + defer wg.Done() + err := scanFile(fullPath, functions) + + if err != nil { + errors <- err + } + }() + }) + + wg.Wait() +} + +// scanFile scans a single file. +func scanFile(path string, functions chan<- *Function) error { + log.Info.Println(path) + contents, err := os.ReadFile(path) + + if err != nil { + return err + } + + log.Info.Println(string(contents)) + return nil +} diff --git a/src/cli/Build.go b/src/cli/Build.go index 761d418..fefbe24 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -1,21 +1,18 @@ package cli import ( + "path/filepath" + "strings" + "git.akyoto.dev/cli/q/src/build" "git.akyoto.dev/cli/q/src/log" ) // Build builds an executable. func Build(args []string) int { - directory := "." + b := build.New(".") - if len(args) > 0 { - directory = args[0] - } - - b := build.New(directory) - - for i := 1; i < len(args); i++ { + for i := 0; i < len(args); i++ { switch args[i] { case "--dry": b.WriteExecutable = false @@ -24,12 +21,24 @@ func Build(args []string) int { b.Verbose = true default: - log.Error.Printf("Unknown parameter: %s\n", args[i]) - return 2 + if strings.HasPrefix(args[i], "-") { + log.Error.Printf("Unknown parameter: %s\n", args[i]) + return 2 + } + + b.Directory = args[i] } } - err := b.Run() + fullPath, err := filepath.Abs(b.Directory) + + if err != nil { + log.Error.Println(err) + return 1 + } + + b.Directory = fullPath + err = b.Run() if err != nil { log.Error.Println(err)