Skip to content

Commit

Permalink
Desafio 10 by MatMercer (#985)
Browse files Browse the repository at this point in the history
  • Loading branch information
MatMercer authored Aug 16, 2023
1 parent 76b6952 commit 45547a2
Show file tree
Hide file tree
Showing 6 changed files with 394 additions and 0 deletions.
1 change: 1 addition & 0 deletions desafio-10/MatMercer/go/.valid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
138578983d1615ac9c4cb284f379847fe
26 changes: 26 additions & 0 deletions desafio-10/MatMercer/go/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.DEFAULT_GOAL := run

turing.tar.gz:
@echo downloading turing programs...
wget https://osprogramadores.com/files/d10/turing.tar.gz 2> /dev/null
tar zxvf turing.tar.gz

turing: turing.tar.gz main.go machine/machine.go go.mod
go build -o turing

.PHONY: run
run: turing
@echo turing output:
@echo
@./turing datafile

.PHONY: run-debug
run-debug: export DEBUG = true
run-debug: turing
@./turing datafile

clean:
@echo "cleaning files"
rm -f *.tur *.gz *.gz.* turing datafile


16 changes: 16 additions & 0 deletions desafio-10/MatMercer/go/README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Solução desafio 10

Para rodar, é necessário ter os seguintes programas:

* go
* wget
* make

Basta rodar esse comando para executar o programa:

```bash
# modo normal
make run
# modo debug
make run-debug
```
3 changes: 3 additions & 0 deletions desafio-10/MatMercer/go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module matbm.net/turing-machine

go 1.20
275 changes: 275 additions & 0 deletions desafio-10/MatMercer/go/machine/machine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
package machine

import (
"bufio"
"fmt"
"io"
"log"
"strings"
)

type state string

type symbol byte
type memory []symbol

type direction byte

type programEntry struct {
newSym symbol // new symbol
dir direction // direction
newState state // newState new state to set
}
type program map[state]map[symbol]programEntry

type TuringMachine struct {
pos int // the position of the current machine
prog program // the machine program
state state // current state
mem memory // memory
nMem memory // negative memory for negative indexes
debug *log.Logger // logger for debug
}

func (m *TuringMachine) Run() {
_, hasGlobalState := m.prog["*"]

for {
// found program entry
var e programEntry

currentSymbol := m.cur()
// spaces must be lookup as '_' in the entry tree
if currentSymbol == ' ' {
currentSymbol = '_'
}
m.debug.Printf("cur: %c:%d\n", currentSymbol, currentSymbol)

// check for exact entries in global state, since precedence
var matchGlobalEntry bool
if hasGlobalState {
ge, ok := m.prog["*"][currentSymbol]
if ok {
matchGlobalEntry = true
e = ge
}
}

// no global entry found do normal entry logic
if !matchGlobalEntry {
// get current entry tree
et, ok := m.prog[m.state]
if !ok {
// halt, no way to continue
if !hasGlobalState {
m.debug.Println("halt at no state found")
m.error()
return
}
et = m.prog["*"]
}

// try to find a matching entry by symbol
e, ok = et[currentSymbol]
if !ok {
// try to find a generic entry symbol
g, ok := et['*']
// halt, no way to continue
if !ok {
m.debug.Println("halt at no symbol found")
m.error()
return
}
e = g
}
m.debug.Printf("entry: %+v\n", e)
}

// swap to new symbol
newSymbol := e.newSym
// check if new symbol must be a space
if newSymbol == '_' {
newSymbol = ' '
}
// no operation
if newSymbol == '*' {
newSymbol = currentSymbol
}
m.debug.Printf("changed %q to %q at %d: %q\n", currentSymbol, newSymbol, m.pos, m.GetMemory())
m.updateSymbol(newSymbol)

// set new state
newState := e.newState
// halt state detection
if strings.HasPrefix(string(newState), string(halt)) {
m.debug.Printf("halt at %q state\n", newState)
return
}
// error state detection
if strings.HasPrefix(string(newState), string(errState)) {
m.debug.Printf("halt at %q state\n", newState)
m.error()
return
}
m.debug.Printf("state change %q -> %q", m.state, newState)
m.state = newState

// walk the needle
if e.dir == left {
m.debug.Println("left")
m.pos -= 1
} else if e.dir == right {
m.debug.Println("right")
m.pos += 1
}
}
}

// Reverse reverses a string
func Reverse(s string) string {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}

// cur returns current symbol, negative indexes are supported
func (m *TuringMachine) cur() symbol {
// deal with negative memory
if m.pos < 0 {
// real index of negative memory is
// -1 = 0
// -2 = 1
return m.currentSymbol(&m.nMem, (m.pos*-1)-1)
}
return m.currentSymbol(&m.mem, m.pos)
}

// currentSymbol returns current symbol, growing mem accordingly
func (m *TuringMachine) currentSymbol(mem *memory, i int) symbol {
if len(*mem)-1 < i {
*mem = append(*mem, ' ')
return ' '
}
return (*mem)[i]
}

// updateSymbol updates a symbol with negative index support using nMem
func (m *TuringMachine) updateSymbol(newSymbol symbol) {
if m.pos < 0 {
m.nMem[(m.pos*-1)-1] = newSymbol
} else {
m.mem[m.pos] = newSymbol
}

}

// error sets the machine into error, output is ERR and negative memory is empty
func (m *TuringMachine) error() {
m.nMem = nil
m.mem = memory("ERR")
}

func (m *TuringMachine) GetMemory() string {
return strings.TrimSpace(Reverse(string(m.nMem)) + string(m.mem))
}

const (
left = direction(iota)
right
noDir
invalid
)

const initialState = "0"

const halt = state("halt")

const errState = state("error")

// New creates a new turing machine.
func New(progSource io.Reader, input string, debug *log.Logger) (*TuringMachine, error) {
fileScanner := bufio.NewScanner(progSource)
fileScanner.Split(bufio.ScanLines)

line := 0

prog := program{}
for fileScanner.Scan() {
line++
text := strings.TrimSpace(fileScanner.Text())
// ignore empty lines
if len(text) == 0 {
continue
}

// ignore comments
if text[0] == ';' {
continue
}

// sanitize comments like that
// a b state d e ; a comment
text = strings.TrimSpace(strings.Split(text, ";")[0])

// scan program line and validate it
tokens := strings.Split(text, " ")
if len(tokens) != 5 {
return nil, fmt.Errorf("invalid syntax at line %d, expected 5 tokens, got %d: %q", line, len(tokens), text)
}
// extract program line as bytes
d := tokens[3]

// everything ok, create the entry
targetState := state(tokens[0])
targetSymbol := symbol(toSingleByte(tokens[1]))
if prog[targetState] == nil {
prog[targetState] = map[symbol]programEntry{}
}

// validate direction
dir := parseDirection(d)
if dir == invalid {
return nil, fmt.Errorf("invalid direction at line %d: %q -> must be one of l,r,*", line, d)
}
prog[targetState][targetSymbol] = programEntry{
newSym: symbol(toSingleByte(tokens[2])),
dir: dir,
newState: state(tokens[4]),
}
}

debug.Printf("created program: %+v\n", prog)

return &TuringMachine{
pos: 0,
prog: prog,
state: initialState,
mem: []symbol(input),
nMem: memory{},
debug: debug,
}, nil
}

// toSingleByte return strings like "a", "v" as their byte values
func toSingleByte(s string) byte {
if len(s) == 0 {
return 0
}

return []byte(s)[0]
}

func parseDirection(d string) direction {
switch d {
case "*":
return noDir
case "l":
return left
case "r":
return right
}

return invalid
}
73 changes: 73 additions & 0 deletions desafio-10/MatMercer/go/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package main

import (
"bufio"
"fmt"
"io"
"log"
"matbm.net/turing-machine/machine"
"os"
"strings"
)

var debug *log.Logger
var stderr *log.Logger

func runPrograms(fileName string) error {
f, err := os.Open(fileName)
if err != nil {
return err
}
fileScanner := bufio.NewScanner(f)
fileScanner.Split(bufio.ScanLines)

line := 0
for fileScanner.Scan() {
line++
// expect line: prog.tur,input
text := strings.TrimSpace(fileScanner.Text())
if len(text) == 0 {
continue
}

progAndInput := strings.Split(text, ",")
if len(progAndInput) != 2 {
return fmt.Errorf("%s: invalid format at line %d: '%s' -> expected prog.tur,input", fileName, line, text)
}

progFileName, input := progAndInput[0], progAndInput[1]
progFile, err := os.Open(progFileName)
if err != nil {
return fmt.Errorf("failed to open %q program file: %s", progFileName, err)
}
turMachine, err := machine.New(progFile, input, debug)
if err != nil {
return fmt.Errorf("failed to create %q program with %q input: %s", progFileName, input, err)
}
_ = progFile.Close()
turMachine.Run()

// output the turMachine processing
fmt.Printf("%s,%s,%s\n", progFileName, input, turMachine.GetMemory())
}

return nil
}

func main() {
// init stderr log
stderr = log.New(os.Stderr, "", 0)
if len(os.Args) != 2 {
stderr.Fatalf("Programs file required, usage: %s [programs file]", os.Args[0])
}

debug = log.New(io.Discard, "", 0)
// init a debug logger if DEBUG env var is set
if strings.ToLower(os.Getenv("DEBUG")) == "true" {
debug = log.New(os.Stderr, "DEBUG ", log.Ldate|log.Ltime|log.Lmicroseconds)
}

if err := runPrograms(os.Args[1]); err != nil {
stderr.Fatalf("Turing machine error: %v", err)
}
}

0 comments on commit 45547a2

Please sign in to comment.