Skip to content

Commit

Permalink
Merge pull request #3 from onrcayci/dev
Browse files Browse the repository at this point in the history
Version 1.0 Release
  • Loading branch information
Onur Cayci authored May 31, 2021
2 parents 380f0bb + cf09b78 commit cd39ae7
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# VS Code files
.vscode/

# build files
build/
9 changes: 9 additions & 0 deletions cmd/goshell/goshell.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

import "github.com/onrcayci/goshell/internal/goshell"

// Main function of the goshell application.
// Uses the function Shell from the goshell package inside the internal module.
func main() {
goshell.Shell()
}
128 changes: 128 additions & 0 deletions internal/command/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package command

import (
"bufio"
"errors"
"fmt"
"io"
"os"

"github.com/onrcayci/goshell/internal/memory"
"github.com/onrcayci/goshell/internal/parser"
)

// Interpreter takes in the tokenized input slice (argv) and the length of the slice(arc)
// and uses a switch statement to determine which shell command to execute.
// Supported commands: "help", "quit", "set", "print" and "run".
func Interpreter(argc int, argv []string) {
if argc == 0 {
return
} else {
switch argv[0] {
case "help":
help()
case "quit":
quit()
case "set":
err := set(argc, argv)
if err != nil {
fmt.Println(err.Error())
}
case "print":
err := print(argc, argv)
if err != nil {
fmt.Println(err.Error())
}
case "run":
err := run(argc, argv)
if err != nil {
fmt.Println(err.Error())
}
default:
fmt.Printf("%s: command not found\n", argv[0])
}
}
}

// Function help which implements the "help" shell command.
// Returns the help text which outputs all of the supported commands
// and their description.
func help() {
helpText := `Go Shell v1.0
Available Commands:
COMMAND DESCRIPTION
help Displays all the commands
quit Exits / terminates the shell with "Bye!"
set VAR STRING Assigns a value to shell memory
print VAR Displays the STRING assigned to VAR
run SCRIPT.TXT Executes the file SCRIPT.TXT
`
fmt.Println(helpText)
}

// Function quit which implements the "quit" shell command.
// Prints out the message "Bye!" on the screen and end the appliction runtime
// using the function os.Exit(0).
func quit() {
fmt.Println("Bye!")
os.Exit(0)
}

// Function set which implements the "set" shell command.
// Creates and saves a new shell environment variable into the runtime if the
// runtime does not exist. If the variable exists, it updates the value of it
// using the new value passed into the command.
// Returns an error if the number of arguments is less than 3 (i.e. "set VAR VALUE").
func set(argc int, args []string) error {
if argc < 3 {
return errors.New("missing arguments!\nusage: set VAR VALUE")
}
memory.NewMemoryItem(args[1], args[2])
return nil
}

// Function print which implements the "print" shell command.
// Displays the value of a saved shell environment variable if it exists.
// Otherwise, displays the error message "variable does not exist".
// Returns an error if the number of arguments is less than 2 (i.e. "print VAR")
func print(argc int, args []string) error {
if argc < 2 {
return errors.New("missing arguments!\nusage: print VAR")
}
varValue := memory.FindMemoryItem(args[1])
if varValue == "" {
return errors.New("variable does not exist")
}
fmt.Println(varValue)
return nil
}

// Function run which implements the "run" shell command.
// This function enables users to run shell scripts with supported commands.
// Returns an error if the number of arguments is less than 2 (i.e. "run SCRIPT.TXT").
// BUG(onrcayci): The function parser.ParseInput parses the filename into 3 tokens, i.e., [<filename> "." <file extension>].
func run(argc int, args []string) error {
if argc < 2 {
return errors.New("missing arguments!\nusage: run SCRIPT.TXT")
}

// due to the file name tokenization bug, the filename is provided using 3 arguments from args:
// args[1] = file name, args[2] = ".", args[3] = file extension.
script, err := os.Open(args[1] + args[2] + args[3])
if err != nil {
return err
}
reader := bufio.NewReader(script)
for {
line, err := reader.ReadString('\n')
if err == io.EOF {
break
} else if err != nil {
return err
}
argc, argv := parser.ParseInput(line)
Interpreter(argc, argv)
}
return nil
}
26 changes: 26 additions & 0 deletions internal/goshell/shell.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package goshell

import (
"bufio"
"fmt"
"os"

"github.com/onrcayci/goshell/internal/command"
"github.com/onrcayci/goshell/internal/parser"
)

// Shell implements the main shell infinite loop. It accepts input from the user,
// parses the input into tokens and then uses the interpreter function to execute
// the desired command.
func Shell() {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Printf(">> ")
input, err := reader.ReadString('\n')
if err != nil {
panic(err)
}
argc, argv := parser.ParseInput(input)
command.Interpreter(argc, argv)
}
}
35 changes: 35 additions & 0 deletions internal/memory/memory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package memory

// Shell environment variable struct.
// Variable = name of the variable,
// Value = value the variable holds.
type mem struct {
variable string
value string
}

// Shell environment variables slice.
// Holds the environment variables available in the current runtime.
var memory []*mem

// Function to add a new environment variable to the shell memory.
func NewMemoryItem(variable, value string) {
for _, v := range memory {
if v.variable == variable {
v.value = value
return
}
}
memoryItem := &mem{variable, value}
memory = append(memory, memoryItem)
}

// Function to find an environment variable with the given variable name.
func FindMemoryItem(variable string) string {
for _, v := range memory {
if v.variable == variable {
return v.value
}
}
return ""
}
19 changes: 19 additions & 0 deletions internal/parser/parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package parser

import (
"strings"
"text/scanner"
)

// Function to parse the input into string tokens.
// Retunrs the number of tokens (argc) and the tokens (args).
func ParseInput(input string) (int, []string) {
var args []string
var scan scanner.Scanner
scan.Init(strings.NewReader(input))
for token := scan.Scan(); token != scanner.EOF; token = scan.Scan() {
arg := scan.TokenText()
args = append(args, arg)
}
return len(args), args
}

0 comments on commit cd39ae7

Please sign in to comment.