From 198efceb1f14c9822bf87ede6961d0f94a4760db Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Thu, 23 Sep 2021 16:14:37 +0200 Subject: Implemented commands until helloworld works \o/ --- go.mod | 2 + go.sum | 4 + pkg/interpreter/interpreter.go | 4 +- pkg/interpreter/interpreter_test.go | 2 +- pkg/pointer/exec.go | 152 ++++++++++++++++++++++++++++++++++-- pkg/pointer/intput-output.go | 61 +++++++++++++++ pkg/pointer/pointer.go | 28 +++++-- pkg/pointer/pointer_test.go | 16 ++-- pkg/pointer/stack-stack.go | 8 -- pkg/pointer/stack-stack_test.go | 10 --- 10 files changed, 243 insertions(+), 44 deletions(-) create mode 100644 pkg/pointer/intput-output.go diff --git a/go.mod b/go.mod index 718a815..4dea3d5 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ require github.com/stretchr/testify v1.7.0 require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pkg/term v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index c221f64..edc2e45 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,15 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= +github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/interpreter/interpreter.go b/pkg/interpreter/interpreter.go index 76917b7..433f264 100644 --- a/pkg/interpreter/interpreter.go +++ b/pkg/interpreter/interpreter.go @@ -16,14 +16,14 @@ func NewInterpreter(f *field.Field, p *pointer.Pointer) *Interpreter { func (i *Interpreter) Run() int { for i.p != nil { - if v := i.Step(); v != nil { + if v := i.step(); v != nil { return *v } } return 0 } -func (i *Interpreter) Step() *int { +func (i *Interpreter) step() *int { var prev *pointer.Pointer = nil for p := i.p; p != nil; p = p.Next { done, v := p.Exec(i.f) diff --git a/pkg/interpreter/interpreter_test.go b/pkg/interpreter/interpreter_test.go index ff633a2..73902b2 100644 --- a/pkg/interpreter/interpreter_test.go +++ b/pkg/interpreter/interpreter_test.go @@ -38,7 +38,7 @@ func TestStep(t *testing.T) { if tc.pointer == nil { tc.pointer = pointer.NewPointer() } - NewInterpreter(f, tc.pointer).Step() + NewInterpreter(f, tc.pointer).step() if tc.expectedField != nil { require.Equal(t, tc.expectedField, f) } diff --git a/pkg/pointer/exec.go b/pkg/pointer/exec.go index 9d55136..f7fcf8f 100644 --- a/pkg/pointer/exec.go +++ b/pkg/pointer/exec.go @@ -8,32 +8,168 @@ import ( func (p *Pointer) Exec(f *field.Field) (done bool, returnValue *int) { c := p.Get(*f) - for jumpingMode := false; jumpingMode || c == ' ' || c == ';'; c = p.StepAndGet(*f) { - if jumpingMode { + if p.stringMode { + if p.lastCharWasSpace { + for c == ' ' { + c = p.StepAndGet(*f) + } + p.lastCharWasSpace = false + } + if c == '"' { + p.stringMode = false + p.lastCharWasSpace = false + } else { + if c == ' ' { + p.lastCharWasSpace = true + } + p.ss.head.Push(c) + } + } else { + for jumpingMode := false; jumpingMode || c == ' ' || c == ';'; c = p.StepAndGet(*f) { if c == ';' { - jumpingMode = false + jumpingMode = !jumpingMode } - continue } + done, returnValue = p.eval(c, f) } + p.Step(*f) + return +} + +func (p *Pointer) eval(c int, f *field.Field) (done bool, returnValue *int) { + handled := true switch c { case '@': return true, nil case '#': p.Step(*f) case 'j': - n := p.Ss.Pop() + n := p.ss.head.Pop() for j := 0; j < n; j++ { p.Step(*f) } case 'q': - v := p.Ss.Pop() + v := p.ss.head.Pop() return true, &v + case 'k': + n := p.ss.head.Pop() + c = p.StepAndGet(*f) + for jumpingMode := false; jumpingMode || c == ' ' || c == ';'; c = p.StepAndGet(*f) { + if c == ';' { + jumpingMode = !jumpingMode + } + } + for j := 0; j < n; j++ { + p.eval(c, f) + } + case '!': + v := p.ss.head.Pop() + if v == 0 { + v = 1 + } else { + v = 0 + } + p.ss.head.Push(v) + case '`': + b, a := p.ss.head.Pop(), p.ss.head.Pop() + if a > b { + a = 1 + } else { + a = 0 + } + p.ss.head.Push(a) + case '_': + v := p.ss.head.Pop() + if v == 0 { + p.Redirect('>') + } else { + p.Redirect('<') + } + case '|': + v := p.ss.head.Pop() + if v == 0 { + p.Redirect('v') + } else { + p.Redirect('^') + } + case 'w': + b, a := p.ss.head.Pop(), p.ss.head.Pop() + if a < b { + p.Redirect('[') + } else if a > b { + p.Redirect(']') + } + case '+': + b, a := p.ss.head.Pop(), p.ss.head.Pop() + p.ss.head.Push(a + b) + case '*': + b, a := p.ss.head.Pop(), p.ss.head.Pop() + p.ss.head.Push(a * b) + case '-': + b, a := p.ss.head.Pop(), p.ss.head.Pop() + p.ss.head.Push(a - b) + case '/': + b, a := p.ss.head.Pop(), p.ss.head.Pop() + if b == 0 { + p.ss.head.Push(0) + return + } + p.ss.head.Push(a / b) + case '%': + b, a := p.ss.head.Pop(), p.ss.head.Pop() + if b == 0 { + p.ss.head.Push(0) + return + } + p.ss.head.Push(a % b) + case '"': + p.stringMode = true + case '\'': + p.ss.head.Push(p.StepAndGet(*f)) + case 's': + p.Step(*f) + f.Set(p.x, p.y, p.ss.head.Pop()) + case '$': + p.ss.head.Pop() + case ':': + p.ss.head.Duplicate() + case '\\': + p.ss.head.Swap() + case 'n': + p.ss.head.Clear() + case '{': + p.ss.Begin(p) + case '}': + p.ss.End(p) + case 'u': + p.ss.Under() + case 'g': + y, x := p.ss.head.Pop(), p.ss.head.Pop() + p.ss.head.Push(f.Get(x+p.sox, y+p.soy)) + case 'p': + y, x, v := p.ss.head.Pop(), p.ss.head.Pop(), p.ss.head.Pop() + f.Set(x+p.sox, y+p.soy, v) + case '.': + p.DecimalOutput(p.ss.head.Pop()) + case ',': + p.CharacterOutput(p.ss.head.Pop()) + case '&': + p.ss.head.Push(p.DecimalInput()) + case '~': + p.ss.head.Push(p.CharacterInput()) default: - if !p.Redirect(c) { + handled = false + } + if !handled { + switch { + case p.Redirect(c): + case c >= '0' && c <= '9': + p.ss.head.Push(c - '0') + case c >= 'a' && c <= 'f': + p.ss.head.Push(c - 'a' + 10) + default: log.Fatalf("Non implemented instruction code %d : %c", c, c) } } - p.Step(*f) return } diff --git a/pkg/pointer/intput-output.go b/pkg/pointer/intput-output.go new file mode 100644 index 0000000..cc7533e --- /dev/null +++ b/pkg/pointer/intput-output.go @@ -0,0 +1,61 @@ +package pointer + +import ( + "fmt" + "log" + "os" + + "github.com/pkg/term" +) + +var defaultInputLastChar *int = nil + +func DefaultCharacterInput() int { + if defaultInputLastChar != nil { + c := *defaultInputLastChar + defaultInputLastChar = nil + return c + } + t, err := term.Open("/dev/stdin") + if err != nil { + log.Fatalf("Could not open stdin: %+v", err) + } + defer t.Close() + defer t.Restore() + term.RawMode(t) + b := make([]byte, 1) + i, err := os.Stdin.Read(b) + if err != nil { + log.Fatalf("Error in DefaultCharacterInput { b: %c, i: %d, err: %+v }", b[0], i, err) + } + return int(b[0]) +} + +func DefaultDecimalInput() int { + var v int + for { + c := DefaultCharacterInput() + if c >= '0' && c <= '9' { + v = c - '0' + break + } + } + for { + c := DefaultCharacterInput() + if c >= '0' && c <= '9' { + v = v*10 + c - '0' + } else { + defaultInputLastChar = &c + break + } + } + return v +} + +func DefaultCharacterOutput(c int) { + fmt.Printf("%c", c) +} + +func DefaultDecimalOutput(c int) { + fmt.Printf("%d ", c) +} diff --git a/pkg/pointer/pointer.go b/pkg/pointer/pointer.go index 902fe69..2eea79d 100644 --- a/pkg/pointer/pointer.go +++ b/pkg/pointer/pointer.go @@ -6,6 +6,9 @@ import ( "git.adyxax.org/adyxax/gofunge/pkg/field" ) +type InputFunction func() int +type OutputFunction func(v int) + type Pointer struct { // the position x int @@ -16,18 +19,33 @@ type Pointer struct { // The Storage offset sox int soy int + // The stringmode flag + stringMode bool + lastCharWasSpace bool // The stack - Ss *StackStack + ss *StackStack // The next element for the multi-"threaded" b98 interpreter Next *Pointer + // The input/output functions + CharacterInput InputFunction + DecimalInput InputFunction + CharacterOutput OutputFunction + DecimalOutput OutputFunction } func NewPointer() *Pointer { - return &Pointer{dx: 1, Ss: NewStackStack()} + return &Pointer{ + dx: 1, + ss: NewStackStack(), + CharacterInput: DefaultCharacterInput, + DecimalInput: DefaultDecimalInput, + CharacterOutput: DefaultCharacterOutput, + DecimalOutput: DefaultDecimalOutput, + } } func (p Pointer) Split() *Pointer { - return &p // p is already a copy + return &p // p is already a copy TODO we need to duplicate the stack and handle the Next } func (p *Pointer) Step(f field.Field) { @@ -76,8 +94,8 @@ func (p *Pointer) Redirect(c int) bool { case 'r': p.Reverse() case 'x': - dy := p.Ss.Pop() - dx := p.Ss.Pop() + dy := p.ss.head.Pop() + dx := p.ss.head.Pop() p.RedirectTo(dx, dy) default: return false diff --git a/pkg/pointer/pointer_test.go b/pkg/pointer/pointer_test.go index aa007fe..59c09ee 100644 --- a/pkg/pointer/pointer_test.go +++ b/pkg/pointer/pointer_test.go @@ -8,10 +8,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestNewPointer(t *testing.T) { - require.Equal(t, NewPointer(), &Pointer{dx: 1, Ss: NewStackStack()}) -} - func TestSplit(t *testing.T) { file, err := os.Open("../field/test_data/hello.b98") require.NoError(t, err, "Failed to open file") @@ -23,12 +19,11 @@ func TestSplit(t *testing.T) { // We check that p2 is a real copy p.Step(*f) p2.Step(*f) - require.Equal(t, &Pointer{x: 1, y: 0, dx: 1, Ss: NewStackStack()}, p) - require.Equal(t, &Pointer{x: 1, y: 0, dx: 1, Ss: NewStackStack()}, p2) + require.Equal(t, 1, p.x) + require.Equal(t, 0, p.y) } func TestStep(t *testing.T) { // Step is thoroughly tested in the field package - defaultPointer := NewPointer() // File of one char file, err := os.Open("../field/test_data/minimal.b98") require.NoError(t, err, "Failed to open file") @@ -37,7 +32,8 @@ func TestStep(t *testing.T) { // Step is thoroughly tested in the field package require.NoError(t, err) p := NewPointer() p.Step(*f) - require.Equal(t, defaultPointer, p) + require.Equal(t, 0, p.x) + require.Equal(t, 0, p.y) } func TestGet(t *testing.T) { @@ -104,8 +100,8 @@ func TestRedirect(t *testing.T) { t.Run(tc.name, func(t *testing.T) { p := NewPointer() p.RedirectTo(3, 14) - p.Ss.Push(2) - p.Ss.Push(7) + p.ss.head.Push(2) + p.ss.head.Push(7) v := p.Redirect(int(tc.input)) require.Equal(t, true, v) require.Equal(t, tc.expectedDx, p.dx, "Invalid dx value") diff --git a/pkg/pointer/stack-stack.go b/pkg/pointer/stack-stack.go index 6d22f51..2f57b05 100644 --- a/pkg/pointer/stack-stack.go +++ b/pkg/pointer/stack-stack.go @@ -85,11 +85,3 @@ func (ss *StackStack) Under() (reflect bool) { } return false } - -func (ss StackStack) Pop() int { - return ss.head.Pop() -} - -func (ss StackStack) Push(v int) { - ss.head.Push(v) -} diff --git a/pkg/pointer/stack-stack_test.go b/pkg/pointer/stack-stack_test.go index fd4c9ea..d5809fd 100644 --- a/pkg/pointer/stack-stack_test.go +++ b/pkg/pointer/stack-stack_test.go @@ -248,13 +248,3 @@ func TestUnder(t *testing.T) { require.Equal(t, expected, ss) }) } - -func TestPushPop(t *testing.T) { - ss := NewStackStack() - ss.Push(12) - ss.Push(5) - v := ss.Pop() - require.Equal(t, 5, v) - v = ss.Pop() - require.Equal(t, 12, v) -} -- cgit v1.2.3