diff --git a/constants/constants.go b/constants/constants.go index 9af6dcf..e2908b8 100644 --- a/constants/constants.go +++ b/constants/constants.go @@ -4,15 +4,16 @@ const ( //TextSection texto para seção text TextSection = "text" + //TextSectionAddress endereço início das instruções + TextSectionAddress = 0 + //DataSection texto para seção data DataSection = "data" - //LabelNotFound erro de label não encontrada - LabelNotFound = "Label not found" - - //NoneInstructionFound erro de nome de instrução não encontrado - NoneInstructionFound = "None instruction found for this name: %s" + //DataSectionAddress endereço início das instruções + DataSectionAddress = 128 - //InvalidOperandCount erro de quantidade de operadores invalidas - InvalidOperandCount = "Invalid operand count" + // MaxErrorCodeDigits define a quantidade máxima de digitos para + // o código de um erro do montador + MaxErrorCodeDigits = 3 ) diff --git a/decoder/decoder.go b/decoder/decoder.go index 78f2555..65c8200 100644 --- a/decoder/decoder.go +++ b/decoder/decoder.go @@ -1,8 +1,7 @@ package decoder import ( - "fmt" - "ph1-assembly/constants" + "ph1-assembly/pherror" ) type metaInstruction struct { @@ -30,14 +29,19 @@ var operations = map[string]*metaInstruction{ "HLT": &metaInstruction{opCode: "F0", size: 1}, } -// Decode traduz o mnemônico de uma instrução e retorna +// DecodeText traduz o mnemônico de uma instrução e retorna // seu opcode e tamanho -func Decode(name string) (string, int, error) { +func DecodeText(name string) (string, int) { instruction := operations[name] if instruction == nil { - return "", 0, fmt.Errorf(constants.NoneInstructionFound, name) + panic(pherror.Format(pherror.NoneInstructionFound, name)) } - return instruction.opCode, instruction.size, nil + return instruction.opCode, instruction.size +} + +// DecodeData decodifica um tipo de dados, retornando seu tamanho em bytes +func DecodeData(name string) (size int) { + return 1 } diff --git a/extractor/instructions.go b/extractor/instructions.go index 136d9fe..5f6f7d4 100644 --- a/extractor/instructions.go +++ b/extractor/instructions.go @@ -4,6 +4,7 @@ import ( "ph1-assembly/constants" "ph1-assembly/decoder" "ph1-assembly/input" + "ph1-assembly/pherror" "strconv" ) @@ -23,20 +24,16 @@ type Data struct { //ExtractInstructions efetua a segunda passagem no código, guardando as // instrucoes em uma lista de struct -func ExtractInstructions(contents []*input.SourceLine, labelMap map[string]int) []Instruction { +func ExtractInstructions(textContent []*input.SourceLine, labelMap map[string]int) []Instruction { var instructions = make([]Instruction, 0) - for _, srcLine := range contents { + for _, srcLine := range textContent { if srcLine.Name == constants.TextSection || srcLine.Name == constants.DataSection { break } - opCode, size, err := decoder.Decode(srcLine.Name) - - if err != nil { - panic(err) - } + opCode, size := decoder.DecodeText(srcLine.Name) // Cria instrução sem operando instruction := &Instruction{ @@ -51,10 +48,11 @@ func ExtractInstructions(contents []*input.SourceLine, labelMap map[string]int) operandValue, found := labelMap[srcLine.Operand] if found == false { + var err error operandValue, err = strconv.Atoi(srcLine.Operand) if err != nil { - panic(constants.LabelNotFound) + panic(pherror.Format(pherror.LabelNotFound, srcLine.Operand)) } } diff --git a/extractor/labels.go b/extractor/labels.go index 1cc4477..6dee863 100644 --- a/extractor/labels.go +++ b/extractor/labels.go @@ -1,42 +1,26 @@ package extractor import ( - "ph1-assembly/decoder" "ph1-assembly/input" ) // ExtractLabels efetua a primeira passagem no código, guardando os rótulos // e seus endereços em um map que irá retornar -func ExtractLabels(contents []*input.SourceLine) map[string]int { +func ExtractLabels(src *input.Source) map[string]int { + labels := map[string]int{} - var labelMap = make(map[string]int) - - sections := map[string]int{ - "text": 0, - "data": 128, - } - currentSection := "" - - for _, srcLine := range contents { - if _, ok := sections[srcLine.Name]; ok { - currentSection = srcLine.Name - continue - } - - srcLine.Address = sections[currentSection] - - _, size, _ := decoder.Decode(srcLine.Name) - - if size == 0 { - size = 1 + // Encontra as labels + for _, srcText := range src.Text { + if srcText.Label != "" { + labels[srcText.Label] = srcText.Address } - sections[currentSection] += size + } - // Adiciona o label no map se a label não for vazia - if srcLine.Label != "" { - labelMap[srcLine.Label] = srcLine.Address + for _, srcData := range src.Data { + if srcData.Label != "" { + labels[srcData.Label] = srcData.Address } } - return labelMap + return labels } diff --git a/input/reader.go b/input/reader.go index a65f1f7..452661e 100644 --- a/input/reader.go +++ b/input/reader.go @@ -4,6 +4,8 @@ import ( "bufio" "os" "ph1-assembly/constants" + "ph1-assembly/decoder" + "ph1-assembly/pherror" "regexp" "strings" ) @@ -17,16 +19,38 @@ var ( // SourceLine contém os dados necessários de uma linha do código fonte type SourceLine struct { - Label string - Name string - Operand string - Address int + Label string + Name string + Operand string + LineNumber int + Address int } // Source contém as informações do código fonte type Source struct { - Filename string - Contents []*SourceLine + Filename string + Text []*SourceLine + Data []*SourceLine + CurrentTextAddress int + CurrentDataAddress int +} + +// AppendText adiciona uma nova linha na seção text, atribuindo +// seu endereço e validando a instrução +func (src *Source) AppendText(line *SourceLine) { + _, size := decoder.DecodeText(line.Name) + line.Address = src.CurrentTextAddress + src.Text = append(src.Text, line) + src.CurrentTextAddress += size +} + +// AppendData adiciona uma nova linha na seção data, atribuindo +// seu endereço e validando o tipo de dado +func (src *Source) AppendData(line *SourceLine) { + size := decoder.DecodeData(line.Name) + line.Address = src.CurrentDataAddress + src.Data = append(src.Data, line) + src.CurrentDataAddress += size } // ReadSource lê o codigo fonte e transforma para o tipo Source @@ -37,11 +61,43 @@ func ReadSource(filename string) (source *Source, err error) { return } - source = &Source{Filename: filename} + source = &Source{ + Filename: filename, + CurrentTextAddress: constants.TextSectionAddress, + CurrentDataAddress: constants.DataSectionAddress, + } - for _, line := range contents { + // Seção atual do código (text ou data) + var section string + + // Cria um erro relacionado ao arquivo + fileError := &pherror.ErrorType{Filename: filename} + + // Lẽ cada linha salvanda na seção atual + for lineNumber, line := range contents { + // Atualiza a linha atual do erro de arquivo + fileError.LineNumber = lineNumber + 1 + + // Faz uma validação previa da linha if validateSourceLine(line) { - source.Contents = append(source.Contents, parseSourceLine(line)) + + // Extrai os dados da linha atual + sourceLine := parseSourceLine(line) + sourceLine.LineNumber = lineNumber + 1 + + // Verifica a seção atual + if strings.ToUpper(sourceLine.Name) == strings.ToUpper(constants.TextSection) { + section = constants.TextSection + } else if strings.ToUpper(sourceLine.Name) == strings.ToUpper(constants.DataSection) { + section = constants.DataSection + } else if section == constants.TextSection { + source.AppendText(sourceLine) + } else if section == constants.DataSection { + source.AppendData(sourceLine) + } else { + err := pherror.Join(pherror.DecoratorNotFound, fileError) + panic(pherror.Format(err, sourceLine.Name)) + } } } @@ -63,7 +119,7 @@ func parseSourceLine(line string) (srcLine *SourceLine) { match := lineMatchRegex.FindStringSubmatch(line) if len(match) == 0 { - panic(constants.InvalidOperandCount) + panic(pherror.InvalidOperandCount) } // Instantcia um novo SourceLine diff --git a/main.go b/main.go index f2f7c60..2c174c4 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,14 @@ package main import ( + "errors" "fmt" "os" + "path/filepath" "ph1-assembly/extractor" "ph1-assembly/input" "ph1-assembly/output" + "ph1-assembly/pherror" "strings" ) @@ -19,29 +22,44 @@ type Options struct { // Mount lê um arquivo fonte em assembly PH1 e monta para linguagem de máquina // no padrão do emulador PH1 func Mount(opt *Options) { + // Validação para permitir que o usuário tente mais vezes caso o nome do arquivo esteja + // errado source, err := input.ReadSource(opt.Input) - - if err != nil { - // Validação para permitir que o usuário tente mais vezes caso o nome do arquivo esteja - // errado - for err != nil { - fmt.Print("Cannot read input file, please try again: ") - fmt.Scanln(&opt.Input) - fmt.Println() + for err != nil { + var perr *os.PathError + if errors.As(err, &perr) { + panic(pherror.Format(pherror.FileNotFound, opt.Input)) + } else { + panic(pherror.Format(pherror.CannotOpenFile, opt.Input)) } } // Primeira passagem: labels - labels := extractor.ExtractLabels(source.Contents) + labels := extractor.ExtractLabels(source) + // Segunda passagem: instruções - instructions := extractor.ExtractInstructions(source.Contents, labels) + instructions := extractor.ExtractInstructions(source.Text, labels) + // Gera o arquivo de saída a partir do nome definido no options output.CreateOutputFile(instructions, opt.Output) } func main() { + // Tratamento de erro + defer func() { + if err := recover(); err != nil { + pherr := pherror.Format(err) + fmt.Println(pherr) + + if pherr.Code != 0 { + os.Exit(pherr.Code) + } + os.Exit(1) + } + }() + if len(os.Args) < 2 { - panic("Informe o nome do arquivo") + panic(pherror.MissingInputFile) } var input, output string @@ -54,7 +72,7 @@ func main() { } else { // Gera o output através do nome do arquivo - output = strings.Split(input, ".")[0] + ".ph1" + output = strings.Split(filepath.Base(input), ".")[0] + ".ph1" } options := &Options{ diff --git a/pherror/errors.go b/pherror/errors.go new file mode 100644 index 0000000..8213fc6 --- /dev/null +++ b/pherror/errors.go @@ -0,0 +1,46 @@ +package pherror + +var ( + //MissingInputFile erro de arquivo de entrada não informado + MissingInputFile = &ErrorType{ + Code: 1, + Message: "No input file given", + } + + //FileNotFound erro arquivo não encontrado + FileNotFound = &ErrorType{ + Code: 2, + Message: "File \"%s\" not found", + } + + //CannotOpenFile erro genérico ao falhar tentando abrir um arquivo + CannotOpenFile = &ErrorType{ + Code: 3, + Message: "Error opening file %s", + } + + //LabelNotFound erro de label não encontrada + LabelNotFound = &ErrorType{ + Code: 4, + Message: "Label \"%s\" not found", + } + + //NoneInstructionFound erro de nome de instrução não encontrado + NoneInstructionFound = &ErrorType{ + Code: 5, + Message: "None instruction found for \"%s\"", + } + + //InvalidOperandCount erro de quantidade de operadores invalidas + InvalidOperandCount = &ErrorType{ + Code: 6, + Message: "Invalid operand count", + } + + //DecoratorNotFound erro retornado ao não encontrar nenhuma instrução ou + // decorador com o nome informado + DecoratorNotFound = &ErrorType{ + Code: 7, + Message: "Decorator or instruction not found for \"%s\"", + } +) diff --git a/pherror/errortype.go b/pherror/errortype.go new file mode 100644 index 0000000..fee745c --- /dev/null +++ b/pherror/errortype.go @@ -0,0 +1,91 @@ +package pherror + +import ( + "fmt" + "ph1-assembly/constants" +) + +// ErrorType é utilizado para erros relacionados ao montador +type ErrorType struct { + Message string + Code int + Filename string + LineNumber int +} + +// ErrorType método da interface de erro +func (err *ErrorType) Error() string { + var msg string = "Error" + var args []interface{} + + // Verifica se existe um código de erro + if err.Code != 0 { + msg += fmt.Sprintf(" [%%0%dd]", constants.MaxErrorCodeDigits) + args = append(args, err.Code) + } + + // Verifica se há um nome de arquivo no erro + if err.Filename != "" { + msg += " in %s" + args = append(args, err.Filename) + } + + // Verifica se há um número de linha informado no erro + if err.LineNumber != 0 { + msg += ":%d" + args = append(args, err.LineNumber) + } + + // Formata a mensagem e retorna o texto formatado do erro + args = append(args, err.Message) + return fmt.Sprintf(msg+": %s", args...) +} + +// Join unifica vários ErrorType em uma única cópia de ErrorType +func Join(errTypes ...*ErrorType) *ErrorType { + if len(errTypes) == 0 { + return nil + } + + retErr := *errTypes[0] + for _, err := range errTypes[1:] { + if err.Code != 0 { + retErr.Code = err.Code + } + if err.Filename != "" { + retErr.Filename = err.Filename + } + if err.LineNumber != 0 { + retErr.LineNumber = err.LineNumber + } + if err.Message != "" { + retErr.Message = err.Message + } + } + + return &retErr +} + +// Format formata qualquer erro em um ErrorType +func Format(msg interface{}, args ...interface{}) *ErrorType { + var errType ErrorType + + // Verifica se o erro passado é do tipo ErrorType + if pherr, ok := msg.(*ErrorType); ok { + if len(args) == 0 { + return pherr + } + errType = *pherr + errType.Message = fmt.Sprintf(errType.Message, args...) + } else if err, ok := msg.(error); ok { + // Cria um novo ErrorType a partir do erro genérico + errType = ErrorType{Message: fmt.Sprintf(err.Error(), args...)} + } else if msgstr, ok := msg.(string); ok { + // Formata a string se o erro for uma string + errType = ErrorType{Message: fmt.Sprintf(msgstr, args...)} + } else { + // Define como mensagem a representação do valor informado + errType = ErrorType{Message: fmt.Sprintf("%#v", msg)} + } + return &errType +}