Skip to content

Commit

Permalink
Allow I/O operations to have a level of indirection.
Browse files Browse the repository at this point in the history
This pull-request supersedes #121, which was never merged, as a
solution to embedding the interpreter and allowing flexible I/O
handling:

* Rather than changing our builtin-signatures we introduce a new
  config/ package.
* We pass an instance of the configuration object, containing handles,
  to the interpreter.
* This is then accessed to read/write to STDIN/STDOUT.

The test-case for (print), TestPrint, was updated to send output
to a faux I/O helper - which is actually a buffer - and shows that
the indirection works.

And now that we've implemented the I/O helper we've updated (read)
to use it appropriately too.
  • Loading branch information
skx committed Jan 25, 2023
1 parent 1aca414 commit 455c733
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 25 deletions.
14 changes: 11 additions & 3 deletions builtins/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -1454,13 +1454,17 @@ func printFn(env *env.Environment, args []primitive.Primitive) primitive.Primiti
return primitive.ArityError()
}

ioHelper := env.GetIOConfig()

// one arg
if len(args) == 1 {
// expand
str := expandStr(args[0].ToString())

// show & return
fmt.Println(str)
// Write via our configuration object
ioHelper.STDOUT.Write([]byte(str))
ioHelper.STDOUT.Write([]byte("\n"))

return primitive.String(str)
}

Expand All @@ -1481,7 +1485,11 @@ func printFn(env *env.Environment, args []primitive.Primitive) primitive.Primiti
}

out := fmt.Sprintf(frmt, parm...)
fmt.Println(out)

// Write via our configuration object
ioHelper.STDOUT.Write([]byte(out))
ioHelper.STDOUT.Write([]byte("\n"))

return primitive.String(out)
}

Expand Down
37 changes: 34 additions & 3 deletions builtins/builtins_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package builtins

