diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..b732288 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "bufio" + "fmt" + "os" + + "github.com/spf13/cobra" + + "git.jfdev.de/JonasFranzDEV/hal/hal" + "git.jfdev.de/JonasFranzDEV/hal/parser" +) + +func Execute() { + if err := rootCommand.Execute(); err != nil { + fmt.Printf("%v\n", err) + os.Exit(1) + } +} + +var rootCommand = &cobra.Command{ + Args: cobra.ExactArgs(1), + Use: "hal", + RunE: runRootCommand, +} + +func init() { + rootCommand.Flags().BoolP("debug", "d", false, "Enable debug mode") +} + +func runRootCommand(cmd *cobra.Command, args []string) error { + filename := args[0] + if stats, err := os.Stat(filename); err != nil || stats.IsDir() { + return err + } + file, err := os.Open(filename) + if err != nil { + return err + } + scanner := bufio.NewScanner(file) + scanner.Split(bufio.ScanLines) + var input []string + for scanner.Scan() { + input = append(input, scanner.Text()) + } + if err := file.Close(); err != nil { + return err + } + program, err := parser.ParseProgram(input) + if err != nil { + return err + } + hal.NewHALModule(program, 256) + return nil +} diff --git a/go.mod b/go.mod index dfc133e..3d564aa 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,17 @@ -module "git.jfdev.de/JonasFranzDEV/hal" \ No newline at end of file +module git.jfdev.de/JonasFranzDEV/hal + +go 1.14 + +require ( + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/mitchellh/mapstructure v1.3.0 // indirect + github.com/pelletier/go-toml v1.8.0 // indirect + github.com/spf13/afero v1.2.2 // indirect + github.com/spf13/cast v1.3.1 // indirect + github.com/spf13/cobra v1.0.0 + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.7.0 // indirect + golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 // indirect + gopkg.in/ini.v1 v1.56.0 // indirect +) diff --git a/hal.go b/hal.go new file mode 100644 index 0000000..86e77b0 --- /dev/null +++ b/hal.go @@ -0,0 +1,7 @@ +package main + +import "git.jfdev.de/JonasFranzDEV/hal/cmd" + +func main() { + cmd.Execute() +} diff --git a/hal/instructions.go b/hal/instructions.go new file mode 100644 index 0000000..2b89143 --- /dev/null +++ b/hal/instructions.go @@ -0,0 +1,72 @@ +package hal + +import ( + "fmt" + "strings" +) + +type ProgrammedInstruction struct { + Instruction *Instruction + Operand int64 +} + +func (pi *ProgrammedInstruction) Execute(module *Module) error { + if pi.Instruction.ExecuteWithOperand != nil { + pi.Instruction.ExecuteWithOperand(module, pi.Operand) + } else if pi.Instruction.Execute != nil { + pi.Instruction.Execute(module) + } else { + return fmt.Errorf("instruction is not implemented") + } + return nil +} + +type Instruction struct { + Name string + ExecuteWithOperand func(module *Module, operand int64) + Execute func(module *Module) +} + +func FindInstructionByName(name string) *Instruction { + for _, instruction := range instructions { + if instruction.Name == strings.ToUpper(name) { + return instruction + } + } + return nil +} + +var instructions = []*Instruction{ + InstructionStart, + InstructionStop, + InstructionIn, + InstructionOut, +} + +var InstructionStart = &Instruction{ + Name: "START", + Execute: func(module *Module) { + // TODO implement + }, +} + +var InstructionStop = &Instruction{ + Name: "STOP", + Execute: func(module *Module) { + // TODO implement + }, +} + +var InstructionOut = &Instruction{ + Name: "OUT", + Execute: func(module *Module) { + // TODO implement + }, +} + +var InstructionIn = &Instruction{ + Name: "IN", + Execute: func(module *Module) { + // TODO implement + }, +} diff --git a/hal/module.go b/hal/module.go new file mode 100644 index 0000000..fd01476 --- /dev/null +++ b/hal/module.go @@ -0,0 +1,33 @@ +package hal + +import "fmt" + +type Program []*ProgrammedInstruction + +type Module struct { + Accumulator int64 + ProgramStorage Program + Register []int64 +} + +func (h *Module) ProgramCounter() int64 { + return h.Register[0] +} + +func (h *Module) Step() error { + instruction := h.ProgramStorage[h.ProgramCounter()] + if instruction == nil { + return fmt.Errorf("instruction not found for program counter = %d", h.ProgramCounter()) + } + return instruction.Execute(h) +} + +func NewHALModule(program Program, registerSize uint64) (*Module, error) { + if registerSize <= 10 { + return nil, fmt.Errorf("register size must be greater then 10 [ registerSize = %d ]", registerSize) + } + return &Module{ + ProgramStorage: program, + Register: make([]int64, registerSize), + }, nil +} diff --git a/parser/program_parser.go b/parser/program_parser.go new file mode 100644 index 0000000..dcffe0c --- /dev/null +++ b/parser/program_parser.go @@ -0,0 +1,47 @@ +package parser + +import ( + "fmt" + "strconv" + "strings" + + "git.jfdev.de/JonasFranzDEV/hal/hal" +) + +// ParseProgram parses an program by the following specification: LINE_NUMBER INSTRUCTION OPERAND(optional) +func ParseProgram(input []string) (hal.Program, error) { + program := make(hal.Program, len(input)) + var err error + for index, line := range input { + program[index], err = parseInstruction(line) + if err != nil { + return nil, fmt.Errorf("error while parsing line %d: %v", index+1, err) + } + } + return program, nil +} + +func parseInstruction(input string) (*hal.ProgrammedInstruction, error) { + args := strings.Split(input, " ") + if len(args) != 0 && len(args) != 1 { + return nil, fmt.Errorf("malformated instruction '%s'", input) + } + instruction := hal.FindInstructionByName(args[0]) + if instruction == nil { + return nil, fmt.Errorf("unknown instruction '%s'", args[0]) + } + programmedInstruction := &hal.ProgrammedInstruction{ + Instruction: instruction, + } + if len(args) == 1 { + operand, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("error while parsing operand for instruction '%s': %v", input, err) + } + if instruction.ExecuteWithOperand == nil { + return nil, fmt.Errorf("instruction '%s' has no operand, got operand %d", instruction.Name, operand) + } + programmedInstruction.Operand = operand + } + return programmedInstruction, nil +}