From 4e9310fe8b394f2793238c51e976493f030cca3b Mon Sep 17 00:00:00 2001 From: Yutaka Ichibangase Date: Sun, 12 Dec 2021 11:52:08 +0900 Subject: [PATCH 1/4] fix raw mode output --- cmd/1pl/main.go | 2 +- engine/builtin.go | 122 ++++++++++++++++++++++++++--------------- engine/builtin_test.go | 104 ++++++++++++++++++++++++++++------- engine/stream.go | 19 +++++-- engine/stream_test.go | 5 +- interpreter.go | 4 +- 6 files changed, 182 insertions(+), 74 deletions(-) diff --git a/cmd/1pl/main.go b/cmd/1pl/main.go index 20964b4a..2b1e5ed0 100644 --- a/cmd/1pl/main.go +++ b/cmd/1pl/main.go @@ -42,7 +42,7 @@ func main() { log.SetOutput(t) - i := prolog.New(os.Stdin, os.Stdout) + i := prolog.New(os.Stdin, t) i.Register1("halt", func(t engine.Term, k func(*engine.Env) *engine.Promise, env *engine.Env) *engine.Promise { restore() return engine.Halt(t, k, env) diff --git a/engine/builtin.go b/engine/builtin.go index c142adbd..f84421b6 100644 --- a/engine/builtin.go +++ b/engine/builtin.go @@ -32,26 +32,56 @@ type State struct { debug bool } -// SetUserInput sets the given reader as a stream with an alias of user_input. -func (state *State) SetUserInput(f *os.File) { - s := NewStream(f, StreamModeRead) - state.setStreamAlias("user_input", s) - state.input = s +var errNotSupported = errors.New("not supported") + +type rwc struct { + r io.Reader + w io.Writer + c io.Closer } -// SetUserOutput sets the given writer as a stream with an alias of user_output. -func (state *State) SetUserOutput(f *os.File) { - s := NewStream(f, StreamModeWrite) - state.setStreamAlias("user_output", s) - state.output = s +func (a *rwc) Read(p []byte) (int, error) { + if a.r == nil { + return 0, errNotSupported + } + return a.r.Read(p) +} + +func (a *rwc) Write(p []byte) (int, error) { + if a.w == nil { + return 0, errNotSupported + } + return a.w.Write(p) +} + +func (a *rwc) Close() error { + if a.c == nil { + return errNotSupported + } + return a.c.Close() } -func (state *State) setStreamAlias(alias Atom, s *Stream) { - s.alias = alias - if state.streams == nil { - state.streams = map[Term]*Stream{} +func readWriteCloser(i interface{}) io.ReadWriteCloser { + if f, ok := i.(io.ReadWriteCloser); ok { + return f } - state.streams[alias] = s + var f rwc + f.r, _ = i.(io.Reader) + f.w, _ = i.(io.Writer) + f.c, _ = i.(io.Closer) + return &f +} + +// SetUserInput sets the given reader as a stream with an alias of user_input. +func (state *State) SetUserInput(r io.Reader, opts ...StreamOption) { + opts = append(opts, WithAlias(state, "user_input")) + state.input = NewStream(readWriteCloser(r), StreamModeRead, opts...) +} + +// SetUserOutput sets the given writer as a stream with an alias of user_output. +func (state *State) SetUserOutput(w io.Writer, opts ...StreamOption) { + opts = append(opts, WithAlias(state, "user_output")) + state.output = NewStream(readWriteCloser(w), StreamModeWrite, opts...) } func (state *State) Parser(r io.Reader, vars *[]ParsedVariable) *Parser { @@ -1058,8 +1088,10 @@ func (state *State) FlushOutput(streamOrAlias Term, k func(*Env) *Promise, env * return Error(permissionErrorOutputStream(streamOrAlias)) } - if err := sync(s.file); err != nil { - return Error(err) + if f, ok := s.file.(*os.File); ok { + if err := sync(f); err != nil { + return Error(err) + } } return k(env) @@ -1183,7 +1215,7 @@ func CharCode(char, code Term, k func(*Env) *Promise, env *Env) *Promise { } } -var write = (*os.File).Write +var write = io.Writer.Write // PutByte outputs an integer byte to a stream represented by streamOrAlias. func (state *State) PutByte(streamOrAlias, byt Term, k func(*Env) *Promise, env *Env) *Promise { @@ -2444,30 +2476,32 @@ func (state *State) StreamProperty(streamOrAlias, property Term, k func(*Env) *P properties = append(properties, &Compound{Functor: "eof_action", Args: []Term{Atom("reset")}}) } - pos, err := s.file.Seek(0, 1) - if err != nil { - return Error(err) - } - pos -= int64(s.buf.Buffered()) + if f, ok := s.file.(*os.File); ok { + pos, err := f.Seek(0, 1) + if err != nil { + return Error(err) + } + pos -= int64(s.buf.Buffered()) - fi, err := s.file.Stat() - if err != nil { - return Error(err) - } + fi, err := f.Stat() + if err != nil { + return Error(err) + } - eos := "not" - switch { - case pos == fi.Size(): - eos = "at" - case pos > fi.Size(): - eos = "past" - } + eos := "not" + switch { + case pos == fi.Size(): + eos = "at" + case pos > fi.Size(): + eos = "past" + } - properties = append(properties, - &Compound{Functor: "file_name", Args: []Term{Atom(s.file.Name())}}, - &Compound{Functor: "position", Args: []Term{Integer(pos)}}, - &Compound{Functor: "end_of_stream", Args: []Term{Atom(eos)}}, - ) + properties = append(properties, + &Compound{Functor: "file_name", Args: []Term{Atom(f.Name())}}, + &Compound{Functor: "position", Args: []Term{Integer(pos)}}, + &Compound{Functor: "end_of_stream", Args: []Term{Atom(eos)}}, + ) + } if s.reposition { properties = append(properties, &Compound{Functor: "reposition", Args: []Term{Atom("true")}}) @@ -2507,11 +2541,13 @@ func (state *State) SetStreamPosition(streamOrAlias, position Term, k func(*Env) case Variable: return Error(InstantiationError(position)) case Integer: - if _, err := s.file.Seek(int64(p), 0); err != nil { - return Error(SystemError(err)) - } + if f, ok := s.file.(io.Seeker); ok { + if _, err := f.Seek(int64(p), 0); err != nil { + return Error(SystemError(err)) + } - s.buf.Reset(s.file) + s.buf.Reset(s.file) + } return k(env) default: diff --git a/engine/builtin_test.go b/engine/builtin_test.go index 0860bd36..766722dc 100644 --- a/engine/builtin_test.go +++ b/engine/builtin_test.go @@ -5,17 +5,81 @@ import ( "context" "errors" "fmt" + "io" "io/ioutil" "math" "os" "path/filepath" "testing" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) +func TestState_SetUserInput(t *testing.T) { + t.Run("file", func(t *testing.T) { + var state State + state.SetUserInput(os.Stdin) + assert.Equal(t, os.Stdin, state.streams[Atom("user_input")].file) + }) + + t.Run("ReadCloser", func(t *testing.T) { + var state State + var r struct { + io.Reader + io.Closer + } + { + } + state.SetUserInput(&r) + assert.Equal(t, &r, state.streams[Atom("user_input")].file.(*rwc).r) + assert.Equal(t, &r, state.streams[Atom("user_input")].file.(*rwc).c) + }) + + t.Run("Reader", func(t *testing.T) { + var state State + var r struct { + io.Reader + } + { + } + state.SetUserInput(&r) + assert.Equal(t, &r, state.streams[Atom("user_input")].file.(*rwc).r) + }) +} + +func TestState_SetUserOutput(t *testing.T) { + t.Run("file", func(t *testing.T) { + var state State + state.SetUserOutput(os.Stdout) + assert.Equal(t, os.Stdout, state.streams[Atom("user_output")].file) + }) + + t.Run("WriteCloser", func(t *testing.T) { + var state State + var w struct { + io.Writer + io.Closer + } + { + } + state.SetUserOutput(&w) + assert.Equal(t, &w, state.streams[Atom("user_output")].file.(*rwc).w) + assert.Equal(t, &w, state.streams[Atom("user_output")].file.(*rwc).c) + }) + + t.Run("Writer", func(t *testing.T) { + var state State + var w struct { + io.Writer + } + { + } + state.SetUserOutput(&w) + assert.Equal(t, &w, state.streams[Atom("user_output")].file.(*rwc).w) + }) +} + func TestState_Call(t *testing.T) { var state State @@ -2977,11 +3041,11 @@ func TestState_Close(t *testing.T) { }) t.Run("ng", func(t *testing.T) { - closeFile = func(f *os.File) error { + closeFile = func(f io.Closer) error { return errors.New("ng") } defer func() { - closeFile = (*os.File).Close + closeFile = io.Closer.Close }() s, err := Open(Atom(f.Name()), StreamModeRead) @@ -3011,11 +3075,11 @@ func TestState_Close(t *testing.T) { }) t.Run("ng", func(t *testing.T) { - closeFile = func(f *os.File) error { + closeFile = func(f io.Closer) error { return errors.New("ng") } defer func() { - closeFile = (*os.File).Close + closeFile = io.Closer.Close }() s, err := Open(Atom(f.Name()), StreamModeRead) @@ -3049,11 +3113,11 @@ func TestState_Close(t *testing.T) { }) t.Run("ng", func(t *testing.T) { - closeFile = func(f *os.File) error { + closeFile = func(f io.Closer) error { return errors.New("ng") } defer func() { - closeFile = (*os.File).Close + closeFile = io.Closer.Close }() s, err := Open(Atom(f.Name()), StreamModeRead) @@ -3517,12 +3581,12 @@ func TestCharCode(t *testing.T) { func TestState_PutByte(t *testing.T) { t.Run("ok", func(t *testing.T) { - write = func(f *os.File, b []byte) (int, error) { + write = func(f io.Writer, b []byte) (int, error) { assert.Equal(t, []byte{97}, b) return 1, nil } defer func() { - write = (*os.File).Write + write = io.Writer.Write }() s := NewStream(os.Stdout, StreamModeWrite) @@ -3535,12 +3599,12 @@ func TestState_PutByte(t *testing.T) { }) t.Run("ng", func(t *testing.T) { - write = func(f *os.File, b []byte) (int, error) { + write = func(f io.Writer, b []byte) (int, error) { assert.Equal(t, []byte{97}, b) return 0, errors.New("") } defer func() { - write = (*os.File).Write + write = io.Writer.Write }() s := NewStream(os.Stdout, StreamModeWrite) @@ -3552,12 +3616,12 @@ func TestState_PutByte(t *testing.T) { }) t.Run("valid stream alias", func(t *testing.T) { - write = func(f *os.File, b []byte) (int, error) { + write = func(f io.Writer, b []byte) (int, error) { assert.Equal(t, []byte{97}, b) return 1, nil } defer func() { - write = (*os.File).Write + write = io.Writer.Write }() s := NewStream(os.Stdout, StreamModeWrite) @@ -3636,12 +3700,12 @@ func TestState_PutByte(t *testing.T) { func TestState_PutCode(t *testing.T) { t.Run("ok", func(t *testing.T) { - write = func(f *os.File, b []byte) (int, error) { + write = func(f io.Writer, b []byte) (int, error) { assert.Equal(t, []byte{0xf0, 0x9f, 0x98, 0x80}, b) return 1, nil } defer func() { - write = (*os.File).Write + write = io.Writer.Write }() s := NewStream(os.Stdout, StreamModeWrite) @@ -3653,12 +3717,12 @@ func TestState_PutCode(t *testing.T) { }) t.Run("ng", func(t *testing.T) { - write = func(f *os.File, b []byte) (int, error) { + write = func(f io.Writer, b []byte) (int, error) { assert.Equal(t, []byte{0xf0, 0x9f, 0x98, 0x80}, b) return 0, errors.New("") } defer func() { - write = (*os.File).Write + write = io.Writer.Write }() s := NewStream(os.Stdout, StreamModeWrite) @@ -3669,12 +3733,12 @@ func TestState_PutCode(t *testing.T) { }) t.Run("valid stream alias", func(t *testing.T) { - write = func(f *os.File, b []byte) (int, error) { + write = func(f io.Writer, b []byte) (int, error) { assert.Equal(t, []byte{0xf0, 0x9f, 0x98, 0x80}, b) return 1, nil } defer func() { - write = (*os.File).Write + write = io.Writer.Write }() s := NewStream(os.Stdout, StreamModeWrite) diff --git a/engine/stream.go b/engine/stream.go index 87abacdf..0242e275 100644 --- a/engine/stream.go +++ b/engine/stream.go @@ -3,6 +3,7 @@ package engine import ( "bufio" "fmt" + "io" "io/fs" "os" "strings" @@ -44,7 +45,7 @@ const ( // Stream is a prolog stream. type Stream struct { - file *os.File + file io.ReadWriteCloser buf *bufio.Reader mode StreamMode alias Atom @@ -54,14 +55,16 @@ type Stream struct { } // NewStream creates a new stream from an opened file. -func NewStream(f *os.File, mode StreamMode, opts ...StreamOption) *Stream { +func NewStream(f io.ReadWriteCloser, mode StreamMode, opts ...StreamOption) *Stream { s := Stream{ file: f, buf: bufio.NewReader(f), mode: mode, } - if stat, err := f.Stat(); err == nil { - s.reposition = stat.Mode()&fs.ModeType == 0 + if f, ok := f.(*os.File); ok { + if stat, err := f.Stat(); err == nil { + s.reposition = stat.Mode()&fs.ModeType == 0 + } } for _, opt := range opts { opt(&s) @@ -75,7 +78,11 @@ type StreamOption func(*Stream) // WithAlias sets an alias for the stream. func WithAlias(state *State, alias Atom) StreamOption { return func(s *Stream) { - state.setStreamAlias(alias, s) + s.alias = alias + if state.streams == nil { + state.streams = map[Term]*Stream{} + } + state.streams[alias] = s } } @@ -119,7 +126,7 @@ func Open(name Atom, mode StreamMode, opts ...StreamOption) (*Stream, error) { return NewStream(f, mode, opts...), nil } -var closeFile = (*os.File).Close +var closeFile = io.Closer.Close // Close closes the underlying file of the stream. func (s *Stream) Close() error { diff --git a/engine/stream_test.go b/engine/stream_test.go index 90e3f8e7..df9fc6bd 100644 --- a/engine/stream_test.go +++ b/engine/stream_test.go @@ -3,6 +3,7 @@ package engine import ( "errors" "fmt" + "io" "os" "testing" @@ -137,13 +138,13 @@ func TestStream_Close(t *testing.T) { file: &f, } var called bool - closeFile = func(file *os.File) error { + closeFile = func(file io.Closer) error { assert.Equal(t, &f, file) called = true return nil } defer func() { - closeFile = (*os.File).Close + closeFile = io.Closer.Close }() assert.NoError(t, s.Close()) assert.True(t, called) diff --git a/interpreter.go b/interpreter.go index 206126f8..6dd17993 100644 --- a/interpreter.go +++ b/interpreter.go @@ -4,8 +4,8 @@ import ( "context" _ "embed" "fmt" + "io" "io/ioutil" - "os" "strings" "github.com/ichiban/prolog/engine" @@ -31,7 +31,7 @@ type Interpreter struct { } // New creates a new Prolog interpreter with predefined predicates/operators. -func New(in, out *os.File) *Interpreter { +func New(in io.Reader, out io.Writer) *Interpreter { var i Interpreter i.SetUserInput(in) i.SetUserOutput(out) From 13b856d9e436c2a228f0691371b89dbb50c4939f Mon Sep 17 00:00:00 2001 From: Yutaka Ichibangase Date: Sun, 12 Dec 2021 13:03:53 +0900 Subject: [PATCH 2/4] fix tests --- engine/builtin_test.go | 135 ++++++++++++++++++++++++++++++++--------- 1 file changed, 107 insertions(+), 28 deletions(-) diff --git a/engine/builtin_test.go b/engine/builtin_test.go index 766722dc..d71975f0 100644 --- a/engine/builtin_test.go +++ b/engine/builtin_test.go @@ -20,31 +20,57 @@ func TestState_SetUserInput(t *testing.T) { t.Run("file", func(t *testing.T) { var state State state.SetUserInput(os.Stdin) - assert.Equal(t, os.Stdin, state.streams[Atom("user_input")].file) + + s, ok := state.streams[Atom("user_input")] + assert.True(t, ok) + assert.Equal(t, os.Stdin, s.file) }) t.Run("ReadCloser", func(t *testing.T) { - var state State var r struct { - io.Reader - io.Closer - } - { + mockReader + mockCloser } + r.mockReader.On("Read", mock.Anything).Return(0, nil).Once() + defer r.mockReader.AssertExpectations(t) + r.mockCloser.On("Close").Return(nil).Once() + defer r.mockCloser.AssertExpectations(t) + + var state State state.SetUserInput(&r) - assert.Equal(t, &r, state.streams[Atom("user_input")].file.(*rwc).r) - assert.Equal(t, &r, state.streams[Atom("user_input")].file.(*rwc).c) + + s, ok := state.streams[Atom("user_input")] + assert.True(t, ok) + + n, err := s.file.Read(nil) + assert.NoError(t, err) + assert.Equal(t, 0, n) + + _, err = s.file.Write(nil) + assert.Equal(t, errNotSupported, err) + + assert.NoError(t, s.file.Close()) }) t.Run("Reader", func(t *testing.T) { + var r mockReader + r.On("Read", mock.Anything).Return(0, nil).Once() + defer r.AssertExpectations(t) + var state State - var r struct { - io.Reader - } - { - } state.SetUserInput(&r) - assert.Equal(t, &r, state.streams[Atom("user_input")].file.(*rwc).r) + + s, ok := state.streams[Atom("user_input")] + assert.True(t, ok) + + n, err := s.file.Read(nil) + assert.NoError(t, err) + assert.Equal(t, 0, n) + + _, err = s.file.Write(nil) + assert.Equal(t, errNotSupported, err) + + assert.Equal(t, errNotSupported, s.file.Close()) }) } @@ -52,34 +78,87 @@ func TestState_SetUserOutput(t *testing.T) { t.Run("file", func(t *testing.T) { var state State state.SetUserOutput(os.Stdout) - assert.Equal(t, os.Stdout, state.streams[Atom("user_output")].file) + + s, ok := state.streams[Atom("user_output")] + assert.True(t, ok) + assert.Equal(t, os.Stdout, s.file) }) t.Run("WriteCloser", func(t *testing.T) { - var state State var w struct { - io.Writer - io.Closer - } - { + mockWriter + mockCloser } + w.mockWriter.On("Write", mock.Anything).Return(0, nil).Once() + defer w.mockWriter.AssertExpectations(t) + w.mockCloser.On("Close").Return(nil).Once() + defer w.mockCloser.AssertExpectations(t) + + var state State state.SetUserOutput(&w) - assert.Equal(t, &w, state.streams[Atom("user_output")].file.(*rwc).w) - assert.Equal(t, &w, state.streams[Atom("user_output")].file.(*rwc).c) + + s, ok := state.streams[Atom("user_output")] + assert.True(t, ok) + + _, err := s.file.Read(nil) + assert.Equal(t, errNotSupported, err) + + n, err := s.file.Write(nil) + assert.NoError(t, err) + assert.Equal(t, 0, n) + + assert.NoError(t, s.file.Close()) }) t.Run("Writer", func(t *testing.T) { + var w mockWriter + w.On("Write", mock.Anything).Return(0, nil).Once() + defer w.AssertExpectations(t) + var state State - var w struct { - io.Writer - } - { - } state.SetUserOutput(&w) - assert.Equal(t, &w, state.streams[Atom("user_output")].file.(*rwc).w) + + s, ok := state.streams[Atom("user_output")] + assert.True(t, ok) + + _, err := s.file.Read(nil) + assert.Equal(t, errNotSupported, err) + + n, err := s.file.Write(nil) + assert.NoError(t, err) + assert.Equal(t, 0, n) + + assert.Equal(t, errNotSupported, s.file.Close()) }) } +type mockReader struct { + mock.Mock +} + +func (m *mockReader) Read(p []byte) (int, error) { + args := m.Called(p) + return args.Int(0), args.Error(1) +} + +type mockWriter struct { + mock.Mock +} + +func (m *mockWriter) Write(p []byte) (int, error) { + args := m.Called(p) + return args.Int(0), args.Error(1) +} + +type mockCloser struct { + mock.Mock +} + +func (m *mockCloser) Close() error { + args := m.Called() + return args.Error(0) +} + func TestState_Call(t *testing.T) { var state State From 3b8ab7535bfcfe3970042078a0c248569967d690 Mon Sep 17 00:00:00 2001 From: Yutaka Ichibangase Date: Sun, 12 Dec 2021 13:19:22 +0900 Subject: [PATCH 3/4] fix more tests --- engine/builtin.go | 4 +++- engine/builtin_test.go | 31 ++++++++++++++++++++++--------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/engine/builtin.go b/engine/builtin.go index f84421b6..2e148998 100644 --- a/engine/builtin.go +++ b/engine/builtin.go @@ -2526,6 +2526,8 @@ func (state *State) StreamProperty(streamOrAlias, property Term, k func(*Env) *P return Delay(ks...) } +var seek = io.Seeker.Seek + // SetStreamPosition sets the position property of the stream represented by streamOrAlias. func (state *State) SetStreamPosition(streamOrAlias, position Term, k func(*Env) *Promise, env *Env) *Promise { s, err := state.stream(streamOrAlias, env) @@ -2542,7 +2544,7 @@ func (state *State) SetStreamPosition(streamOrAlias, position Term, k func(*Env) return Error(InstantiationError(position)) case Integer: if f, ok := s.file.(io.Seeker); ok { - if _, err := f.Seek(int64(p), 0); err != nil { + if _, err := seek(f, int64(p), 0); err != nil { return Error(SystemError(err)) } diff --git a/engine/builtin_test.go b/engine/builtin_test.go index d71975f0..fc7fc626 100644 --- a/engine/builtin_test.go +++ b/engine/builtin_test.go @@ -6283,15 +6283,8 @@ func TestState_StreamProperty(t *testing.T) { } func TestState_SetStreamPosition(t *testing.T) { - f, err := ioutil.TempFile("", "") - assert.NoError(t, err) - - defer func() { - assert.NoError(t, os.Remove(f.Name())) - }() - t.Run("ok", func(t *testing.T) { - s, err := Open(Atom(f.Name()), StreamModeRead) + s, err := Open("testdata/empty.txt", StreamModeRead) assert.NoError(t, err) defer func() { assert.NoError(t, s.Close()) @@ -6303,6 +6296,26 @@ func TestState_SetStreamPosition(t *testing.T) { assert.True(t, ok) }) + t.Run("seek failed", func(t *testing.T) { + seek = func(f io.Seeker, offset int64, whence int) (int64, error) { + return 0, errors.New("failed") + } + defer func() { + seek = io.Seeker.Seek + }() + + s, err := Open("testdata/empty.txt", StreamModeRead) + assert.NoError(t, err) + defer func() { + assert.NoError(t, s.Close()) + }() + + var state State + ok, err := state.SetStreamPosition(s, Integer(0), Success, nil).Force(context.Background()) + assert.Error(t, err) + assert.False(t, ok) + }) + t.Run("streamOrAlias is a variable", func(t *testing.T) { streamOrAlias := Variable("Stream") @@ -6313,7 +6326,7 @@ func TestState_SetStreamPosition(t *testing.T) { }) t.Run("position is a variable", func(t *testing.T) { - s, err := Open(Atom(f.Name()), StreamModeRead) + s, err := Open("testdata/empty.txt", StreamModeRead) assert.NoError(t, err) defer func() { assert.NoError(t, s.Close()) From de6a6d6f90d037d93fa271ebe743eaa2b86dfdcd Mon Sep 17 00:00:00 2001 From: Yutaka Ichibangase Date: Sun, 12 Dec 2021 14:21:15 +0900 Subject: [PATCH 4/4] optimize for *bufio.Reader --- engine/stream.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/engine/stream.go b/engine/stream.go index 0242e275..9b1500f0 100644 --- a/engine/stream.go +++ b/engine/stream.go @@ -58,7 +58,6 @@ type Stream struct { func NewStream(f io.ReadWriteCloser, mode StreamMode, opts ...StreamOption) *Stream { s := Stream{ file: f, - buf: bufio.NewReader(f), mode: mode, } if f, ok := f.(*os.File); ok { @@ -69,6 +68,11 @@ func NewStream(f io.ReadWriteCloser, mode StreamMode, opts ...StreamOption) *Str for _, opt := range opts { opt(&s) } + if a, ok := f.(*rwc); ok && a.r != nil { + s.buf = bufio.NewReader(a.r) + } else { + s.buf = bufio.NewReader(f) + } return &s }