diff --git a/README.md b/README.md index 3eb81c5..69a45a7 100644 --- a/README.md +++ b/README.md @@ -89,22 +89,24 @@ ACC | The ACC register variable | A variable from the memory label | Jump label [ ] | "Value of" +```${memAddr}``` | Memory address parameter +```${jumpTo}``` | Instruction index or jump label parameter ### Instructions Table Assembly Command | Short Instruction Description | Long Instruction Description | Short Param Description | Long Param Description --- | --- | --- | --- | --- -nop | - | This instruction doesn't perform any action | - | No parameter is required -copy | [ACC] = [variable] | A value from the memory is copied to the ACC register | variable | It's the name of the variable that will be used in the instruction -store | [variable] = [ACC] | The value from the ACC register is stored into memory | variable | It's the name of the variable that will be used in the instruction -add | [ACC] = [ACC] + [variable] | The sum of the value of the ACC register and a value from the memory is stored in the ACC register | variable | It's the name of the variable that will be used in the instruction -sub | [ACC] = [ACC] - [variable] | The difference between the value of the ACC register and a value from the memory is stored in the ACC register | variable | It's the name of the variable that will be used in the instruction -input | [variable] = input value | The input value is copied to the memory | variable | It's the name of the variable that will be used in the instruction -output | Output [variable] | Outputs a value from the memory into the circuit LEDs | variable | It's the name of the variable that will be used in the instruction -kill | Finishes program | When this instruction is encountered, the program is finished and no more instructions will be executed | - | No parameter is required -jmp | Jump to EE | Jump to another line of code | label | The jump label the program will jump to -jg | Jump to EE if [ACC] > 0 | Jump to another line of code if the value of the ACC register is positive | label | The jump label the program will jump to if the condition is right -je | Jump to EE if [ACC] = 0 | Jump to another line of code if the value of the ACC register is zero | label | The jump label the program will jump to if the condition is right -jl | Jump to EE if [ACC] < 0 | Jump to another line of code if the value of the ACC register is negative | label | The jump label the program will jump to if the condition is right +```nop``` | - | This instruction doesn't perform any action | - | No parameter is required +```copy ${memAddr}``` | [ACC] = [variable] | A value from the memory is copied to the ACC register | variable | It's the name of the variable that will be used in the instruction +```store ${memAddr}``` | [variable] = [ACC] | The value from the ACC register is stored into memory | variable | It's the name of the variable that will be used in the instruction +```add ${memAddr}``` | [ACC] = [ACC] + [variable] | The sum of the value of the ACC register and a value from the memory is stored in the ACC register | variable | It's the name of the variable that will be used in the instruction +```sub ${memAddr}``` | [ACC] = [ACC] - [variable] | The difference between the value of the ACC register and a value from the memory is stored in the ACC register | variable | It's the name of the variable that will be used in the instruction +```input ${memAddr}``` | [variable] = input value | The input value is copied to the memory | variable | It's the name of the variable that will be used in the instruction +```output ${memAddr}``` | Output [variable] | Outputs a value from the memory into the circuit LEDs | variable | It's the name of the variable that will be used in the instruction +```kill``` | Finishes program | When this instruction is encountered, the program is finished and no more instructions will be executed | - | No parameter is required +```jmp ${jumpTo}``` | Jump to EE | Jump to another line of code | label | The jump label the program will jump to +```jg ${jumpTo}``` | Jump to EE if [ACC] > 0 | Jump to another line of code if the value of the ACC register is positive | label | The jump label the program will jump to if the condition is right +```je ${jumpTo}``` | Jump to EE if [ACC] = 0 | Jump to another line of code if the value of the ACC register is zero | label | The jump label the program will jump to if the condition is right +```jl ${jumpTo}``` | Jump to EE if [ACC] < 0 | Jump to another line of code if the value of the ACC register is negative | label | The jump label the program will jump to if the condition is right
@@ -137,10 +139,19 @@ Read the specifications below to learn the code syntax. - **Numbers** can be written in hexadecimal in the form of ```0xff``` or in decimal as ```255```; ## Naming Practices -- A **label name** should start with a letter and the rest of the name can have more letters and numbers; +- A **variable or label name** should start with a letter and the rest of the name can have more letters and numbers; - Every name should obey the following regex: ```[a-z][a-zA-Z0-9]*```; - Snake-case is not allowed and the use of camel-case is encouraged. +## Declaring Variables +- The variables should be declared between the imports and instructions. +- Form: ```declare {variableName} {number}``` +- Example + ```sh + declare variable 0xff + ``` +- Remember to follow the [naming practices](#naming-practices) + ## Jump Label - Definition: it marks the line for possible jumps to that line; - Form: ```{labelName}:``` @@ -154,7 +165,7 @@ An instruction line is a line that contains an instruction call. ***Components*** - ```instruction``` is the actual instruction that will be executed, it must be one of the following in the instruction table; -- ```arg``` can be either a jump label or a +- ```arg``` can be a jump label, variable or a number (depending on the instruction) ***Form*** - A instruction line should be in the following form ```{instruction} [arg]```; @@ -171,22 +182,30 @@ Check out [here](#-instructions) the instruction table to know what instructions
# 👨🏻‍💻 Code Example +The following assembly code gets two numbers from input and outputs the sum of them. If the sum is greater than zero it will output zero. + +*ps: Since the ```input``` instruction doesn't wait for a change, expect the output to be zero.* ```sh -# Let's get two numbers as input and print the result of the sum of them +# data inputs +input 0x55 +input 0x56 +# sum +copy 0x55 +add 0x56 +store 0x57 +# output +output 0x57 -procedure procSub - copy 0xff # copy value in the address ff in RAM - store 0x0a # stores the value of ACC in the address 0a -end +# if output higher than zero, it will output zero +copy 0x57 +je finish # if +jl finish # if +output 0xff # [0xff] = 0 since we didn't change it -start: - procSub +finish: - copy 0xff # copy value in the address ff in RAM - store 0x0a # stores the value of ACC in the address 0a - je start # jumps to the 'start' label if ACC is 0 kill ``` diff --git a/commands/instruction/config.go b/commands/instruction/config.go new file mode 100644 index 0000000..1f27cbd --- /dev/null +++ b/commands/instruction/config.go @@ -0,0 +1,26 @@ +package instruction + +import ( + "github.com/open-machine/assembler/commands/instruction/engine" + "github.com/open-machine/assembler/core/commandsassembler" +) + +func GetInstructionsConfig() map[string]commandsassembler.CommandAssembler { + return instructions +} + +// TODO: test if any instruction has the same code +var instructions = map[string]commandsassembler.CommandAssembler{ + "nop": engine.NewNoParamInstruct(0x0), + "copy": engine.NewOneParamInstruct(0x1, !engine.ACCEPT_STRING_PARAM), + "store": engine.NewOneParamInstruct(0x2, !engine.ACCEPT_STRING_PARAM), + "add": engine.NewOneParamInstruct(0x3, !engine.ACCEPT_STRING_PARAM), + "sub": engine.NewOneParamInstruct(0x4, !engine.ACCEPT_STRING_PARAM), + "input": engine.NewOneParamInstruct(0x7, !engine.ACCEPT_STRING_PARAM), + "output": engine.NewOneParamInstruct(0x8, !engine.ACCEPT_STRING_PARAM), + "kill": engine.NewNoParamInstruct(0x9), + "jmp": engine.NewOneParamInstruct(0xA, engine.ACCEPT_STRING_PARAM), + "jg": engine.NewOneParamInstruct(0xB, engine.ACCEPT_STRING_PARAM), + "je": engine.NewOneParamInstruct(0xD, engine.ACCEPT_STRING_PARAM), + "jl": engine.NewOneParamInstruct(0xF, engine.ACCEPT_STRING_PARAM), +} diff --git a/core/instruction/instructionsconfig_test.go b/commands/instruction/config_test.go similarity index 100% rename from core/instruction/instructionsconfig_test.go rename to commands/instruction/config_test.go diff --git a/commands/instruction/engine/index_test.go b/commands/instruction/engine/index_test.go new file mode 100644 index 0000000..6dc4fac --- /dev/null +++ b/commands/instruction/engine/index_test.go @@ -0,0 +1,326 @@ +package engine + +// TODO + +// func TestGetNoParam(t *testing.T) { +// got, err := getParamNoParam("nop", []string{"nop"}) +// if !(err == nil && !got.IsStr && got.Num == 0) { +// t.Errorf("Wrong") +// } +// } + +// func TestGetSecondParamAsInt(t *testing.T) { +// var tests = []struct { +// line string +// expected *data.InstructionParameter +// expectsErr bool +// }{ +// // Decimal Number +// {"mov 1", newCmdIntParam(1), false}, +// // Hexadecimal Number +// {"mov 0x1a", newCmdIntParam(26), false}, +// {"mov 0x001", newCmdIntParam(1), false}, +// {"mov 0x0f", newCmdIntParam(15), false}, +// {"mov 0xff", newCmdIntParam(255), false}, +// {"mov 0x0ff", newCmdIntParam(255), false}, +// // Variable +// {"mov 0xx0ff", nil, true}, +// {"mov x1", nil, true}, +// {"mov 0x1g", nil, true}, +// {"mov 1a", nil, true}, +// // Words +// {"mov", nil, true}, +// {"mov a b", nil, true}, +// {"mov a b c", nil, true}, +// } + +// for i, test := range tests { +// arrayWords := strings.Split(test.line, " ") +// got, err := getSecondWord(arrayWords[0], arrayWords, false) +// gotError := err != nil + +// if test.expectsErr != gotError { +// t.Errorf("[%d] Expected error: %t, Got error: %t", i, test.expectsErr, gotError) +// } + +// if !helper.SafeIsEqualInstructionParamPointer(test.expected, got) { +// t.Errorf("[%d] Expected: %v, Got: %v", i, test.expected, got) +// } + +// if got != nil && got.IsStr { +// t.Errorf("[%d] Expecting int parameter", i) +// } +// } +// } + +// func TestGetSecondParamAsIntOrString(t *testing.T) { +// var tests = []struct { +// line string +// expected *data.InstructionParameter +// expectsErr bool +// }{ +// // Decimal Number +// {"jmp 1", newCmdIntParam(1), false}, +// // Hexadecimal Number +// {"jmp 0x001", newCmdIntParam(1), false}, +// {"jmp 0x0f", newCmdIntParam(15), false}, +// {"jmp 0xff", newCmdIntParam(255), false}, +// {"jmp 0x0ff", newCmdIntParam(255), false}, +// // Variable +// {"jmp a8", newCmdStringParam("a8"), false}, +// {"jmp x1", newCmdStringParam("x1"), false}, +// {"jmp a1", newCmdStringParam("a1"), false}, +// // Errors 1 param +// {"jmp 0xx0ff", nil, true}, +// {"jmp 0x1g", nil, true}, +// {"jmp 1a", nil, true}, +// // Erros amnt params +// {"jmp", nil, true}, +// {"jmp a b", nil, true}, +// {"jmp a b c", nil, true}, +// } + +// for i, test := range tests { +// arrayWords := strings.Split(test.line, " ") +// got, err := getSecondWord(arrayWords[0], arrayWords, true) +// gotError := err != nil + +// if test.expectsErr != gotError { +// t.Errorf("[%d] Expected error: %t, Got error: %t", i, test.expectsErr, gotError) +// } + +// if !helper.SafeIsEqualInstructionParamPointer(test.expected, got) { +// t.Errorf("[%d] Expected: %v, Got: %v", i, test.expected, got) +// } + +// if got != nil && test.expected != nil && got.IsStr != test.expected.IsStr { +// t.Errorf("[%d] Expected IsStr: %t, Got IsStr: %t", i, test.expected.IsStr, got.IsStr) +// } +// } +// } +// func newCmdIntParam(num int) *data.InstructionParameter { +// param := data.NewIntParam(num) +// return ¶m +// } +// func newCmdStringParam(str string) *data.InstructionParameter { +// param := data.NewStringParam(str) +// return ¶m +// } + +// func TestAssembleInstruction(t *testing.T) { +// if len(instructions) != 12 { +// t.Errorf("Tests were not updated") +// } + +// var tests = []struct { +// line string +// expected *data.Instruction +// expectsErr bool +// }{ +// // Success Number +// {"nop", getInstruction(0x0, 0), false}, +// {"copy 0x10", getInstruction(0x1, 16), false}, +// {"store 0x10", getInstruction(0x2, 16), false}, +// {"add 10", getInstruction(0x3, 10), false}, +// {"sub 10", getInstruction(0x4, 10), false}, +// {"input 7", getInstruction(0x7, 7), false}, +// {"output 8", getInstruction(0x8, 8), false}, +// {"kill", getInstruction(0x9, 0), false}, +// {"jmp 0x8", getInstruction(0xA, 8), false}, +// {"jg 0x8", getInstruction(0xB, 8), false}, +// {"je 0x8", getInstruction(0xD, 8), false}, +// {"jl 0x8", getInstruction(0xF, 8), false}, +// // Success Label +// {"jmp label", getInstructionStr(0xA, "label"), false}, +// {"jg label", getInstructionStr(0xB, "label"), false}, +// {"je label", getInstructionStr(0xD, "label"), false}, +// {"jl label", getInstructionStr(0xF, "label"), false}, +// // Fail: Wrong Instruction +// {"nope", nil, true}, +// // Fail: No label as param +// {"copy label", nil, true}, +// {"store label", nil, true}, +// {"add label", nil, true}, +// {"sub label", nil, true}, +// {"input label", nil, true}, +// {"output label", nil, true}, +// // Fail: Wrong param +// {"add 1x10", nil, true}, +// // Fail: Amnt params +// {"kill 0", nil, true}, +// {"output", nil, true}, +// {"output 8 1", nil, true}, +// } + +// for i, test := range tests { +// got, err := AssembleInstruction(test.line) +// gotError := err != nil + +// if test.expectsErr != gotError { +// t.Errorf("[%d] Expected error: %t, Got error: %t", i, test.expectsErr, gotError) +// } +// if !helper.SafeIsEqualInstructionPointer(test.expected, got) { +// t.Errorf("Instruction expected is: %v, Got expected is: %v", test.expected, got) +// } +// } +// } +// func getInstruction(code int, param int) *data.Instruction { +// cmd, _ := data.NewInstruction(code, data.NewIntParam(param)) +// return cmd +// } +// func getInstructionStr(code int, param string) *data.Instruction { +// cmd, _ := data.NewInstruction(code, data.NewStringParam(param)) +// return cmd +// } + +// func TestGetInstructionParams(t *testing.T) { +// var tests = []struct { +// param []string +// expected []string +// }{ +// { +// []string{"mov", "0x1", "1", "label"}, +// []string{"0x1", "1", "label"}, +// }, +// { +// []string{"cp", "to", "here"}, +// []string{"to", "here"}, +// }, +// { +// []string{"cp"}, +// []string{}, +// }, +// } + +// for i, test := range tests { +// got := getInstructionParams(test.param) +// if !reflect.DeepEqual(got, test.expected) { +// t.Errorf("[%d] Expected: %v, Got: %v", i, test.expected, got) +// } +// } +// } + +// func TestCheckAmntWords(t *testing.T) { +// var tests = []struct { +// amntParamsExpected int +// instructionName string +// words []string +// expectedErr bool +// }{ +// // Success: no param +// { +// amntParamsExpected: 0, +// instructionName: "mov", +// words: []string{}, +// expectedErr: false, +// }, +// // Success: one param +// { +// amntParamsExpected: 1, +// instructionName: "mov", +// words: []string{"hello"}, +// expectedErr: false, +// }, +// // Success: two param +// { +// amntParamsExpected: 2, +// instructionName: "mov", +// words: []string{"hello", "there"}, +// expectedErr: false, +// }, +// // Fail: expected more (no param) +// { +// amntParamsExpected: 1, +// instructionName: "mov", +// words: []string{}, +// expectedErr: true, +// }, +// // Fail: expected more (more params) +// { +// amntParamsExpected: 5, +// instructionName: "mov", +// words: []string{"hello", "there", "now"}, +// expectedErr: true, +// }, +// // Fail: expected less +// { +// amntParamsExpected: 0, +// instructionName: "mov", +// words: []string{"hello"}, +// expectedErr: true, +// }, +// } + +// for i, test := range tests { +// err := checkAmntWords(test.amntParamsExpected, test.instructionName, test.words) +// gotErr := err != nil + +// if gotErr != test.expectedErr { +// t.Errorf("[%d] Expected error: %t, Got error: %t", i, test.expectedError, gotErr) +// } +// } +// } + +// func TestAddInstruction(t *testing.T) { +// var tests = []struct { +// code int +// param data.InstructionParameter +// program data.Program +// expectedError bool +// expectedProgram data.Program +// }{ +// // Success +// { +// code: 5, +// param: 1000, +// program: newProgramPointer([]data.Instruction{ +// newInstruction(1, 2), +// newInstruction(3, 4), +// }), +// expectedError: false, +// expectedProgram: newProgramPointer([]data.Instruction{ +// newInstruction(1, 2), +// newInstruction(3, 4), +// newInstruction(5, 1000), +// }), +// }, +// // Fail +// { +// code: 1000, +// param: 200, +// program: newProgramPointer([]data.Instruction{ +// newInstruction(1, 2), +// newInstruction(3, 4), +// }), +// expectedError: true, +// expectedProgram: newProgramPointer([]data.Instruction{ +// newInstruction(1, 2), +// newInstruction(3, 4), +// }), +// }, +// } + +// for i, test := range tests { +// err := addInstruction(test.code, test.param, test.program) +// gotErr := err != nil + +// if gotErr != test.expectedError { +// t.Errorf("[%d] Expected error: %t, Got error: %t", i, test.expectedError, gotErr) +// } +// if test.program != test.expectedProgram { +// t.Errorf("[%d] Expected program: %v, Got program: %v", i, test.expectedProgram, test.program) +// } +// } +// } + +// func newInstruction(code int, numParam int) data.Instruction { +// config.Testing = true +// param := data.NewParamTest(numParam, "", true) +// instruc, _ := data.NewInstruction(code, param) +// return instruc +// } + +// func newProgramPointer(instructions []data.Instruction) *data.Program { +// prog := data.ProgramFromInstructionsAndLabels(instructions, map[string]int{}) +// return &prog +// } diff --git a/commands/instruction/engine/noparam.go b/commands/instruction/engine/noparam.go new file mode 100644 index 0000000..22f77d7 --- /dev/null +++ b/commands/instruction/engine/noparam.go @@ -0,0 +1,29 @@ +package engine + +import ( + "strings" + + "github.com/open-machine/assembler/config/myerrors" + "github.com/open-machine/assembler/data" +) + +const DEFAULT_INT_PARAM = 0 + +type NoParamInstruct struct { + code int +} + +func NewNoParamInstruct(c int) NoParamInstruct { + return NoParamInstruct{code: c} +} + +func (assembler NoParamInstruct) Assemble(instructionName string, line string, program *data.Program) *myerrors.CustomError { + words := strings.Split(line, " ") + + errAmntWords := checkAmntWords(0, instructionName, words) + if errAmntWords != nil { + return errAmntWords + } + + return addInstructionIntParam(assembler.code, DEFAULT_INT_PARAM, program) +} diff --git a/commands/instruction/engine/oneparam.go b/commands/instruction/engine/oneparam.go new file mode 100644 index 0000000..fc452d0 --- /dev/null +++ b/commands/instruction/engine/oneparam.go @@ -0,0 +1,49 @@ +package engine + +import ( + "strings" + + "github.com/open-machine/assembler/config/myerrors" + "github.com/open-machine/assembler/data" + "github.com/open-machine/assembler/utils" +) + +const ACCEPT_STRING_PARAM = true + +type OneParamInstruct struct { + code int + acceptStringParam bool +} + +func NewOneParamInstruct(code int, acceptStringParam bool) OneParamInstruct { + return OneParamInstruct{code: code, acceptStringParam: acceptStringParam} +} + +func (assembler OneParamInstruct) Assemble(instructionName string, line string, program *data.Program) *myerrors.CustomError { + words := strings.Split(line, " ") + + errAmntWords := checkAmntWords(1, instructionName, words) + if errAmntWords != nil { + return errAmntWords + } + + strParam := words[1] + + if assembler.acceptStringParam && utils.IsValidName(strParam) { + return addInstructionStrParam(assembler.code, strParam, program) + } + + num, err := utils.StrToPositiveInt(strParam) + + if err != nil { + var customMsgErr error + if assembler.acceptStringParam { + customMsgErr = myerrors.InvalidParamLabelOrInt(strParam, err) + } else { + customMsgErr = myerrors.InvalidParamInt(strParam, err) + } + return myerrors.NewCodeError(customMsgErr) + } + + return addInstructionIntParam(assembler.code, num, program) +} diff --git a/commands/instruction/engine/utils.go b/commands/instruction/engine/utils.go new file mode 100644 index 0000000..98b1416 --- /dev/null +++ b/commands/instruction/engine/utils.go @@ -0,0 +1,54 @@ +package engine + +import ( + "github.com/open-machine/assembler/config/myerrors" + "github.com/open-machine/assembler/data" +) + +func checkAmntWords(amntParamsExpected int, instructionName string, words []string) *myerrors.CustomError { + params := getInstructionParams(words) + amntParamsReceived := len(params) + if amntParamsReceived < amntParamsExpected { + err := myerrors.WrongNumberOfParamsError(instructionName, amntParamsExpected, amntParamsReceived, params) + return myerrors.NewCodeError(err) + } else if amntParamsReceived > amntParamsExpected { + err := myerrors.WrongNumberOfParamsError(instructionName, amntParamsExpected, amntParamsReceived, params) + return myerrors.NewCodeError(err) + } + + return nil +} + +func addInstructionStrParam(code int, strParam string, program *data.Program) *myerrors.CustomError { + param, err := data.NewStringParam(strParam) + // TODO: test + if err != nil { + return myerrors.NewCodeError(err) + } + + return addInstruction(code, *param, program) +} + +func addInstructionIntParam(code int, intParam int, program *data.Program) *myerrors.CustomError { + param, err := data.NewIntParam(intParam) + // TODO: test + if err != nil { + return myerrors.NewCodeError(err) + } + + return addInstruction(code, *param, program) +} + +func addInstruction(code int, param data.InstructionParameter, program *data.Program) *myerrors.CustomError { + instruction, errNew := data.NewInstruction(code, param) + if errNew != nil { + return myerrors.NewCodeError(errNew) + } + + program.AddInstruction(*instruction) + return nil +} + +func getInstructionParams(words []string) []string { + return words[1:] +} diff --git a/core/label/label.go b/commands/label/label.go similarity index 100% rename from core/label/label.go rename to commands/label/label.go diff --git a/core/label/label_test.go b/commands/label/label_test.go similarity index 100% rename from core/label/label_test.go rename to commands/label/label_test.go diff --git a/config/myerrors/param.go b/config/myerrors/param.go index 8832b58..5a45918 100644 --- a/config/myerrors/param.go +++ b/config/myerrors/param.go @@ -6,6 +6,10 @@ func ParamOverflow(param int, amntBits int) error { return fmt.Errorf("Param '%b' overflows %d bits", param, amntBits) } +func ThereIsNoSpaceLeft(err error) error { + return fmt.Errorf("There is no space left: %s", err.Error()) +} + func InvalidParamLabelOrInt(param string, err error) error { return fmt.Errorf("Param '%s' is not a valid label nor a valid number (Conversion error: %s)", param, err.Error()) } diff --git a/config/myerrors/setup.go b/config/myerrors/setup.go new file mode 100644 index 0000000..73626d1 --- /dev/null +++ b/config/myerrors/setup.go @@ -0,0 +1,9 @@ +package myerrors + +import ( + "fmt" +) + +func CommandAlreadyExistError(instructionStr string) error { + return fmt.Errorf("Map key '%s' already exists, there can't be any commands with the same name", instructionStr) +} diff --git a/core/commandsassembler/config.go b/core/commandsassembler/config.go new file mode 100644 index 0000000..88429f5 --- /dev/null +++ b/core/commandsassembler/config.go @@ -0,0 +1,32 @@ +package commandsassembler + +import ( + "github.com/open-machine/assembler/config/myerrors" +) + +type AssemblerConfig struct { + mapAssemblers map[string]CommandAssembler + regexAssemblers []RegexCommandAssembler + last *CommandAssembler +} + +func NewAssemblerConfig() AssemblerConfig { + return AssemblerConfig{mapAssemblers: map[string]CommandAssembler{}, regexAssemblers: []RegexCommandAssembler{}, last: nil} +} + +// TODO: check every command if it is only accepted in only one regex (in the beginning test if the number of tests is equal to the setup) + +func (a *AssemblerConfig) AppendRegexAssembler(assembler RegexCommandAssembler) *myerrors.CustomError { + a.regexAssemblers = append(a.regexAssemblers, assembler) + return nil +} + +func (a *AssemblerConfig) AppendMapAssembler(key string, assembler CommandAssembler) *myerrors.CustomError { + _, exists := a.mapAssemblers[key] + if exists { + return myerrors.NewAssemblerError(myerrors.CommandAlreadyExistError(key)) + } + + a.mapAssemblers[key] = assembler + return nil +} diff --git a/core/commandsassembler/index.go b/core/commandsassembler/index.go new file mode 100644 index 0000000..731a395 --- /dev/null +++ b/core/commandsassembler/index.go @@ -0,0 +1,10 @@ +package commandsassembler + +import ( + "github.com/open-machine/assembler/config/myerrors" + "github.com/open-machine/assembler/data" +) + +type CommandAssembler interface { + Assemble(mapKey string, line string, program *data.Program) *myerrors.CustomError +} diff --git a/core/commandsassembler/regexcmdassembler.go b/core/commandsassembler/regexcmdassembler.go new file mode 100644 index 0000000..ebd685c --- /dev/null +++ b/core/commandsassembler/regexcmdassembler.go @@ -0,0 +1,23 @@ +package commandsassembler + +import ( + "github.com/open-machine/assembler/config/myerrors" + "github.com/open-machine/assembler/data" +) + +type RegexCommandAssembler struct { + regex string + fun func(mapKey string, line string, program *data.Program) *myerrors.CustomError +} + +func NewRegexCommandAssembler(regex string, f func(mapKey string, line string, program *data.Program) *myerrors.CustomError) RegexCommandAssembler { + return RegexCommandAssembler{regex: regex, fun: f} +} + +func RegexAssemblerFrom(regex string, assembler CommandAssembler) RegexCommandAssembler { + return RegexCommandAssembler{regex: regex, fun: assembler.Assemble} +} + +func (regexassemble *RegexCommandAssembler) Assemble(k string, l string, p *data.Program) *myerrors.CustomError { + return regexassemble.fun(k, l, p) +} diff --git a/core/comment/index.go b/core/comment/index.go deleted file mode 100644 index a47b84a..0000000 --- a/core/comment/index.go +++ /dev/null @@ -1,13 +0,0 @@ -package comment - -import ( - "strings" -) - -func RemoveComment(line string) string { - index := strings.Index(line, "#") - if index < 0 { - return line - } - return line[:index] -} diff --git a/core/comment/index_test.go b/core/comment/index_test.go deleted file mode 100644 index 9e86a02..0000000 --- a/core/comment/index_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package comment - -import ( - "testing" -) - -func TestRemoveComment(t *testing.T) { - tests := []struct { - param string - expected string - }{ - {"", ""}, - {"copy 0x0", "copy 0x0"}, - {"# Hello World ", ""}, - {"copy 0x0 # Hello World", "copy 0x0 "}, - {"luca dillenburg doing stuff # Hello World", "luca dillenburg doing stuff "}, - } - - for _, test := range tests { - got := RemoveComment(test.param) - if got != test.expected { - t.Errorf("Comment removed wrongly. Expected '%s', Got: '%s'", test.expected, test.param) - } - } -} diff --git a/core/fileio/progrfromfile.go b/core/fileio/progrfromfile.go new file mode 100644 index 0000000..4984cf1 --- /dev/null +++ b/core/fileio/progrfromfile.go @@ -0,0 +1,46 @@ +package fileio + +import ( + "bufio" + "fmt" + "io" + + "github.com/open-machine/assembler/utils" + + "github.com/open-machine/assembler/data" + "github.com/open-machine/assembler/helper" +) + +func ProgramFromFile(file utils.MyFileInterface) *data.Program { + reader := bufio.NewReader(file.Reader()) + lineIndex := 1 + program := data.NewProgram(0) + + successful := true + + for { + line, errRead := reader.ReadString('\n') + + if errRead != nil && errRead != io.EOF { + helper.LogOtherError(fmt.Sprintf("Error while reading file. Error: %s", errRead.Error())) + return nil + } + + errAssemble := assembleEntireLine(line, &program) + + if errAssemble != nil { + successful = false + helper.LogErrorInLine(*errAssemble, lineIndex, line) + } + + if errRead == io.EOF { + break + } + lineIndex++ + } + + if !successful { + return nil + } + return &program +} diff --git a/core/readwriteprogram_test.go b/core/fileio/progrfromfile_test.go similarity index 55% rename from core/readwriteprogram_test.go rename to core/fileio/progrfromfile_test.go index 535dad9..8d8adaf 100644 --- a/core/readwriteprogram_test.go +++ b/core/fileio/progrfromfile_test.go @@ -1,8 +1,7 @@ -package core +package fileio import ( "bytes" - "reflect" "strings" "testing" @@ -122,7 +121,7 @@ func TestProgramFromFile(t *testing.T) { config.Err = new(bytes.Buffer) f := utils.NewMyBufferAsFile(strings.NewReader(str), "file.asm") - got := programFromFile(&f) + got := ProgramFromFile(&f) if !helper.SafeIsEqualProgramPointer(test.expected, got) { t.Errorf("[%d] Expected: %v, Got: %v", i, test.expected, got) @@ -140,81 +139,3 @@ func newProgramPointer(instructions []data.Instruction, jumpLabelsDict map[strin prog := data.ProgramFromInstructionsAndLabels(instructions, jumpLabelsDict) return &prog } - -func TestWriteAssembledFile(t *testing.T) { - config.Out = new(bytes.Buffer) - config.Err = new(bytes.Buffer) - - config.Testing = true - - var tests = []struct { - param data.Program - expectedFileStr string - expectedCode int - expectsErr bool - }{ - { - data.ProgramFromInstructionsAndLabels([]data.Instruction{ - *newInstruction(0x2, 1), - *newInstruction(0x2, 12), - }, map[string]int{}), - "v2.0 raw\n00002001200c", - 0, - false, - }, - { - data.ProgramFromInstructionsAndLabels([]data.Instruction{ - *newInstruction(0x2, 1), - *newInstruction(0x2, 12), - *newInstruction(0xD, 15), - }, map[string]int{}), - "v2.0 raw\n00002001200cd00f", - 0, - false, - }, - { - data.ProgramFromInstructionsAndLabels([]data.Instruction{ - *newInstruction(0x2, 1), - *data.NewInstructionTest(0x222, data.NewIntParam(12)), - *newInstruction(0xD, 115), - }, map[string]int{}), - "", - 1, - true, - }, - { - data.ProgramFromInstructionsAndLabels([]data.Instruction{ - *newInstruction(0x2, 1), - *data.NewInstructionTest(0x2, data.NewStringParam("a")), - }, map[string]int{}), - "", - 1, - true, - }, - } - - for i, test := range tests { - fileWriter := new(bytes.Buffer) - got := writeExecProgram(test.param, "File", fileWriter) - - if !reflect.DeepEqual(test.expectedCode, got) { - t.Errorf("[%d] Expected: %v, Got: %v", i, test.expectedCode, got) - } - - gotFileStr := fileWriter.String() - if gotFileStr != test.expectedFileStr { - t.Errorf("[%d] Expected file str: %v, Got file str: %v", i, test.expectedFileStr, gotFileStr) - } - - stderrStr := config.Err.(*bytes.Buffer).String() - gotErr := stderrStr != "" - if test.expectsErr != gotErr { - t.Errorf("[%d] Expected error: %t, Got error: %t // ", i, test.expectsErr, gotErr) - t.Errorf("\t\t StdErr: %s", stderrStr) - } - } -} -func newInstruction(cmdCode int, param int) *data.Instruction { - got, _ := data.NewInstruction(cmdCode, data.NewIntParam(param)) - return got -} diff --git a/core/fileio/writeexec.go b/core/fileio/writeexec.go new file mode 100644 index 0000000..ee1625a --- /dev/null +++ b/core/fileio/writeexec.go @@ -0,0 +1,34 @@ +package fileio + +import ( + "bufio" + "fmt" + "io" + + "github.com/open-machine/assembler/config" + "github.com/open-machine/assembler/data" + "github.com/open-machine/assembler/helper" +) + +func WriteExecProgram(program data.Program, execFileName string, execFile io.Writer) int { + writer := bufio.NewWriter(execFile) + defer writer.Flush() + + execStr, errs := program.ToExecuter() + + if len(errs) > 0 { + for _, err := range errs { + // TODO: infrastructure to get line + helper.LogErrorInLine(err, 0, "") + } + return config.FailStatus + } + + _, err := writer.WriteString(execStr) + if err != nil { + helper.LogOtherError(fmt.Sprintf("Could not write to file %s \n", execFileName)) + return config.FailStatus + } + + return config.SuccessStatus +} diff --git a/core/fileio/writeexec_test.go b/core/fileio/writeexec_test.go new file mode 100644 index 0000000..67a891e --- /dev/null +++ b/core/fileio/writeexec_test.go @@ -0,0 +1,95 @@ +package fileio + +import ( + "bytes" + "reflect" + "testing" + + "github.com/open-machine/assembler/config" + "github.com/open-machine/assembler/data" +) + +func TestWriteAssembledFile(t *testing.T) { + config.Out = new(bytes.Buffer) + config.Err = new(bytes.Buffer) + + config.Testing = true + + var tests = []struct { + param data.Program + expectedFileStr string + expectedCode int + expectsErr bool + }{ + { + data.ProgramFromInstructionsAndLabels([]data.Instruction{ + *newInstruction(0x2, 1), + *newInstruction(0x2, 12), + }, map[string]int{}), + "v2.0 raw\n00002001200c", + 0, + false, + }, + { + data.ProgramFromInstructionsAndLabels([]data.Instruction{ + *newInstruction(0x2, 1), + *newInstruction(0x2, 12), + *newInstruction(0xD, 15), + }, map[string]int{}), + "v2.0 raw\n00002001200cd00f", + 0, + false, + }, + { + data.ProgramFromInstructionsAndLabels([]data.Instruction{ + *newInstruction(0x2, 1), + *newInstruction(0x222, 12), + *newInstruction(0xD, 115), + }, map[string]int{}), + "", + 1, + true, + }, + { + data.ProgramFromInstructionsAndLabels([]data.Instruction{ + *newInstruction(0x2, 1), + *newInstruction(0x2, 1), + *newInstructionStr(0x2, "a"), + }, map[string]int{}), + "", + 1, + true, + }, + } + + for i, test := range tests { + fileWriter := new(bytes.Buffer) + got := WriteExecProgram(test.param, "File", fileWriter) + + if !reflect.DeepEqual(test.expectedCode, got) { + t.Errorf("[%d] Expected: %v, Got: %v", i, test.expectedCode, got) + } + + gotFileStr := fileWriter.String() + if gotFileStr != test.expectedFileStr { + t.Errorf("[%d] Expected file str: %v, Got file str: %v", i, test.expectedFileStr, gotFileStr) + } + + stderrStr := config.Err.(*bytes.Buffer).String() + gotErr := stderrStr != "" + if test.expectsErr != gotErr { + t.Errorf("[%d] Expected error: %t, Got error: %t // ", i, test.expectsErr, gotErr) + t.Errorf("\t\t StdErr: %s", stderrStr) + } + } +} +func newInstruction(cmdCode int, intParam int) *data.Instruction { + param, _ := data.NewIntParam(intParam) + got, _ := data.NewInstruction(cmdCode, *param) + return got +} +func newInstructionStr(cmdCode int, strParam string) *data.Instruction { + param, _ := data.NewStringParam(strParam) + got, _ := data.NewInstruction(cmdCode, *param) + return got +} diff --git a/core/index.go b/core/index.go index 27c0935..b8544af 100644 --- a/core/index.go +++ b/core/index.go @@ -5,6 +5,7 @@ import ( "io" "os" + "github.com/open-machine/assembler/core/fileio" "github.com/open-machine/assembler/utils" "github.com/open-machine/assembler/config" @@ -53,7 +54,7 @@ func AssembleFileAux(path string, execFileNameParam *string, ioReaderFromPath io } defer file.Close() - ptrProgram := programFromFile(file) + ptrProgram := fileio.ProgramFromFile(file) if ptrProgram == nil { return config.FailStatus, "" } @@ -67,8 +68,6 @@ func AssembleFileAux(path string, execFileNameParam *string, ioReaderFromPath io return config.FailStatus, "" } - // TODO: verify execFileName as possible name or not ([a-zA-Z_-]+) - var execFileName string if execFileNameParam == nil { execFileName = helper.FileNameWithoutExtension(file.Name()) @@ -82,7 +81,7 @@ func AssembleFileAux(path string, execFileNameParam *string, ioReaderFromPath io return config.FailStatus, "" } - writeStatus := writeExecProgram(*ptrProgram, execFileName, execFile) + writeStatus := fileio.WriteExecProgram(*ptrProgram, execFileName, execFile) if writeStatus == config.FailStatus { return config.FailStatus, "" } diff --git a/core/instruction/index.go b/core/instruction/index.go deleted file mode 100644 index 4b5650a..0000000 --- a/core/instruction/index.go +++ /dev/null @@ -1,86 +0,0 @@ -package instruction - -import ( - "strings" - - "github.com/open-machine/assembler/config/myerrors" - "github.com/open-machine/assembler/data" - "github.com/open-machine/assembler/utils" -) - -func AssembleInstruction(line string) (*data.Instruction, *myerrors.CustomError) { - arrayWords := strings.Split(line, " ") - instructionName := arrayWords[0] - - instructionConfig, exists := instructions[instructionName] - if !exists { - err := myerrors.InstructionDoesNotExistError(instructionName) - return nil, myerrors.NewCodeError(err) - } - - param, paramErr := instructionConfig.getParam(instructionName, arrayWords) - if paramErr != nil { - return nil, paramErr - } - - instructionPointer, customErr := data.NewInstruction(instructionConfig.code, *param) - return instructionPointer, customErr -} - -func getParamNoParam(instructionName string, words []string) (*data.InstructionParameter, *myerrors.CustomError) { - if len(words) != 1 { - remainingParams := getInstructionParams(words) - err := myerrors.WrongNumberOfParamsError(instructionName, 0, len(remainingParams), remainingParams) - return nil, myerrors.NewCodeError(err) - } - - param := data.NewIntParam(0) - return ¶m, nil -} - -func getSecondWordAsInt(instructionName string, words []string) (*data.InstructionParameter, *myerrors.CustomError) { - return getSecondWord(instructionName, words, false) -} - -func getSecondWordAsIntOrString(instructionName string, words []string) (*data.InstructionParameter, *myerrors.CustomError) { - return getSecondWord(instructionName, words, true) -} - -func getSecondWord(instructionName string, words []string, acceptStringParam bool) (*data.InstructionParameter, *myerrors.CustomError) { - if len(words) != 2 { - if len(words) < 2 { - err := myerrors.WrongNumberOfParamsError(instructionName, 1, 0, []string{}) - return nil, myerrors.NewCodeError(err) - } - - remainingParams := getInstructionParams(words) - err := myerrors.WrongNumberOfParamsError(instructionName, 1, len(remainingParams), remainingParams) - return nil, myerrors.NewCodeError(err) - } - - strParam := words[1] - - if acceptStringParam && utils.IsValidName(strParam) { - param := data.NewStringParam(strParam) - return ¶m, nil - } - - num, err := utils.StrToPositiveInt(strParam) - - if err != nil { - var customMsgErr error - if acceptStringParam { - customMsgErr = myerrors.InvalidParamLabelOrInt(strParam, err) - } else { - customMsgErr = myerrors.InvalidParamInt(strParam, err) - } - return nil, myerrors.NewCodeError(customMsgErr) - } - - param := data.NewIntParam(num) - return ¶m, nil -} - -func getInstructionParams(words []string) []string { - return words[1:] -} diff --git a/core/instruction/index_test.go b/core/instruction/index_test.go deleted file mode 100644 index 905c4c7..0000000 --- a/core/instruction/index_test.go +++ /dev/null @@ -1,189 +0,0 @@ -package instruction - -import ( - "reflect" - "strings" - "testing" - - "github.com/open-machine/assembler/data" - "github.com/open-machine/assembler/helper" -) - -func TestGetNoParam(t *testing.T) { - got, err := getParamNoParam("nop", []string{"nop"}) - if !(err == nil && !got.IsStr && got.Num == 0) { - t.Errorf("Wrong") - } -} - -func TestGetSecondParamAsInt(t *testing.T) { - var tests = []struct { - line string - expected *data.InstructionParameter - expectsErr bool - }{ - // Decimal Number - {"mov 1", newCmdIntParam(1), false}, - // Hexadecimal Number - {"mov 0x1a", newCmdIntParam(26), false}, - {"mov 0x001", newCmdIntParam(1), false}, - {"mov 0x0f", newCmdIntParam(15), false}, - {"mov 0xff", newCmdIntParam(255), false}, - {"mov 0x0ff", newCmdIntParam(255), false}, - // Variable - {"mov 0xx0ff", nil, true}, - {"mov x1", nil, true}, - {"mov 0x1g", nil, true}, - {"mov 1a", nil, true}, - // Words - {"mov", nil, true}, - {"mov a b", nil, true}, - {"mov a b c", nil, true}, - } - - for i, test := range tests { - arrayWords := strings.Split(test.line, " ") - got, err := getSecondWord(arrayWords[0], arrayWords, false) - gotError := err != nil - - if test.expectsErr != gotError { - t.Errorf("[%d] Expected error: %t, Got error: %t", i, test.expectsErr, gotError) - } - - if !helper.SafeIsEqualInstructionParamPointer(test.expected, got) { - t.Errorf("[%d] Expected: %v, Got: %v", i, test.expected, got) - } - - if got != nil && got.IsStr { - t.Errorf("[%d] Expecting int parameter", i) - } - } -} - -func TestGetSecondParamAsIntOrString(t *testing.T) { - var tests = []struct { - line string - expected *data.InstructionParameter - expectsErr bool - }{ - // Decimal Number - {"jmp 1", newCmdIntParam(1), false}, - // Hexadecimal Number - {"jmp 0x001", newCmdIntParam(1), false}, - {"jmp 0x0f", newCmdIntParam(15), false}, - {"jmp 0xff", newCmdIntParam(255), false}, - {"jmp 0x0ff", newCmdIntParam(255), false}, - // Variable - {"jmp a8", newCmdStringParam("a8"), false}, - {"jmp x1", newCmdStringParam("x1"), false}, - {"jmp a1", newCmdStringParam("a1"), false}, - // Errors 1 param - {"jmp 0xx0ff", nil, true}, - {"jmp 0x1g", nil, true}, - {"jmp 1a", nil, true}, - // Erros amnt params - {"jmp", nil, true}, - {"jmp a b", nil, true}, - {"jmp a b c", nil, true}, - } - - for i, test := range tests { - arrayWords := strings.Split(test.line, " ") - got, err := getSecondWord(arrayWords[0], arrayWords, true) - gotError := err != nil - - if test.expectsErr != gotError { - t.Errorf("[%d] Expected error: %t, Got error: %t", i, test.expectsErr, gotError) - } - - if !helper.SafeIsEqualInstructionParamPointer(test.expected, got) { - t.Errorf("[%d] Expected: %v, Got: %v", i, test.expected, got) - } - - if got != nil && test.expected != nil && got.IsStr != test.expected.IsStr { - t.Errorf("[%d] Expected IsStr: %t, Got IsStr: %t", i, test.expected.IsStr, got.IsStr) - } - } -} -func newCmdIntParam(num int) *data.InstructionParameter { - param := data.NewIntParam(num) - return ¶m -} -func newCmdStringParam(str string) *data.InstructionParameter { - param := data.NewStringParam(str) - return ¶m -} - -func TestAssembleInstruction(t *testing.T) { - if len(instructions) != 12 { - t.Errorf("Tests were not updated") - } - - var tests = []struct { - line string - expected *data.Instruction - expectsErr bool - }{ - // Success Number - {"nop", getInstruction(0x0, 0), false}, - {"copy 0x10", getInstruction(0x1, 16), false}, - {"store 0x10", getInstruction(0x2, 16), false}, - {"add 10", getInstruction(0x3, 10), false}, - {"sub 10", getInstruction(0x4, 10), false}, - {"input 7", getInstruction(0x7, 7), false}, - {"output 8", getInstruction(0x8, 8), false}, - {"kill", getInstruction(0x9, 0), false}, - {"jmp 0x8", getInstruction(0xA, 8), false}, - {"jg 0x8", getInstruction(0xB, 8), false}, - {"je 0x8", getInstruction(0xD, 8), false}, - {"jl 0x8", getInstruction(0xF, 8), false}, - // Success Label - {"jmp label", getInstructionStr(0xA, "label"), false}, - {"jg label", getInstructionStr(0xB, "label"), false}, - {"je label", getInstructionStr(0xD, "label"), false}, - {"jl label", getInstructionStr(0xF, "label"), false}, - // Fail: Wrong Instruction - {"nope", nil, true}, - // Fail: No label as param - {"copy label", nil, true}, - {"store label", nil, true}, - {"add label", nil, true}, - {"sub label", nil, true}, - {"input label", nil, true}, - {"output label", nil, true}, - // Fail: Wrong param - {"add 1x10", nil, true}, - // Fail: Amnt params - {"kill 0", nil, true}, - {"output", nil, true}, - {"output 8 1", nil, true}, - } - - for i, test := range tests { - got, err := AssembleInstruction(test.line) - gotError := err != nil - - if test.expectsErr != gotError { - t.Errorf("[%d] Expected error: %t, Got error: %t", i, test.expectsErr, gotError) - } - if !helper.SafeIsEqualInstructionPointer(test.expected, got) { - t.Errorf("Instruction expected is: %v, Got expected is: %v", test.expected, got) - } - } -} -func getInstruction(code int, param int) *data.Instruction { - cmd, _ := data.NewInstruction(code, data.NewIntParam(param)) - return cmd -} -func getInstructionStr(code int, param string) *data.Instruction { - cmd, _ := data.NewInstruction(code, data.NewStringParam(param)) - return cmd -} - -func TestGetInstructionParams(t *testing.T) { - expected := []string{"0x1", "1", "label"} - got := getInstructionParams([]string{"mov", "0x1", "1", "label"}) - if !reflect.DeepEqual(got, expected) { - t.Errorf("Error") - } -} diff --git a/core/instruction/instructionsconfig.go b/core/instruction/instructionsconfig.go deleted file mode 100644 index 249d6d3..0000000 --- a/core/instruction/instructionsconfig.go +++ /dev/null @@ -1,66 +0,0 @@ -package instruction - -import ( - "github.com/open-machine/assembler/config/myerrors" - "github.com/open-machine/assembler/data" -) - -type InstructionConfig struct { - code int - getParam func(instructionName string, words []string) (*data.InstructionParameter, *myerrors.CustomError) -} - -func GetInstructionsConfig() map[string]InstructionConfig { - return instructions -} - -var instructions = map[string]InstructionConfig{ - "nop": InstructionConfig{ - getParam: getParamNoParam, - code: 0x0, - }, - "copy": InstructionConfig{ - getParam: getSecondWordAsInt, - code: 0x1, - }, - "store": InstructionConfig{ - getParam: getSecondWordAsInt, - code: 0x2, - }, - "add": InstructionConfig{ - getParam: getSecondWordAsInt, - code: 0x3, - }, - "sub": InstructionConfig{ - getParam: getSecondWordAsInt, - code: 0x4, - }, - "input": InstructionConfig{ - getParam: getSecondWordAsInt, - code: 0x7, - }, - "output": InstructionConfig{ - getParam: getSecondWordAsInt, - code: 0x8, - }, - "kill": InstructionConfig{ - getParam: getParamNoParam, - code: 0x9, - }, - "jmp": InstructionConfig{ - getParam: getSecondWordAsIntOrString, - code: 0xA, - }, - "jg": InstructionConfig{ - getParam: getSecondWordAsIntOrString, - code: 0xB, - }, - "je": InstructionConfig{ - getParam: getSecondWordAsIntOrString, - code: 0xD, - }, - "jl": InstructionConfig{ - getParam: getSecondWordAsIntOrString, - code: 0xF, - }, -} diff --git a/core/lineassembler.go b/core/lineassembler.go index a079d1b..0914bfd 100644 --- a/core/lineassembler.go +++ b/core/lineassembler.go @@ -1,35 +1,29 @@ package core import ( - "strings" - + "github.com/open-machine/assembler/commands/instruction" + "github.com/open-machine/assembler/commands/label" "github.com/open-machine/assembler/config/myerrors" - "github.com/open-machine/assembler/core/comment" - "github.com/open-machine/assembler/core/instruction" - "github.com/open-machine/assembler/core/label" "github.com/open-machine/assembler/data" "github.com/open-machine/assembler/utils" ) -func assembleEntireLine(line string) (*string, *data.Instruction, *myerrors.CustomError) { +func assembleEntireLine(line string, program *data.Program) *myerrors.CustomError { normalizedStr := utils.LineNormalization(line) - lineWithoutCommand := comment.RemoveComment(normalizedStr) - lineWithoutCommand = strings.TrimSpace(lineWithoutCommand) - - if lineWithoutCommand == "" { - return nil, nil, nil + if normalizedStr == "" { + return nil } // Jump Label - jumpLabel, restOfLine, errLabel := label.AssembleJumpLabel(lineWithoutCommand) + jumpLabel, restOfLine, errLabel := label.AssembleJumpLabel(normalizedStr) if errLabel != nil { return nil, nil, errLabel } - if jumpLabel != nil && restOfLine != "" { - return nil, nil, myerrors.NewCodeError(myerrors.MoreThanOneCommandInLine(restOfLine)) - } if jumpLabel != nil { + if restOfLine != "" { + return nil, nil, myerrors.NewCodeError(myerrors.MoreThanOneCommandInLine(restOfLine)) + } return jumpLabel, nil, nil } diff --git a/core/lineassembler_test.go b/core/lineassembler_test.go index d35ea33..2291e95 100644 --- a/core/lineassembler_test.go +++ b/core/lineassembler_test.go @@ -34,6 +34,7 @@ func TestAssembleEntireLine(t *testing.T) { // Label: Success {" label: ", helper.StringPointer("label"), nil, false}, + {" label:\t ", helper.StringPointer("label"), nil, false}, // Label: Fail {" 1label: input 0x1 ", nil, nil, true}, diff --git a/core/readwriteprogram.go b/core/readwriteprogram.go deleted file mode 100644 index 2a7060d..0000000 --- a/core/readwriteprogram.go +++ /dev/null @@ -1,81 +0,0 @@ -package core - -import ( - "bufio" - "fmt" - "io" - - "github.com/open-machine/assembler/config/myerrors" - "github.com/open-machine/assembler/utils" - - "github.com/open-machine/assembler/config" - "github.com/open-machine/assembler/data" - "github.com/open-machine/assembler/helper" -) - -func programFromFile(file utils.MyFileInterface) *data.Program { - reader := bufio.NewReader(file.Reader()) - lineIndex := 1 - program := data.NewProgram(0) - - successful := true - - for { - line, errRead := reader.ReadString('\n') - - if errRead != nil && errRead != io.EOF { - helper.LogOtherError(fmt.Sprintf("Error while reading file. Error: %s", errRead.Error())) - return nil - } - - jumpLabel, instructionPointer, errAssemble := assembleEntireLine(line) - - if jumpLabel != nil { - errJumpLabel := program.AddJumpLabel(*jumpLabel, program.LenInstructions()) - if errJumpLabel != nil { - helper.LogErrorInLine(*myerrors.NewCodeError(errJumpLabel), lineIndex, line) - return nil - } - } - - if errAssemble != nil { - successful = false - helper.LogErrorInLine(*errAssemble, lineIndex, line) - } else if instructionPointer != nil { - program.AddInstruction(*instructionPointer) - } - - if errRead == io.EOF { - break - } - lineIndex++ - } - - if !successful { - return nil - } - return &program -} - -func writeExecProgram(program data.Program, execFileName string, execFile io.Writer) int { - writer := bufio.NewWriter(execFile) - defer writer.Flush() - - execStr, errs := program.ToExecuter() - - if len(errs) > 0 { - for _, err := range errs { - // TODO: infrastructure to get line - helper.LogErrorInLine(err, 0, "") - } - return config.FailStatus - } - - _, err := writer.WriteString(execStr) - if err != nil { - helper.LogOtherError(fmt.Sprintf("Could not write to file %s \n", execFileName)) - return config.FailStatus - } - - return config.SuccessStatus -} diff --git a/data/instruction.go b/data/instruction.go index e53b726..cfbcdae 100644 --- a/data/instruction.go +++ b/data/instruction.go @@ -17,18 +17,6 @@ func NewInstruction(code int, param InstructionParameter) (*Instruction, *myerro return nil, myerrors.NewAssemblerError(err) } - if param.IsStr { - err := utils.CheckParamName(param.Str) - if err != nil { - return nil, myerrors.NewAssemblerError(err) - } - } else { - if !param.IsStr && utils.IsOverflow(uint(param.Num), config.AmntBitsParam) { - err := myerrors.ParamOverflow(param.Num, config.AmntBitsParam) - return nil, myerrors.NewCodeError(err) - } - } - return &Instruction{code, param}, nil } diff --git a/data/instructionParameter.go b/data/instructionParameter.go index 629c816..566d799 100644 --- a/data/instructionParameter.go +++ b/data/instructionParameter.go @@ -1,15 +1,39 @@ package data +import ( + "github.com/open-machine/assembler/config" + "github.com/open-machine/assembler/config/myerrors" + "github.com/open-machine/assembler/utils" +) + type InstructionParameter struct { Num int Str string IsStr bool } -func NewStringParam(str string) InstructionParameter { - return InstructionParameter{Num: 0, Str: str, IsStr: true} +func NewStringParam(str string) (*InstructionParameter, *myerrors.CustomError) { + err := utils.CheckParamName(str) + if err != nil { + return nil, myerrors.NewAssemblerError(err) + } + + return &InstructionParameter{Num: 0, Str: str, IsStr: true}, nil +} + +func NewIntParam(num int) (*InstructionParameter, *myerrors.CustomError) { + if utils.IsOverflow(uint(num), config.AmntBitsParam) { + err := myerrors.ParamOverflow(num, config.AmntBitsParam) + return nil, myerrors.NewCodeError(err) + } + + return &InstructionParameter{Num: num, Str: "", IsStr: false}, nil } -func NewIntParam(num int) InstructionParameter { - return InstructionParameter{Num: num, Str: "", IsStr: false} +func NewParamTest(num int, str string, isStr bool) *InstructionParameter { + if config.Testing { + return &InstructionParameter{Num: num, Str: str, IsStr: isStr} + } else { + return nil + } } diff --git a/data/instructionParameter_test.go b/data/instructionParameter_test.go index 05fe407..b9c46d0 100644 --- a/data/instructionParameter_test.go +++ b/data/instructionParameter_test.go @@ -1,15 +1,91 @@ package data -import "testing" +import ( + "testing" -func TestParamConstructors(t *testing.T) { - strParam := NewStringParam("Hello World") - if !strParam.IsStr { - t.Errorf("String param should have true isStr") + "github.com/open-machine/assembler/config" +) + +func TestStrParamConstructors(t *testing.T) { + var tests = []struct { + str string + expectedErr bool + }{ + // Success + { + str: "helloWorld", + expectedErr: false, + }, + // Fail + { + str: "Hello World", + expectedErr: true, + }, + { + str: "Hello", + expectedErr: true, + }, + } + + for i, test := range tests { + strParam, err := NewStringParam("Hello World") + gotErr := err != nil + + if gotErr != test.expectedErr { + t.Errorf("[%d] Expected error: %t, got error: %t", i, test.expectedErr, gotErr) + } + + if !strParam.IsStr { + t.Errorf("String param should have true isStr") + } + } +} + +func TestIntParamConstructors(t *testing.T) { + var tests = []struct { + num int + expectedErr bool + }{ + // Success + { + num: 1, + expectedErr: false, + }, + // Fail + { + num: 5000, + expectedErr: true, + }, + } + + for i, test := range tests { + strParam, err := NewStringParam("Hello World") + gotErr := err != nil + + if gotErr != test.expectedErr { + t.Errorf("[%d] Expected error: %t, got error: %t", i, test.expectedErr, gotErr) + } + + if strParam.IsStr { + t.Errorf("Int param should have false isStr") + } + } +} + +func TestParamConstructorsTesting(t *testing.T) { + num := 0 + str := "a" + isStr := true + + config.Testing = true + instruc1 := NewParamTest(num, str, isStr) + if instruc1 == nil { + t.Errorf("Instruction should not be nil") } - numParam := NewIntParam(1) - if numParam.IsStr { - t.Errorf("Int param should have false isStr") + config.Testing = false + instruc2 := NewParamTest(num, str, isStr) + if instruc2 != nil { + t.Errorf("Instruction should be nil") } } diff --git a/data/instruction_test.go b/data/instruction_test.go index 3a85b15..e3197e2 100644 --- a/data/instruction_test.go +++ b/data/instruction_test.go @@ -27,7 +27,8 @@ func TestNewInstructionOverflowValidation(t *testing.T) { } for i, test := range tests { - _, err := NewInstruction(test.code, NewIntParam(test.param)) + param, _ := NewIntParam(test.param) + _, err := NewInstruction(test.code, *param) gotErr := err != nil if test.expectsError != gotErr { @@ -48,7 +49,8 @@ func TestNewInstructionWrongStringParam(t *testing.T) { } for i, test := range tests { - _, err := NewInstruction(test.code, NewStringParam(test.param)) + param, _ := NewStringParam(test.param) + _, err := NewInstruction(test.code, *param) gotErr := err != nil if test.expectsError != gotErr { @@ -63,12 +65,12 @@ func TestToExecuter(t *testing.T) { expected string expectsError bool }{ - {Instruction{0, NewIntParam(0)}, "0000", false}, - {Instruction{11, NewIntParam(5)}, "b005", false}, - {Instruction{5, NewIntParam(300)}, "512c", false}, - {Instruction{5000, NewIntParam(5)}, "", true}, - {Instruction{5, NewIntParam(5000)}, "", true}, - {Instruction{5, NewStringParam("abc")}, "", true}, + {Instruction{0, newIntParam(0)}, "0000", false}, + {Instruction{11, newIntParam(5)}, "b005", false}, + {Instruction{5, newIntParam(300)}, "512c", false}, + {Instruction{5000, newIntParam(5)}, "", true}, + {Instruction{5, newIntParam(5000)}, "", true}, + {Instruction{5, newStringParam("abc")}, "", true}, } for i, test := range tests { @@ -84,24 +86,30 @@ func TestToExecuter(t *testing.T) { } } } +func newIntParam(num int) InstructionParameter { + return InstructionParameter{Num: 0, Str: "", IsStr: false} +} +func newStringParam(str string) InstructionParameter { + return InstructionParameter{Num: 0, Str: str, IsStr: true} +} func TestNewInstructionTest(t *testing.T) { code := 300 - param := NewIntParam(300) + param, _ := NewIntParam(300) - _, err := NewInstruction(code, param) + _, err := NewInstruction(code, *param) if err == nil { t.Errorf("Expected error! NewInstruction should verify params and these params should be wrong to validate the NewInstructionTest function") } config.Testing = false - ptrInstructionNil := NewInstructionTest(code, param) + ptrInstructionNil := NewInstructionTest(code, *param) if ptrInstructionNil != nil { t.Errorf("Expected nil instruction, got not nil instruction") } config.Testing = true - ptrInstructionNotNil := NewInstructionTest(code, param) + ptrInstructionNotNil := NewInstructionTest(code, *param) if ptrInstructionNotNil == nil { t.Errorf("Expected nil instruction, got not nil instruction") } diff --git a/data/program.go b/data/program.go index 06ac2c1..1913f55 100644 --- a/data/program.go +++ b/data/program.go @@ -46,8 +46,15 @@ func (p *Program) ReplaceLabelsWithNumbers() []error { if !exists { errs = append(errs, myerrors.JumpLabelDoesNotExistError(label)) } else { - instruction.parameter = NewIntParam(instructionIndex) - p.instructions[i] = instruction + updatedParam, err := NewIntParam(instructionIndex) + // TODO: test this error + + if err != nil { + errs = append(errs, myerrors.ThereIsNoSpaceLeft(err)) + } else { + instruction.parameter = *updatedParam + p.instructions[i] = instruction + } } } } diff --git a/data/program_test.go b/data/program_test.go index b06a950..4a93a73 100644 --- a/data/program_test.go +++ b/data/program_test.go @@ -12,7 +12,7 @@ func TestAddInstruction(t *testing.T) { t.Errorf("Expected length 0, got: %d", len(program.instructions)) } - program.AddInstruction(Instruction{0, NewIntParam(0)}) + program.AddInstruction(Instruction{0, newIntParam(0)}) if len(program.instructions) != 1 { t.Errorf("Expected length 1, got: %d", len(program.instructions)) @@ -28,9 +28,9 @@ func TestProgToExecuter(t *testing.T) { // Success with header { []Instruction{ - Instruction{1, NewIntParam(2)}, - Instruction{15, NewIntParam(7)}, - Instruction{0, NewIntParam(0)}, + Instruction{1, newIntParam(2)}, + Instruction{15, newIntParam(7)}, + Instruction{0, newIntParam(0)}, }, "v2.0 raw\n00001002f0070000", 0, @@ -38,9 +38,9 @@ func TestProgToExecuter(t *testing.T) { // 1 Overflow { []Instruction{ - Instruction{1, NewIntParam(2)}, - Instruction{1200, NewIntParam(7)}, - Instruction{0, NewIntParam(0)}, + Instruction{1, newIntParam(2)}, + Instruction{1200, newIntParam(7)}, + Instruction{0, newIntParam(0)}, }, "", 1, @@ -48,9 +48,9 @@ func TestProgToExecuter(t *testing.T) { // 2 Overflows { []Instruction{ - Instruction{1, NewIntParam(2)}, - Instruction{1200, NewIntParam(7)}, - Instruction{3000, NewIntParam(7)}, + Instruction{1, newIntParam(2)}, + Instruction{1200, newIntParam(7)}, + Instruction{3000, newIntParam(7)}, }, "", 2, @@ -109,19 +109,19 @@ func TestReplaceLabelsWithNumbers(t *testing.T) { { &Program{ []Instruction{ - Instruction{3, NewIntParam(1)}, - Instruction{1, NewStringParam("label")}, - Instruction{5, NewIntParam(3)}, - Instruction{7, NewIntParam(3)}, + Instruction{3, newIntParam(1)}, + Instruction{1, newStringParam("label")}, + Instruction{5, newIntParam(3)}, + Instruction{7, newIntParam(3)}, }, map[string]int{"label": 3}, }, Program{ []Instruction{ - Instruction{3, NewIntParam(1)}, - Instruction{1, NewIntParam(3)}, - Instruction{5, NewIntParam(3)}, - Instruction{7, NewIntParam(3)}, + Instruction{3, newIntParam(1)}, + Instruction{1, newIntParam(3)}, + Instruction{5, newIntParam(3)}, + Instruction{7, newIntParam(3)}, }, map[string]int{}, }, @@ -131,23 +131,23 @@ func TestReplaceLabelsWithNumbers(t *testing.T) { { &Program{ []Instruction{ - Instruction{3, NewIntParam(1)}, - Instruction{1, NewStringParam("label")}, - Instruction{5, NewIntParam(3)}, - Instruction{2, NewIntParam(0)}, - Instruction{1, NewStringParam("abc")}, - Instruction{11, NewIntParam(15)}, + Instruction{3, newIntParam(1)}, + Instruction{1, newStringParam("label")}, + Instruction{5, newIntParam(3)}, + Instruction{2, newIntParam(0)}, + Instruction{1, newStringParam("abc")}, + Instruction{11, newIntParam(15)}, }, map[string]int{"abc": 0, "label": 0}, }, Program{ []Instruction{ - Instruction{3, NewIntParam(1)}, - Instruction{1, NewIntParam(0)}, - Instruction{5, NewIntParam(3)}, - Instruction{2, NewIntParam(0)}, - Instruction{1, NewIntParam(0)}, - Instruction{11, NewIntParam(15)}, + Instruction{3, newIntParam(1)}, + Instruction{1, newIntParam(0)}, + Instruction{5, newIntParam(3)}, + Instruction{2, newIntParam(0)}, + Instruction{1, newIntParam(0)}, + Instruction{11, newIntParam(15)}, }, map[string]int{}, }, @@ -157,19 +157,19 @@ func TestReplaceLabelsWithNumbers(t *testing.T) { { &Program{ []Instruction{ - Instruction{3, NewIntParam(1)}, - Instruction{5, NewIntParam(3)}, - Instruction{2, NewIntParam(0)}, - Instruction{11, NewIntParam(15)}, + Instruction{3, newIntParam(1)}, + Instruction{5, newIntParam(3)}, + Instruction{2, newIntParam(0)}, + Instruction{11, newIntParam(15)}, }, map[string]int{}, }, Program{ []Instruction{ - Instruction{3, NewIntParam(1)}, - Instruction{5, NewIntParam(3)}, - Instruction{2, NewIntParam(0)}, - Instruction{11, NewIntParam(15)}, + Instruction{3, newIntParam(1)}, + Instruction{5, newIntParam(3)}, + Instruction{2, newIntParam(0)}, + Instruction{11, newIntParam(15)}, }, map[string]int{}, }, @@ -179,23 +179,23 @@ func TestReplaceLabelsWithNumbers(t *testing.T) { { &Program{ []Instruction{ - Instruction{3, NewIntParam(1)}, - Instruction{1, NewStringParam("label")}, - Instruction{5, NewIntParam(3)}, - Instruction{2, NewIntParam(0)}, - Instruction{1, NewStringParam("abc")}, - Instruction{11, NewIntParam(15)}, + Instruction{3, newIntParam(1)}, + Instruction{1, newStringParam("label")}, + Instruction{5, newIntParam(3)}, + Instruction{2, newIntParam(0)}, + Instruction{1, newStringParam("abc")}, + Instruction{11, newIntParam(15)}, }, map[string]int{"abc": 0, "label": 0, "abcdario": 11}, }, Program{ []Instruction{ - Instruction{3, NewIntParam(1)}, - Instruction{1, NewIntParam(0)}, - Instruction{5, NewIntParam(3)}, - Instruction{2, NewIntParam(0)}, - Instruction{1, NewIntParam(0)}, - Instruction{11, NewIntParam(15)}, + Instruction{3, newIntParam(1)}, + Instruction{1, newIntParam(0)}, + Instruction{5, newIntParam(3)}, + Instruction{2, newIntParam(0)}, + Instruction{1, newIntParam(0)}, + Instruction{11, newIntParam(15)}, }, map[string]int{}, }, @@ -205,23 +205,23 @@ func TestReplaceLabelsWithNumbers(t *testing.T) { { &Program{ []Instruction{ - Instruction{3, NewIntParam(1)}, - Instruction{1, NewStringParam("luca")}, - Instruction{5, NewIntParam(3)}, - Instruction{2, NewIntParam(0)}, - Instruction{1, NewStringParam("abc")}, - Instruction{11, NewIntParam(15)}, + Instruction{3, newIntParam(1)}, + Instruction{1, newStringParam("luca")}, + Instruction{5, newIntParam(3)}, + Instruction{2, newIntParam(0)}, + Instruction{1, newStringParam("abc")}, + Instruction{11, newIntParam(15)}, }, map[string]int{"abc": 0, "label": 0, "abcdario": 11}, }, Program{ []Instruction{ - Instruction{3, NewIntParam(1)}, - Instruction{1, NewStringParam("luca")}, - Instruction{5, NewIntParam(3)}, - Instruction{2, NewIntParam(0)}, - Instruction{1, NewIntParam(0)}, - Instruction{11, NewIntParam(15)}, + Instruction{3, newIntParam(1)}, + Instruction{1, newStringParam("luca")}, + Instruction{5, newIntParam(3)}, + Instruction{2, newIntParam(0)}, + Instruction{1, newIntParam(0)}, + Instruction{11, newIntParam(15)}, }, map[string]int{"abc": 0, "label": 0, "abcdario": 11}, }, @@ -231,23 +231,23 @@ func TestReplaceLabelsWithNumbers(t *testing.T) { { &Program{ []Instruction{ - Instruction{3, NewIntParam(1)}, - Instruction{1, NewStringParam("luca")}, - Instruction{5, NewIntParam(3)}, - Instruction{2, NewIntParam(0)}, - Instruction{1, NewStringParam("abc")}, - Instruction{11, NewIntParam(15)}, + Instruction{3, newIntParam(1)}, + Instruction{1, newStringParam("luca")}, + Instruction{5, newIntParam(3)}, + Instruction{2, newIntParam(0)}, + Instruction{1, newStringParam("abc")}, + Instruction{11, newIntParam(15)}, }, map[string]int{"label": 0, "abcdario": 11}, }, Program{ []Instruction{ - Instruction{3, NewIntParam(1)}, - Instruction{1, NewStringParam("luca")}, - Instruction{5, NewIntParam(3)}, - Instruction{2, NewIntParam(0)}, - Instruction{1, NewStringParam("abc")}, - Instruction{11, NewIntParam(15)}, + Instruction{3, newIntParam(1)}, + Instruction{1, newStringParam("luca")}, + Instruction{5, newIntParam(3)}, + Instruction{2, newIntParam(0)}, + Instruction{1, newStringParam("abc")}, + Instruction{11, newIntParam(15)}, }, map[string]int{"label": 0, "abcdario": 11}, }, @@ -281,6 +281,6 @@ func TestLenInstructions(t *testing.T) { } } func mockInstruction() Instruction { - cmd, _ := NewInstruction(0, NewIntParam(1)) + cmd, _ := NewInstruction(0, newIntParam(1)) return *cmd } diff --git a/helper/comparisons_test.go b/helper/comparisons_test.go index 2d4f706..b77db02 100644 --- a/helper/comparisons_test.go +++ b/helper/comparisons_test.go @@ -1,8 +1,9 @@ package helper import ( - "github.com/open-machine/assembler/data" "testing" + + "github.com/open-machine/assembler/data" ) func TestSafeIsEqualStringPointer(t *testing.T) { @@ -47,7 +48,8 @@ func TestSafeIsEqualProgramPointer(t *testing.T) { } } func newProgram(a int, b int) *data.Program { - cmd, _ := data.NewInstruction(a, data.NewIntParam(b)) + param, _ := data.NewIntParam(b) + cmd, _ := data.NewInstruction(a, *param) program := data.ProgramFromInstructionsAndLabels([]data.Instruction{*cmd}, map[string]int{}) return &program } @@ -73,7 +75,8 @@ func TestSafeIsEqualInstructionPointer(t *testing.T) { } } func newInstruction(a int, b int) *data.Instruction { - cmd, _ := data.NewInstruction(a, data.NewIntParam(b)) + param, _ := data.NewIntParam(b) + cmd, _ := data.NewInstruction(a, *param) return cmd } @@ -98,6 +101,6 @@ func TestSafeIsEqualInstructionParamPointer(t *testing.T) { } } func newIntParam(a int) *data.InstructionParameter { - param := data.NewIntParam(a) - return ¶m + param, _ := data.NewIntParam(a) + return param } diff --git a/utils/normalization.go b/utils/normalization.go index fb82052..cc438cd 100644 --- a/utils/normalization.go +++ b/utils/normalization.go @@ -6,11 +6,12 @@ import ( ) func LineNormalization(line string) string { - withoutNewLine := RemoveNewLine(line) - return removeUnecessarySpaces(withoutNewLine) + withoutNewLine := removeNewLine(line) + withoutComment := removeComment(withoutNewLine) + return removeUnecessarySpaces(withoutComment) } -func RemoveNewLine(line string) string { +func removeNewLine(line string) string { lineWithoutEndingUnix := strings.TrimSuffix(line, "\n") lineWithoutEndingUnixAndWindows := strings.TrimSuffix(lineWithoutEndingUnix, "\r") return lineWithoutEndingUnixAndWindows @@ -21,3 +22,11 @@ func removeUnecessarySpaces(line string) string { str := space.ReplaceAllString(line, " ") return strings.TrimSpace(str) } + +func removeComment(line string) string { + index := strings.Index(line, "#") + if index < 0 { + return line + } + return line[:index] +} diff --git a/utils/normalization_test.go b/utils/normalization_test.go index 0a3aaef..473630b 100644 --- a/utils/normalization_test.go +++ b/utils/normalization_test.go @@ -9,6 +9,8 @@ func TestLineNormalization(t *testing.T) { }{ {" MOV\t 8 \r\n", "MOV 8"}, {"MoV 8\n", "MoV 8"}, + {"MoV 8 #asdf", "MoV 8"}, + {"hello, my name is Luca #adfd \n", "hello, my name is Luca"}, } for _, test := range tests { @@ -33,7 +35,7 @@ func TestRemoveNewLine(t *testing.T) { } for _, test := range tests { - got := RemoveNewLine(test.param) + got := removeNewLine(test.param) if got != test.expected { t.Errorf("Expected: '%s', Got: '%s'", test.expected, got) @@ -59,3 +61,23 @@ func TestRemoveUnecessarySpaces(t *testing.T) { } } } + +func TestRemoveComment(t *testing.T) { + tests := []struct { + param string + expected string + }{ + {"", ""}, + {"copy 0x0", "copy 0x0"}, + {"# Hello World ", ""}, + {"copy 0x0 # Hello World", "copy 0x0 "}, + {"luca dillenburg doing stuff # Hello World", "luca dillenburg doing stuff "}, + } + + for _, test := range tests { + got := removeComment(test.param) + if got != test.expected { + t.Errorf("Comment removed wrongly. Expected '%s', Got: '%s'", test.expected, test.param) + } + } +}