From e5baba175b4fc8a5a5a1a3d678ff079ca6a19c52 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 19 Jun 2025 13:05:55 +0200 Subject: [PATCH] Added compiler and scanner packages --- src/build/Build.go | 1 + src/cli/Exec.go | 2 +- src/cli/Exec_test.go | 11 ++++- src/cli/build.go | 92 +++++++++++++++++++++++++++++++++++- src/cli/errors.go | 34 +++++++++++++ src/cli/exit.go | 30 ++++++++++++ src/compiler/Compile.go | 8 ++++ src/compiler/Compile_test.go | 16 +++++++ src/compiler/Result.go | 4 ++ src/scanner/Result.go | 4 ++ src/scanner/Scan.go | 10 ++++ src/scanner/Scan_test.go | 13 +++++ 12 files changed, 221 insertions(+), 4 deletions(-) create mode 100644 src/cli/errors.go create mode 100644 src/cli/exit.go create mode 100644 src/compiler/Compile.go create mode 100644 src/compiler/Compile_test.go create mode 100644 src/compiler/Result.go create mode 100644 src/scanner/Result.go create mode 100644 src/scanner/Scan.go create mode 100644 src/scanner/Scan_test.go diff --git a/src/build/Build.go b/src/build/Build.go index c9aba8f..21e2e14 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -10,6 +10,7 @@ type Build struct { Files []string Arch Arch OS OS + Dry bool } // Executable returns the path to the executable. diff --git a/src/cli/Exec.go b/src/cli/Exec.go index 901fd9d..3230998 100644 --- a/src/cli/Exec.go +++ b/src/cli/Exec.go @@ -8,7 +8,7 @@ func Exec(args []string) int { switch args[0] { case "build": - return build(args[1:]) + return _build(args[1:]) case "run": return run(args[1:]) diff --git a/src/cli/Exec_test.go b/src/cli/Exec_test.go index 103e13f..6a1282c 100644 --- a/src/cli/Exec_test.go +++ b/src/cli/Exec_test.go @@ -11,6 +11,15 @@ func TestExec(t *testing.T) { assert.Equal(t, cli.Exec(nil), 2) assert.Equal(t, cli.Exec([]string{"_"}), 2) assert.Equal(t, cli.Exec([]string{"build"}), 0) - assert.Equal(t, cli.Exec([]string{"run"}), 0) + assert.Equal(t, cli.Exec([]string{"build", "--invalid-parameter"}), 2) + assert.Equal(t, cli.Exec([]string{"build", "../../examples/hello", "--invalid-parameter"}), 2) + assert.Equal(t, cli.Exec([]string{"build", "../../examples/hello", "--dry"}), 0) + assert.Equal(t, cli.Exec([]string{"build", "../../examples/hello", "--dry", "--os", "linux"}), 0) + assert.Equal(t, cli.Exec([]string{"build", "../../examples/hello", "--dry", "--os", "mac"}), 0) + assert.Equal(t, cli.Exec([]string{"build", "../../examples/hello", "--dry", "--os", "windows"}), 0) + assert.Equal(t, cli.Exec([]string{"build", "../../examples/hello", "--dry", "--arch", "arm"}), 0) + assert.Equal(t, cli.Exec([]string{"build", "../../examples/hello", "--dry", "--arch", "x86"}), 0) + assert.Equal(t, cli.Exec([]string{"build", "../../examples/hello/hello.q", "--dry"}), 0) assert.Equal(t, cli.Exec([]string{"help"}), 0) + assert.Equal(t, cli.Exec([]string{"run"}), 0) } \ No newline at end of file diff --git a/src/cli/build.go b/src/cli/build.go index 46fd1ab..88aae64 100644 --- a/src/cli/build.go +++ b/src/cli/build.go @@ -1,6 +1,94 @@ package cli -// build parses the arguments and creates a build. -func build(args []string) int { +import ( + "runtime" + "strings" + + "git.urbach.dev/cli/q/src/build" + "git.urbach.dev/cli/q/src/compiler" + "git.urbach.dev/cli/q/src/scanner" +) + +// _build parses the arguments and creates a build. +func _build(args []string) int { + b, err := newBuildFromArgs(args) + + if err != nil { + return exit(err) + } + + _, err = compiler.Compile(scanner.Scan(b)) + + if err != nil { + return exit(err) + } + return 0 +} + +// newBuildFromArgs creates a new build with the given arguments. +func newBuildFromArgs(args []string) (*build.Build, error) { + b := build.New() + + for i := 0; i < len(args); i++ { + switch args[i] { + case "--arch": + i++ + + if i >= len(args) { + return b, &expectedParameterError{Parameter: "arch"} + } + + switch args[i] { + case "arm": + b.Arch = build.ARM + case "x86": + b.Arch = build.X86 + default: + return b, &invalidValueError{Value: args[i], Parameter: "arch"} + } + + case "--dry": + b.Dry = true + + case "--os": + i++ + + if i >= len(args) { + return b, &expectedParameterError{Parameter: "os"} + } + + switch args[i] { + case "linux": + b.OS = build.Linux + case "mac": + b.OS = build.Mac + case "windows": + b.OS = build.Windows + default: + return b, &invalidValueError{Value: args[i], Parameter: "os"} + } + + default: + if strings.HasPrefix(args[i], "-") { + return b, &unknownParameterError{Parameter: args[i]} + } + + b.Files = append(b.Files, args[i]) + } + } + + if b.OS == build.UnknownOS { + return b, &invalidValueError{Value: runtime.GOOS, Parameter: "os"} + } + + if b.Arch == build.UnknownArch { + return b, &invalidValueError{Value: runtime.GOARCH, Parameter: "arch"} + } + + if len(b.Files) == 0 { + b.Files = append(b.Files, ".") + } + + return b, nil } \ No newline at end of file diff --git a/src/cli/errors.go b/src/cli/errors.go new file mode 100644 index 0000000..10691a9 --- /dev/null +++ b/src/cli/errors.go @@ -0,0 +1,34 @@ +package cli + +import "fmt" + +// expectedParameterError is created when a command line parameter is missing. +type expectedParameterError struct { + Parameter string +} + +// Error implements the error interface. +func (err *expectedParameterError) Error() string { + return fmt.Sprintf("Expected parameter '%s'", err.Parameter) +} + +// invalidValueError is created when a parameter has an invalid value. +type invalidValueError struct { + Value string + Parameter string +} + +// Error implements the error interface. +func (err *invalidValueError) Error() string { + return fmt.Sprintf("Invalid value '%s' for parameter '%s'", err.Value, err.Parameter) +} + +// unknownParameterError is created when a command line parameter is not recognized. +type unknownParameterError struct { + Parameter string +} + +// Error implements the error interface. +func (err *unknownParameterError) Error() string { + return fmt.Sprintf("Unknown parameter '%s'", err.Parameter) +} \ No newline at end of file diff --git a/src/cli/exit.go b/src/cli/exit.go new file mode 100644 index 0000000..dd1353f --- /dev/null +++ b/src/cli/exit.go @@ -0,0 +1,30 @@ +package cli + +import ( + "errors" + "fmt" + "os" + "os/exec" +) + +// exit returns the exit code depending on the error type. +func exit(err error) int { + fmt.Fprintln(os.Stderr, err) + + var ( + exit *exec.ExitError + expectedParameter *expectedParameterError + unknownParameter *unknownParameterError + invalidValue *invalidValueError + ) + + if errors.As(err, &exit) { + return exit.ExitCode() + } + + if errors.As(err, &expectedParameter) || errors.As(err, &unknownParameter) || errors.As(err, &invalidValue) { + return 2 + } + + return 1 +} \ No newline at end of file diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go new file mode 100644 index 0000000..3b47936 --- /dev/null +++ b/src/compiler/Compile.go @@ -0,0 +1,8 @@ +package compiler + +import "git.urbach.dev/cli/q/src/scanner" + +// Compile waits for the scan to finish and compiles all functions. +func Compile(scan scanner.Result) (Result, error) { + return Result{}, nil +} \ No newline at end of file diff --git a/src/compiler/Compile_test.go b/src/compiler/Compile_test.go new file mode 100644 index 0000000..858e3ea --- /dev/null +++ b/src/compiler/Compile_test.go @@ -0,0 +1,16 @@ +package compiler_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/build" + "git.urbach.dev/cli/q/src/compiler" + "git.urbach.dev/cli/q/src/scanner" + "git.urbach.dev/go/assert" +) + +func TestCompile(t *testing.T) { + b := build.New("../../examples/hello") + _, err := compiler.Compile(scanner.Scan(b)) + assert.Nil(t, err) +} \ No newline at end of file diff --git a/src/compiler/Result.go b/src/compiler/Result.go new file mode 100644 index 0000000..398ced5 --- /dev/null +++ b/src/compiler/Result.go @@ -0,0 +1,4 @@ +package compiler + +// Result contains everything we need to write an executable file to disk. +type Result struct{} \ No newline at end of file diff --git a/src/scanner/Result.go b/src/scanner/Result.go new file mode 100644 index 0000000..69aa88f --- /dev/null +++ b/src/scanner/Result.go @@ -0,0 +1,4 @@ +package scanner + +// Result contains everything the compiler needs to start a build. +type Result struct{} \ No newline at end of file diff --git a/src/scanner/Scan.go b/src/scanner/Scan.go new file mode 100644 index 0000000..4b6ed00 --- /dev/null +++ b/src/scanner/Scan.go @@ -0,0 +1,10 @@ +package scanner + +import ( + "git.urbach.dev/cli/q/src/build" +) + +// Scan scans all the files included in the build. +func Scan(b *build.Build) Result { + return Result{} +} \ No newline at end of file diff --git a/src/scanner/Scan_test.go b/src/scanner/Scan_test.go new file mode 100644 index 0000000..72a0502 --- /dev/null +++ b/src/scanner/Scan_test.go @@ -0,0 +1,13 @@ +package scanner_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/build" + "git.urbach.dev/cli/q/src/scanner" +) + +func TestScan(t *testing.T) { + b := build.New("../../examples/hello") + scanner.Scan(b) +} \ No newline at end of file