diff --git a/.gitignore b/.gitignore index 496ee2ca6a2f08396a4076fe43dedf3dc0da8b6d..465b720754867d1db092df805477698d62f52178 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,37 @@ -.DS_Store \ No newline at end of file +.DS_Store + +# Goland IDE +*.iml +*.ipr +*.iws +.idea/ +/build/ +/dist/ +/out/ + +# Golang +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# env file +.env \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..13566b81b018ad684f3a35fee301741b2734c8f4 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000000000000000000000000000000000..35eb1ddfbbc029bcab630581847471d7f238ec53 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="" vcs="Git" /> + </component> +</project> \ No newline at end of file diff --git a/modules/compiler/compiler.go b/modules/compiler/compiler.go new file mode 100644 index 0000000000000000000000000000000000000000..73a7dc823ed984d69f153d3c37af01b032390d74 --- /dev/null +++ b/modules/compiler/compiler.go @@ -0,0 +1,275 @@ +package compiler + +import ( + "log" + "os" + "os/exec" + "strings" +) + +const TempOutputDir = "tempOutput/" +const TempModuleName = "tempModule" + +type Language string + +// Supported languages +const ( + Go Language = "go" + Rust Language = "rust" +) + +type OS string + +// Supported OS +const ( + Windows OS = "windows" + Linux OS = "linux" + MacOS OS = "darwin" // Darwin is the kernel of macOS +) + +// TODO: I want to make an interface for a compilable language, so that we can add more languages in the future +// TODO: The cmd might also be an interface or a struct, so that it can build itself based on the platform and language +// TODO: A cleanup and panic might be needed in setup because if it panics the temp folders should be removed +// TODO: I am not sure that the setup should panic, maybe it should return an error instead so its easier to clean up + +type Compiler struct { + OS OS + Language Language + languageEnv ILanguageEnvironment + SourceCode string + Filename string + cmdPrefix string // For example "cmd /c" on Windows + Dependencies []string + tempOutputDir string +} + +type ICompiler interface { + Compile() (string, error) +} + +type ILanguageEnvironment interface { + SetupEnvironment(cmdPrefix string, dependencies []string) + CheckCompileErrors(filename string, language Language, cmdPrefix string) (string, error) + WriteCodeToFile(filename, sourceCode string) error + RunPipeline(c *Compiler) (string, error) +} + +type GoEnvironment struct { +} + +func (ge *GoEnvironment) RunPipeline(c *Compiler) (string, error) { + srcCodeFilename := TempOutputDir + appendSuffix(c.Filename, c.Language) + //compiledFilename := TempOutputDir + c.Filename + + // Write the source code to a file first, because it determines the dependencies for "go mod tidy" + err := c.languageEnv.WriteCodeToFile(srcCodeFilename, c.SourceCode) + if err != nil { + log.Fatalf("Error writing source code to file: %v", err) + } + + // Sets up go environment with go mod and go mod tidy + c.languageEnv.SetupEnvironment(c.cmdPrefix, c.Dependencies) + + // CheckCompileErrors the code + return c.languageEnv.CheckCompileErrors(c.Filename, c.Language, c.cmdPrefix) +} + +// SetupEnvironment initializes the go environment by creating a go module and running go mod tidy. Panics if it fails. +// Go modules are used to manage dependencies in go projects. +func (ge *GoEnvironment) SetupEnvironment(cmdPrefix string, _ []string) { + // One string + cmdString := cmdPrefix + " go mod init " + TempModuleName + " && go mod tidy" + // Split the string into a slice + cmdSlice := strings.Fields(cmdString) // Fields splits the strings around each instance of one or more consecutive white space characters + + // Make the command + cmd := exec.Command(cmdSlice[0], cmdSlice[1:]...) + // Set its target directory + cmd.Dir = TempOutputDir + // Execute the command + err := cmd.Run() + if err != nil { + removeTempFolders(TempOutputDir) + log.Fatalf("Error initializing go module: %v", err) + } +} + +func (ge *GoEnvironment) CheckCompileErrors(filename string, language Language, cmdPrefix string) (string, error) { + + srcCodeFilename := appendSuffix(filename, language) + compiledFilename := filename + + cmdString := cmdPrefix + " go build -o " + compiledFilename + " " + srcCodeFilename + cmdSlice := strings.Fields(cmdString) // Fields splits the string on white space of variable length + + cmd := exec.Command(cmdSlice[0], cmdSlice[1:]...) + cmd.Dir = TempOutputDir + output, err := cmd.CombinedOutput() + return string(output), err +} + +func (ge *GoEnvironment) WriteCodeToFile(filename, sourceCode string) error { + return os.WriteFile(filename, []byte(sourceCode), 0644) +} + +func InitCompiler(OS OS, language Language, sourceCode string, filename string, dependencies ...string) ICompiler { + compiler := &Compiler{} + compiler.OS = OS + compiler.Language = language + compiler.SourceCode = sourceCode + compiler.Filename = filename + compiler.Dependencies = dependencies + compiler.cmdPrefix = getOsPrefix(OS) + compiler.languageEnv = getLanguageEnv(language) + return compiler + +} + +func getOsPrefix(OS OS) string { + // Set the cmd prefix based on the platform + switch OS { + case Windows: + return "cmd /c " + case Linux, MacOS: + return "" + default: + panic("Unsupported platform") + } +} + +func getLanguageEnv(language Language) ILanguageEnvironment { + switch language { + case Go: + return &GoEnvironment{} + case Rust: + return &RustEnvironment{} + default: + panic("Unsupported language") + } +} + +type RustEnvironment struct { +} + +func (re *RustEnvironment) RunPipeline(c *Compiler) (string, error) { + srcCodeFilename := TempOutputDir + appendSuffix(c.Filename, c.Language) + //compiledFilename := TempOutputDir + c.Filename + + // SetupEnvironment either Go or Rust environment, should be an interface method + c.languageEnv.SetupEnvironment(c.cmdPrefix, c.Dependencies) + + // Write the source code to a file + err := c.languageEnv.WriteCodeToFile(srcCodeFilename, c.SourceCode) + if err != nil { + log.Fatalf("Error writing source code to file: %v", err) + } + + // CheckCompileErrors the code + return c.languageEnv.CheckCompileErrors(c.Filename, c.Language, c.cmdPrefix) +} + +// SetupEnvironment initializes the rust environment by creating a cargo project and adding dependencies. Panics if it fails. +func (re *RustEnvironment) SetupEnvironment(cmdPrefix string, dependencies []string) { + // Initialize the rust cargo project-------------------------------------------------------------------------------- + // Command to initialize a cargo project + cmdString := cmdPrefix + " cargo init --bin" + // Split the string into a slice + cmdSlice := strings.Fields(cmdString) + // Make the command + cmd := exec.Command(cmdSlice[0], cmdSlice[1:]...) + // Set its target directory + cmd.Dir = TempOutputDir + // Execute the command + err := cmd.Run() + if err != nil { + log.Fatalf("Error initializing rust project: %v", err) + } + + // Update rust dependencies in cargo.toml file using cargo add (cargo-edit)----------------------------------------- + + if len(dependencies) == 0 { + return + } + addCommand := cmdPrefix + " cargo add" + addSlice := strings.Fields(addCommand) + addSlice = append(addSlice, dependencies...) + cmd = exec.Command(addSlice[0], addSlice[1:]...) + cmd.Dir = TempOutputDir + err = cmd.Run() + if err != nil { + log.Fatalf("Error adding dependencies: %v", err) + } +} + +// CheckCompileErrors checks the code for errors using cargo check. Returns the output and an error. +// Cargo check does not produce an executable, it only checks the code for errors. +// It also does not need a filename, because it checks the whole cargo project. +func (re *RustEnvironment) CheckCompileErrors(_ string, _ Language, cmdPrefix string) (string, error) { + + cmdString := cmdPrefix + " cargo check" + cmdSlice := strings.Fields(cmdString) + cmd := exec.Command(cmdSlice[0], cmdSlice[1:]...) + cmd.Dir = TempOutputDir + output, err := cmd.CombinedOutput() + return string(output), err +} + +func (re *RustEnvironment) WriteCodeToFile(_, sourceCode string) error { + srcCodeFilename := TempOutputDir + "src/" + appendSuffix("main.rs", Rust) // Rust source code file is always named main.rs + return os.WriteFile(srcCodeFilename, []byte(sourceCode), 0644) +} + +/* +Compile compiles the source code and returns the output and an error. +The compiler pipeline +1. Set up OS and Language +2. Set up the temp folders +3. Write the source code to a file +4. SetupEnvironment the code +5. Return the output and error +*/ +func (c *Compiler) Compile() (string, error) { + // Set up temp folders + setupTempFolders(TempOutputDir) + defer removeTempFolders(TempOutputDir) + + // CheckCompileErrors the code + return c.languageEnv.RunPipeline(c) +} + +// appendSuffix appends the suffix to the filename if it is not already there depending on the language, panics if the language is not supported +func appendSuffix(filename string, language Language) string { + + suffix := "" + switch language { + case Go: + suffix = ".go" + case Rust: + suffix = ".rs" + default: + panic("Unsupported language") + } + + // We check if the filename already has the suffix, if not we append it + if !strings.HasSuffix(filename, suffix) { + filename += suffix + } + return filename +} + +// setupTempFolders creates the temp output directory for compiled files, panics if it fails +func setupTempFolders(tempOutputDir string) { + // 0777 are the permissions for the directory, everyone can read, write and execute + err := os.MkdirAll(tempOutputDir, os.ModePerm) + if err != nil { + panic("Error creating temp output directory:\n\n" + err.Error()) + } +} + +// removeTempFolders removes the temp output directory for compiled files, panics if it fails +func removeTempFolders(tempOutputDir string) { + err := os.RemoveAll(tempOutputDir) + if err != nil { + panic("Error removing temp output directory:\n\n" + err.Error()) + } +} diff --git a/modules/compiler/go-compiler/go_compiler.go b/modules/compiler/go-compiler/go_compiler.go new file mode 100644 index 0000000000000000000000000000000000000000..39cf2f1da6bd573b522baed3099231367edf2200 --- /dev/null +++ b/modules/compiler/go-compiler/go_compiler.go @@ -0,0 +1,19 @@ +package go_compiler + +import ( + "compiler" + "runtime" +) + +// Deprecated: Use `go_compiler_v2.NewGoCompiler` instead +// +// CompileStringToGo tries to compile a string of go code to a go executable, and returns the compiler output and an error. +// The function does not produce any executables, since they are deleted after the function ends. +func CompileStringToGo(code string, filename string) (string, error) { + + // Get the platform + OS := runtime.GOOS + + // SetupEnvironment + return compiler.InitCompiler(compiler.OS(OS), compiler.Go, code, filename).Compile() +} diff --git a/modules/compiler/go-compiler/go_compiler_test.go b/modules/compiler/go-compiler/go_compiler_test.go new file mode 100644 index 0000000000000000000000000000000000000000..8493df160a19682073b1c268b79ea2746c0eaf69 --- /dev/null +++ b/modules/compiler/go-compiler/go_compiler_test.go @@ -0,0 +1,51 @@ +package go_compiler + +import ( + "os" + "testing" +) + +func TestCompileStringToGo(t *testing.T) { + + tests := []struct { + filename string + shouldCompile bool + }{ + { + filename: "should_compile", + shouldCompile: true, + }, + { + filename: "should_not_compile", + shouldCompile: false, + }, + { + filename: "should_compile_with_standard_library_dependencies", + shouldCompile: true, + }, + { + filename: "should_compile_with_external_dependencies", + shouldCompile: true, + }, + } + + for _, test := range tests { + t.Run(test.filename, func(t *testing.T) { + // Read the code from the file + code, err := os.ReadFile(test.filename) + + output, err := CompileStringToGo(string(code), test.filename) + + if err != nil && test.shouldCompile { + t.Errorf("Expected the code to compile, but got an error: %v", err) + } else if err == nil && !test.shouldCompile { + t.Errorf("Expected the code to not compile, but got no error") + } + + // Check if the output is empty when the code shouldn't compile + if output == "" && !test.shouldCompile { + t.Errorf("Expected compiler error output, but got none") + } + }) + } +} diff --git a/modules/compiler/go-compiler/should_compile b/modules/compiler/go-compiler/should_compile new file mode 100644 index 0000000000000000000000000000000000000000..2d35af136296fb470b37ebdc9600e80256d8e94d --- /dev/null +++ b/modules/compiler/go-compiler/should_compile @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +} \ No newline at end of file diff --git a/modules/compiler/go-compiler/should_compile_with_external_dependencies b/modules/compiler/go-compiler/should_compile_with_external_dependencies new file mode 100644 index 0000000000000000000000000000000000000000..3a047aeb0925bc1b611490cfbe7de32c1effb5e3 --- /dev/null +++ b/modules/compiler/go-compiler/should_compile_with_external_dependencies @@ -0,0 +1,37 @@ +// These are dependencies that are **NOT** part of the standard library +package main + +import ( + "fmt" + "golang.org/x/net/http2" + "golang.org/x/crypto/bcrypt" + "net/http" +) + +func main() { + // Setting up a simple HTTP/2 server + srv := &http.Server{ + Addr: ":8080", + } + + // Register a simple handler + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + // Hashing a password + password := "mysecretpassword" + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + http.Error(w, "Could not hash password", http.StatusInternalServerError) + return + } + fmt.Fprintf(w, "Hashed Password: %s\n", hashedPassword) + }) + + // Enable HTTP/2 + http2.ConfigureServer(srv, nil) + + // Start the server + fmt.Println("Starting server on https://localhost:8080") + if err := srv.ListenAndServeTLS("server.crt", "server.key"); err != nil { + fmt.Println("Error starting server:", err) + } +} diff --git a/modules/compiler/go-compiler/should_compile_with_standard_library_dependencies b/modules/compiler/go-compiler/should_compile_with_standard_library_dependencies new file mode 100644 index 0000000000000000000000000000000000000000..7134eea32030c82a7b185f3c0bc2022a08bd0736 --- /dev/null +++ b/modules/compiler/go-compiler/should_compile_with_standard_library_dependencies @@ -0,0 +1,19 @@ +// These are dependencies that are part of the standard library +package main + +import ( + "fmt" + "math/rand" + "time" +) + +func main() { + // Seed the random number generator + rand.Seed(time.Now().UnixNano()) + + // Generate a random number between 1 and 100 + randomNum := rand.Intn(100) + 1 // rand.Intn(100) generates a number from 0 to 99 + + // Print the random number + fmt.Println("Random Number:", randomNum) +} \ No newline at end of file diff --git a/modules/compiler/go-compiler/should_not_compile b/modules/compiler/go-compiler/should_not_compile new file mode 100644 index 0000000000000000000000000000000000000000..8c5b2be4eea8c08de303e168d0e1b001018a0df6 --- /dev/null +++ b/modules/compiler/go-compiler/should_not_compile @@ -0,0 +1,8 @@ +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") + } +} \ No newline at end of file diff --git a/modules/compiler/go.mod b/modules/compiler/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..573df1e3a45fa3973ada3cefe00615dca68e5f58 --- /dev/null +++ b/modules/compiler/go.mod @@ -0,0 +1,3 @@ +module compiler + +go 1.23 \ No newline at end of file diff --git a/modules/compiler/rust-compiler/rust_compiler.go b/modules/compiler/rust-compiler/rust_compiler.go new file mode 100644 index 0000000000000000000000000000000000000000..6e8f2af3fcb4d1a62f9f8a5aad89baaa749ca4ec --- /dev/null +++ b/modules/compiler/rust-compiler/rust_compiler.go @@ -0,0 +1,18 @@ +package rust_compiler + +import ( + "compiler" + "runtime" +) + +// Deprecated: Use `rust_compiler_v2.NewRustCompiler` instead +// +// CompileStringToRust compiles a string of go code to a rust executable +func CompileStringToRust(code string, filename string, dependencies ...string) (string, error) { + + // Get the platform + OS := runtime.GOOS + + // SetupEnvironment + return compiler.InitCompiler(compiler.OS(OS), compiler.Rust, code, filename, dependencies...).Compile() +} diff --git a/modules/compiler/rust-compiler/rust_compiler_test.go b/modules/compiler/rust-compiler/rust_compiler_test.go new file mode 100644 index 0000000000000000000000000000000000000000..53b1483e412a3ea10ca126cb9104afb30fc91b18 --- /dev/null +++ b/modules/compiler/rust-compiler/rust_compiler_test.go @@ -0,0 +1,50 @@ +package rust_compiler + +import ( + "os" + "testing" +) + +func TestCompileStringToRust(t *testing.T) { + + tests := []struct { + filename string + shouldCompile bool + dependencies []string + }{ + { + filename: "should_compile", + shouldCompile: true, + dependencies: nil, + }, + { + filename: "should_not_compile", + shouldCompile: false, + dependencies: nil, + }, + { + filename: "should_compile_with_dependencies", + shouldCompile: true, + dependencies: []string{"rand", "colored"}, + }, + } + + for _, test := range tests { + t.Run(test.filename, func(t *testing.T) { + // Read the code from the file + code, err := os.ReadFile(test.filename) + + output, err := CompileStringToRust(string(code), "main.rs", test.dependencies...) + + if err != nil && test.shouldCompile { + t.Errorf("Expected the code to compile, but got an error: %v", err) + } else if err == nil && !test.shouldCompile { + t.Errorf("Expected the code to not compile, but got no error") + } + // Check if the output is empty when the code shouldn't compile + if output == "" && !test.shouldCompile { + t.Errorf("Expected compiler error output, but got none") + } + }) + } +} diff --git a/modules/compiler/rust-compiler/should_compile b/modules/compiler/rust-compiler/should_compile new file mode 100644 index 0000000000000000000000000000000000000000..19e7b9497a7a8e803bf82f823e5c81746a21a2a6 --- /dev/null +++ b/modules/compiler/rust-compiler/should_compile @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, Should Compile :)"); +} \ No newline at end of file diff --git a/modules/compiler/rust-compiler/should_compile_with_dependencies b/modules/compiler/rust-compiler/should_compile_with_dependencies new file mode 100644 index 0000000000000000000000000000000000000000..5a39cdaf505bdaedeb67a40cc5f2adab1af92233 --- /dev/null +++ b/modules/compiler/rust-compiler/should_compile_with_dependencies @@ -0,0 +1,13 @@ +use rand::Rng; // Import the Rng trait from the rand crate +use colored::*; // Import colored for terminal text coloring + +fn main() { + // Create a random number generator + let mut rng = rand::thread_rng(); + + // Generate a random number between 1 and 100 + let random_number = rng.gen_range(1..=100); // Inclusive range + + // Print the random number in green + println!("Random number between 1 and 100: {}", random_number.to_string().green()); +} \ No newline at end of file diff --git a/modules/compiler/rust-compiler/should_not_compile b/modules/compiler/rust-compiler/should_not_compile new file mode 100644 index 0000000000000000000000000000000000000000..70a13aaa3ebfc5a31dd8134f3c0411f171f35378 --- /dev/null +++ b/modules/compiler/rust-compiler/should_not_compile @@ -0,0 +1,3 @@ +fn main() + println!("Hello, Should Not Compile :(") +} \ No newline at end of file diff --git a/modules/compiler_v2/consts/consts.go b/modules/compiler_v2/consts/consts.go new file mode 100644 index 0000000000000000000000000000000000000000..6ad098a82f4014a73058d285aa93210cdc73dcc2 --- /dev/null +++ b/modules/compiler_v2/consts/consts.go @@ -0,0 +1,3 @@ +package consts + +const TempOutputDir = "tempOutput/" diff --git a/modules/compiler_v2/go.mod b/modules/compiler_v2/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..9671edb6c9a08e72d34bba95d9b996aff2256e75 --- /dev/null +++ b/modules/compiler_v2/go.mod @@ -0,0 +1,3 @@ +module compiler_V2 + +go 1.23 \ No newline at end of file diff --git a/modules/compiler_v2/go_compiler_v2/go_compiler.go b/modules/compiler_v2/go_compiler_v2/go_compiler.go new file mode 100644 index 0000000000000000000000000000000000000000..800797b92fd3d5cd83723cfe5db05a120d91d590 --- /dev/null +++ b/modules/compiler_v2/go_compiler_v2/go_compiler.go @@ -0,0 +1,47 @@ +package go_compiler_v2 + +import ( + "compiler_V2/consts" + "compiler_V2/utils" + "os" +) + +const fileName = "main.go" + +type GoCompiler struct{} + +// NewGoCompiler creates a new GoCompiler +func NewGoCompiler() *GoCompiler { + return &GoCompiler{} +} + +// CheckCompileErrors takes Go source code and checks for compile errors. +// +// The dependencies are handled automatically by go mod and go tidy. +// +// NOTE: Make sure you have an up-to-date Go installed on the system +// +// Returns the output of the compilation and an error if any +func (gb *GoCompiler) CheckCompileErrors(srcCode []byte) ([]byte, error) { + // Make temp folders + utils.SetupTempFolders(consts.TempOutputDir) + defer utils.RemoveTempFolders(consts.TempOutputDir) + + // Write code to file + err := os.WriteFile(consts.TempOutputDir+fileName, srcCode, 0644) + if err != nil { + return nil, err + } + // Init go mod and tidy + cmdString := "go mod init tempOutput && go mod tidy " + + // Run go build + cmdString += " && go build -o main " + fileName + + // Run tests + cmdString += " && go test " + fileName + + cmd := utils.MakeCommand(cmdString) + cmd.Dir = consts.TempOutputDir + return cmd.CombinedOutput() +} diff --git a/modules/compiler_v2/go_compiler_v2/go_compiler_test.go b/modules/compiler_v2/go_compiler_v2/go_compiler_test.go new file mode 100644 index 0000000000000000000000000000000000000000..02ea84c6c7360a29aa4bb14687849b29060767df --- /dev/null +++ b/modules/compiler_v2/go_compiler_v2/go_compiler_test.go @@ -0,0 +1,59 @@ +package go_compiler_v2 + +import ( + "os" + "testing" +) + +func TestCompileStringToGo(t *testing.T) { + + tests := []struct { + filename string + shouldCompile bool + }{ + { + filename: "should_compile", + shouldCompile: true, + }, + { + filename: "should_not_compile", + shouldCompile: false, + }, + { + filename: "should_compile_with_standard_library_dependencies", + shouldCompile: true, + }, + { + filename: "should_compile_with_external_dependencies", + shouldCompile: true, + }, + { + filename: "should_compile_and_run_tests", + shouldCompile: true, + }, + { + filename: "should_compile_with_faulty_test", + shouldCompile: true, + }, + } + + for _, test := range tests { + t.Run(test.filename, func(t *testing.T) { + // Read the code from the file + code, err := os.ReadFile(test.filename) + + output, err := NewGoCompiler().CheckCompileErrors(code) + + if err != nil && test.shouldCompile { + t.Errorf("Expected the code to compile, but got an output: %v \n error: %v", string(output), err) + } else if err == nil && !test.shouldCompile { + t.Errorf("Expected the code to not compile, but got no error") + } + + // Check if the output is empty when the code shouldn't compile + if output == nil && !test.shouldCompile { + t.Errorf("Expected compiler error output, but got none") + } + }) + } +} diff --git a/modules/compiler_v2/go_compiler_v2/should_compile b/modules/compiler_v2/go_compiler_v2/should_compile new file mode 100644 index 0000000000000000000000000000000000000000..2d35af136296fb470b37ebdc9600e80256d8e94d --- /dev/null +++ b/modules/compiler_v2/go_compiler_v2/should_compile @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +} \ No newline at end of file diff --git a/modules/compiler_v2/go_compiler_v2/should_compile_and_run_tests b/modules/compiler_v2/go_compiler_v2/should_compile_and_run_tests new file mode 100644 index 0000000000000000000000000000000000000000..32c15eef9526acc3ee3129281f7650ba6732b35d --- /dev/null +++ b/modules/compiler_v2/go_compiler_v2/should_compile_and_run_tests @@ -0,0 +1,48 @@ +package main + +import ( + "errors" + "fmt" + "testing" +) + +// Divide divides two numbers and returns the result. +// Returns an error if division by zero is attempted. +func Divide(a, b float64) (float64, error) { + if b == 0 { + return 0, errors.New("cannot divide by zero") + } + return a / b, nil +} + +// Test cases for Divide function +func TestDivide(t *testing.T) { + // Test case 1: Normal division + result, err := Divide(10, 2) + if err != nil || result != 5 { + t.Errorf("Expected 5, got %v, error: %v", result, err) + } + + // Test case 2: Division by zero + _, err = Divide(10, 0) + if err == nil { + t.Error("Expected error for division by zero, got nil") + } + + // Test case 3: Division with negative numbers + result, err = Divide(-10, 2) + if err != nil || result != -5 { + t.Errorf("Expected -5, got %v, error: %v", result, err) + } +} + +// main function for demonstration purposes +func main() { + a, b := 10.0, 2.0 + result, err := Divide(a, b) + if err != nil { + fmt.Println("Error:", err) + } else { + fmt.Printf("Result of %.2f / %.2f = %.2f\n", a, b, result) + } +} diff --git a/modules/compiler_v2/go_compiler_v2/should_compile_with_external_dependencies b/modules/compiler_v2/go_compiler_v2/should_compile_with_external_dependencies new file mode 100644 index 0000000000000000000000000000000000000000..3a047aeb0925bc1b611490cfbe7de32c1effb5e3 --- /dev/null +++ b/modules/compiler_v2/go_compiler_v2/should_compile_with_external_dependencies @@ -0,0 +1,37 @@ +// These are dependencies that are **NOT** part of the standard library +package main + +import ( + "fmt" + "golang.org/x/net/http2" + "golang.org/x/crypto/bcrypt" + "net/http" +) + +func main() { + // Setting up a simple HTTP/2 server + srv := &http.Server{ + Addr: ":8080", + } + + // Register a simple handler + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + // Hashing a password + password := "mysecretpassword" + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + http.Error(w, "Could not hash password", http.StatusInternalServerError) + return + } + fmt.Fprintf(w, "Hashed Password: %s\n", hashedPassword) + }) + + // Enable HTTP/2 + http2.ConfigureServer(srv, nil) + + // Start the server + fmt.Println("Starting server on https://localhost:8080") + if err := srv.ListenAndServeTLS("server.crt", "server.key"); err != nil { + fmt.Println("Error starting server:", err) + } +} diff --git a/modules/compiler_v2/go_compiler_v2/should_compile_with_faulty_test b/modules/compiler_v2/go_compiler_v2/should_compile_with_faulty_test new file mode 100644 index 0000000000000000000000000000000000000000..a98891139495585829592e5b2cf2645f43b97dd9 --- /dev/null +++ b/modules/compiler_v2/go_compiler_v2/should_compile_with_faulty_test @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "testing" +) + +// Add adds two integers and returns the result. +func Add(a, b int) int { + return a + b +} + +// Test cases for Add function +func TestAdd(t *testing.T) { + // Test case 1: Normal addition + result := Add(2, 3) + if result != 5 { + t.Errorf("Expected 5, got %v", result) + } + + // Faulty Test case 2: Incorrect expected result + result = Add(2, 2) + if result != 5 { // This is faulty, it should expect 4, not 5 + t.Errorf("Expected 5, got %v", result) + } + + // Test case 3: Adding negative numbers + result = Add(-2, -3) + if result != -5 { + t.Errorf("Expected -5, got %v", result) + } +} + +// main function for demonstration purposes +func main() { + a, b := 2, 3 + result := Add(a, b) + fmt.Printf("Result of %d + %d = %d\n", a, b, result) +} diff --git a/modules/compiler_v2/go_compiler_v2/should_compile_with_standard_library_dependencies b/modules/compiler_v2/go_compiler_v2/should_compile_with_standard_library_dependencies new file mode 100644 index 0000000000000000000000000000000000000000..7134eea32030c82a7b185f3c0bc2022a08bd0736 --- /dev/null +++ b/modules/compiler_v2/go_compiler_v2/should_compile_with_standard_library_dependencies @@ -0,0 +1,19 @@ +// These are dependencies that are part of the standard library +package main + +import ( + "fmt" + "math/rand" + "time" +) + +func main() { + // Seed the random number generator + rand.Seed(time.Now().UnixNano()) + + // Generate a random number between 1 and 100 + randomNum := rand.Intn(100) + 1 // rand.Intn(100) generates a number from 0 to 99 + + // Print the random number + fmt.Println("Random Number:", randomNum) +} \ No newline at end of file diff --git a/modules/compiler_v2/go_compiler_v2/should_not_compile b/modules/compiler_v2/go_compiler_v2/should_not_compile new file mode 100644 index 0000000000000000000000000000000000000000..8c5b2be4eea8c08de303e168d0e1b001018a0df6 --- /dev/null +++ b/modules/compiler_v2/go_compiler_v2/should_not_compile @@ -0,0 +1,8 @@ +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") + } +} \ No newline at end of file diff --git a/modules/compiler_v2/platform/platform.go b/modules/compiler_v2/platform/platform.go new file mode 100644 index 0000000000000000000000000000000000000000..7ca146514fece1c8f6aa0fb6358f36897296d8b9 --- /dev/null +++ b/modules/compiler_v2/platform/platform.go @@ -0,0 +1,11 @@ +package platform + +// OS Operating system type +type OS = string + +// Platform enums +const ( + Windows OS = "windows" + Linux OS = "linux" + MacOS OS = "darwin" +) diff --git a/modules/compiler_v2/rust_compiler_v2/rust_compiler.go b/modules/compiler_v2/rust_compiler_v2/rust_compiler.go new file mode 100644 index 0000000000000000000000000000000000000000..6dd5f45e97eb075e550601709946d43816bfb8a8 --- /dev/null +++ b/modules/compiler_v2/rust_compiler_v2/rust_compiler.go @@ -0,0 +1,63 @@ +package rust_compiler_v2 + +import ( + "compiler_V2/consts" + "compiler_V2/utils" + "os" + "strings" +) + +const fileName = "main.rs" + +type RustCompiler struct{} + +// NewRustCompiler creates a new RustCompiler +func NewRustCompiler() *RustCompiler { + return &RustCompiler{} +} + +// CheckCompileErrors takes Rust source code and the dependencies it requires and checks for compile errors. +// +// The dependencies are optional, and should be name only, not version. +// For instance "rand" and not "rand:0.8.3". Cargo will automatically fetch the latest version. +// +// Returns the output of the compilation and an error if any +func (gb *RustCompiler) CheckCompileErrors(srcCode []byte, dependencies ...string) ([]byte, error) { + // Make temp folders + utils.SetupTempFolders(consts.TempOutputDir) + defer utils.RemoveTempFolders(consts.TempOutputDir) + + // Init cargo + if err := initCargo(); err != nil { + return nil, err + } + + // Write code to file + if err := os.WriteFile(consts.TempOutputDir+"src/"+fileName, srcCode, 0644); err != nil { + return nil, err + } + + cmdString := "" + // Add dependencies + if dependencies != nil { + cmdString = "cargo add " + strings.Join(dependencies, " ") + " &&" + } + + // Run go build + cmdString += " cargo build" + + cmdString += " && cargo test" + + //cmdSlice := strings.Fields(cmdString) + cmd := utils.MakeCommand(cmdString) + cmd.Dir = consts.TempOutputDir + return cmd.CombinedOutput() +} + +// initCargo initializes a cargo project +func initCargo() error { + // Init cargo + cmd := utils.MakeCommand("cargo init --bin") + cmd.Dir = consts.TempOutputDir + return cmd.Run() +} diff --git a/modules/compiler_v2/rust_compiler_v2/rust_compiler_test.go b/modules/compiler_v2/rust_compiler_v2/rust_compiler_test.go new file mode 100644 index 0000000000000000000000000000000000000000..169049b3088a05ff90a5eb72d6ff2ab148c04fda --- /dev/null +++ b/modules/compiler_v2/rust_compiler_v2/rust_compiler_test.go @@ -0,0 +1,60 @@ +package rust_compiler_v2 + +import ( + "os" + "testing" +) + +func TestCompileStringToRust(t *testing.T) { + + tests := []struct { + filename string + shouldCompile bool + dependencies []string + }{ + { + filename: "should_compile", + shouldCompile: true, + dependencies: nil, + }, + { + filename: "should_not_compile", + shouldCompile: false, + dependencies: nil, + }, + { + filename: "should_compile_with_dependencies", + shouldCompile: true, + dependencies: []string{"rand", "colored"}, + }, + { + filename: "should_compile_and_run_tests", + shouldCompile: true, + dependencies: nil, + }, + { + filename: "should_compile_with_faulty_test", + shouldCompile: false, + dependencies: nil, + }, + } + + for _, test := range tests { + t.Run(test.filename, func(t *testing.T) { + // Read the code from the file + code, err := os.ReadFile(test.filename) + + output, err := NewRustCompiler().CheckCompileErrors(code, test.dependencies...) + + if err != nil && test.shouldCompile { + t.Errorf("Expected the code to compile, but got an output: %v \n error: %v", string(output), err) + } else if err == nil && !test.shouldCompile { + t.Errorf("Expected the code to not compile, but got no error") + } + // Check if the output is empty when the code shouldn't compile + if output == nil && !test.shouldCompile { + t.Errorf("Expected compiler error output, but got none") + } + }) + } +} diff --git a/modules/compiler_v2/rust_compiler_v2/should_compile b/modules/compiler_v2/rust_compiler_v2/should_compile new file mode 100644 index 0000000000000000000000000000000000000000..19e7b9497a7a8e803bf82f823e5c81746a21a2a6 --- /dev/null +++ b/modules/compiler_v2/rust_compiler_v2/should_compile @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, Should Compile :)"); +} \ No newline at end of file diff --git a/modules/compiler_v2/rust_compiler_v2/should_compile_and_run_tests b/modules/compiler_v2/rust_compiler_v2/should_compile_and_run_tests new file mode 100644 index 0000000000000000000000000000000000000000..1a75cfa8fdf08d8cec2cff77d4b0d956c7b2e351 --- /dev/null +++ b/modules/compiler_v2/rust_compiler_v2/should_compile_and_run_tests @@ -0,0 +1,36 @@ +fn divide(a: f64, b: f64) -> Result<f64, String> { + if b == 0.0 { + return Err(String::from("Cannot divide by zero")); + } + Ok(a / b) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_divide_positive_numbers() { + let result = divide(10.0, 2.0); + assert_eq!(result.unwrap(), 5.0); + } + + #[test] + fn test_divide_negative_numbers() { + let result = divide(-10.0, 2.0); + assert_eq!(result.unwrap(), -5.0); + } + + #[test] + fn test_divide_by_zero() { + let result = divide(10.0, 0.0); + assert!(result.is_err()); + } +} + +fn main() { + match divide(10.0, 2.0) { + Ok(result) => println!("10.0 divided by 2.0 is {}", result), + Err(e) => println!("Error: {}", e), + } +} diff --git a/modules/compiler_v2/rust_compiler_v2/should_compile_with_dependencies b/modules/compiler_v2/rust_compiler_v2/should_compile_with_dependencies new file mode 100644 index 0000000000000000000000000000000000000000..5a39cdaf505bdaedeb67a40cc5f2adab1af92233 --- /dev/null +++ b/modules/compiler_v2/rust_compiler_v2/should_compile_with_dependencies @@ -0,0 +1,13 @@ +use rand::Rng; // Import the Rng trait from the rand crate +use colored::*; // Import colored for terminal text coloring + +fn main() { + // Create a random number generator + let mut rng = rand::thread_rng(); + + // Generate a random number between 1 and 100 + let random_number = rng.gen_range(1..=100); // Inclusive range + + // Print the random number in green + println!("Random number between 1 and 100: {}", random_number.to_string().green()); +} \ No newline at end of file diff --git a/modules/compiler_v2/rust_compiler_v2/should_compile_with_faulty_test b/modules/compiler_v2/rust_compiler_v2/should_compile_with_faulty_test new file mode 100644 index 0000000000000000000000000000000000000000..d22217d9d7ec70d0638e5ad67bf9f0177cd68030 --- /dev/null +++ b/modules/compiler_v2/rust_compiler_v2/should_compile_with_faulty_test @@ -0,0 +1,32 @@ +// Function to add two numbers +fn add(a: i32, b: i32) -> i32 { + a + b +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add_positive_numbers() { + let result = add(2, 3); + assert_eq!(result, 5); + } + + #[test] + fn test_add_negative_numbers() { + let result = add(-2, -3); + assert_eq!(result, -5); + } + + #[test] + fn test_add_faulty() { + // This test is intentionally faulty + let result = add(2, 2); + assert_eq!(result, 5); // This will fail because 2 + 2 is actually 4 + } +} + +fn main() { + println!("2 + 3 = {}", add(2, 3)); +} diff --git a/modules/compiler_v2/rust_compiler_v2/should_not_compile b/modules/compiler_v2/rust_compiler_v2/should_not_compile new file mode 100644 index 0000000000000000000000000000000000000000..70a13aaa3ebfc5a31dd8134f3c0411f171f35378 --- /dev/null +++ b/modules/compiler_v2/rust_compiler_v2/should_not_compile @@ -0,0 +1,3 @@ +fn main() + println!("Hello, Should Not Compile :(") +} \ No newline at end of file diff --git a/modules/compiler_v2/utils/make_command.go b/modules/compiler_v2/utils/make_command.go new file mode 100644 index 0000000000000000000000000000000000000000..52b8c6fa0e80853010d4bc7b9863c1c9b6749147 --- /dev/null +++ b/modules/compiler_v2/utils/make_command.go @@ -0,0 +1,20 @@ +package utils + +import ( + p "compiler_V2/platform" + "os/exec" + "runtime" +) + +// MakeCommand creates a command based on the runtime platform +func MakeCommand(cmd string) *exec.Cmd { + platform := runtime.GOOS + switch platform { + case p.Windows: + return exec.Command("cmd", "/c", cmd) + case p.Linux, p.MacOS: + return exec.Command("bash", "-c", cmd) + default: + panic("Unsupported platform") + } +} diff --git a/modules/compiler_v2/utils/setup.go b/modules/compiler_v2/utils/setup.go new file mode 100644 index 0000000000000000000000000000000000000000..c7e44cddd67552a2e9374dd8547960c96ba05292 --- /dev/null +++ b/modules/compiler_v2/utils/setup.go @@ -0,0 +1,20 @@ +package utils + +import "os" + +// SetupTempFolders creates the temp output directory for compiled files, panics if it fails +func SetupTempFolders(tempOutputDir string) { + // 0777 are the permissions for the directory, everyone can read, write and execute + err := os.MkdirAll(tempOutputDir, os.ModePerm) + if err != nil { + panic("Error creating temp output directory:\n\n" + err.Error()) + } +} + +// RemoveTempFolders removes the temp output directory for compiled files, panics if it fails +func RemoveTempFolders(tempOutputDir string) { + err := os.RemoveAll(tempOutputDir) + if err != nil { + panic("Error removing temp output directory:\n\n" + err.Error()) + } +}