import (
"bytes"
"math"
"os"
"path/filepath"
Expand All @@ -9,6 +10,7 @@ import (
"testing"
"time"

"github.com/skx/yal/config"
"github.com/skx/yal/env"
"github.com/skx/yal/eval"
"github.com/skx/yal/primitive"
Expand All @@ -21,6 +23,10 @@ var ENV *env.Environment
// init ensures our environment pointer is up to date.
func init() {
ENV = env.New()

// Environment will have a config
ENV.SetIOConfig(config.DefaultIO())

}

func TestArch(t *testing.T) {
Expand Down Expand Up @@ -3046,6 +3052,31 @@ func TestPrint(t *testing.T) {
t.Fatalf("got string, but wrong one %v", e2)
}

// Preserve the current config
orig := ENV.GetIOConfig()

// Setup a new STDOUT handle, pointing to a buffer
cfg := config.New()
tmp := new(bytes.Buffer)
cfg.STDOUT = tmp
ENV.SetIOConfig(cfg)

// Print something
out = printFn(ENV, []primitive.Primitive{
primitive.String("Hello %s!"),
primitive.List{
primitive.String("world"),
primitive.Number(42),
},
})

// Restore our handles
ENV.SetIOConfig(orig)

// Our buffer should be good
if tmp.String() != "Hello (world 42)!\n" {
t.Fatalf("(print 'hello (list)!') failed, got '%s'", tmp.String())
}
}

// TestRandom tests (random)
Expand Down Expand Up @@ -3524,7 +3555,7 @@ func TestStringLt(t *testing.T) {
}
}

func TestTrig(t *testing.T ) {
func TestTrig(t *testing.T) {

funs := []primitive.GolangPrimitiveFn{
acosFn,
Expand All @@ -3538,9 +3569,9 @@ func TestTrig(t *testing.T ) {
tanhFn,
}

for _, fn := range(funs) {
for _, fn := range funs {

out := fn(nil, []primitive.Primitive{} )
out := fn(nil, []primitive.Primitive{})

// Will lead to an error
e, ok := out.(primitive.Error)
Expand Down
1 change: 0 additions & 1 deletion builtins/file_info_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ func getGID(info os.FileInfo) (int, error) {
return int(stat.Gid), nil
}


// getUID returns the owner of the file, from the extended information
// available after a stat - that is not portable to Windows though.
//
Expand Down
28 changes: 25 additions & 3 deletions env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
// or call-frames, you can create a nested environment via NewEnvironment.
package env

import (
"github.com/skx/yal/config"
)

// Environment holds our state
type Environment struct {

Expand All @@ -17,6 +21,10 @@ type Environment struct {

// values holds the actual values
values map[string]any

// ioconfig holds the interface to the outside world,
// which is used for I/O
ioconfig *config.Config
}

// Get retrieves a value from the environment.
Expand Down Expand Up @@ -59,16 +67,18 @@ func (env *Environment) Items() map[string]any {
// New creates a new environment, with no parent.
func New() *Environment {
return &Environment{
values: map[string]any{},
values: map[string]any{},
ioconfig: config.New(),
}
}

// NewEnvironment creates a new environment, which will use the specified
// parent environment for values in a higher level.
func NewEnvironment(parent *Environment) *Environment {
return &Environment{
parent: parent,
values: map[string]any{},
parent: parent,
values: map[string]any{},
ioconfig: parent.ioconfig,
}
}

Expand All @@ -87,3 +97,15 @@ func (env *Environment) SetOuter(key string, value any) {
env.parent.SetOuter(key, value)
}
}

// SetIOConfig updates the configuration object which is stored
// in our environment
func (env *Environment) SetIOConfig(cfg *config.Config) {
env.ioconfig = cfg
}

// GetIOConfig returns the configuration object which is stored in
// our environment.
func (env *Environment) GetIOConfig() *config.Config {
return env.ioconfig
}
14 changes: 0 additions & 14 deletions eval/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@
package eval

import (
"bufio"
"context"
"errors"
"flag"
"fmt"
"os"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -59,14 +57,6 @@ type Eval struct {
// The key is the name of the fake method, the value the name of
// the field to get/set
accessors map[string]string

// STDIN is an input-reader used for the (read) function, when
// called with no arguments.
STDIN *bufio.Reader

// STDOUT is the writer which we should use for "(print)", but we
// currently don't.
STDOUT *bufio.Writer
}

// New constructs a new lisp interpreter.
Expand All @@ -93,10 +83,6 @@ func New(src string) *Eval {
accessors: make(map[string]string),
}

// Setup default input/output streams
e.STDIN = bufio.NewReader(os.Stdin)
e.STDOUT = bufio.NewWriter(os.Stdout)

// Setup the default symbol-table (interned) entries.

// true
Expand Down
7 changes: 7 additions & 0 deletions eval/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/skx/yal/builtins"
"github.com/skx/yal/config"
"github.com/skx/yal/env"
"github.com/skx/yal/primitive"
"github.com/skx/yal/stdlib"
Expand All @@ -26,6 +27,9 @@ func TestAliased(t *testing.T) {
// With a new environment
env := env.New()

// Environment will have a config
env.SetIOConfig(config.DefaultIO())

// Populate the default primitives
builtins.PopulateEnvironment(env)

Expand Down Expand Up @@ -416,6 +420,9 @@ a
// With a new environment
env := env.New()

// Environment will have a config
env.SetIOConfig(config.DefaultIO())

// Populate the default primitives
builtins.PopulateEnvironment(env)

Expand Down
6 changes: 5 additions & 1 deletion eval/specials.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package eval

import (
"bufio"
"fmt"
"strings"

Expand Down Expand Up @@ -286,7 +287,10 @@ func (ev *Eval) evalSpecialForm(name string, args []primitive.Primitive, e *env.

// zero arguments: read from STDIN
if len(args) == 0 {
input, err := ev.STDIN.ReadString('\n')

ioHelper := e.GetIOConfig()
r := bufio.NewReader(ioHelper.STDIN)
input, err := r.ReadString('\n')
if err != nil {
return primitive.Error(
fmt.Sprintf("failed to read from STDIN %s", err)), true
Expand Down
4 changes: 4 additions & 0 deletions fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"time"

"github.com/skx/yal/builtins"
"github.com/skx/yal/config"
"github.com/skx/yal/env"
"github.com/skx/yal/eval"
"github.com/skx/yal/primitive"
Expand Down Expand Up @@ -188,6 +189,9 @@ func FuzzYAL(f *testing.F) {
// Create a new environment
environment := env.New()

// Environment will have a config
environment.SetIOConfig(config.DefaultIO())

// Populate the default primitives
builtins.PopulateEnvironment(environment)

Expand Down
4 changes: 4 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"time"

"github.com/skx/yal/builtins"
"github.com/skx/yal/config"
"github.com/skx/yal/env"
"github.com/skx/yal/eval"
"github.com/skx/yal/primitive"
Expand All @@ -43,6 +44,9 @@ func create() {
// Create a new environment
ENV = env.New()

// Setup the I/O
ENV.SetIOConfig(config.DefaultIO())

// Populate the default primitives
builtins.PopulateEnvironment(ENV)

Expand Down

0 comments on commit 455c733

Please sign in to comment.