Skip to content

Commit

Permalink
Allow string multiplication (#114)
Browse files Browse the repository at this point in the history
* Allow string multiplication

This pull-request closes #113, by allowing the multiplication
operator to be applied to strings:

The following program:

    10 PRINT "STEVE" * 3

Will output

    STEVESTEVESTEVE

The test-cases were updated to cope with the changes, as previously
"STRING * STRING" was invalid and expected to create an error.

* Install the linting tools we use the new way

Don't use "go get -u ..", instead use "go install .."

* Remove deprecated object-usage

As reported by the linter(s) we use.

* Bump version on go.mod

* Updated installation instructions.
  • Loading branch information
skx authored Oct 9, 2021
1 parent 267f677 commit 7a694a5
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 57 deletions.
16 changes: 12 additions & 4 deletions .github/run-tests.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
#!/bin/bash

# Install tools to test our code-quality.
go get -u golang.org/x/lint/golint
go get -u golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
go get -u honnef.co/go/tools/cmd/staticcheck
# Install the tools we use to test our code-quality.
#
# Here we setup the tools to install only if the "CI" environmental variable
# is not empty. This is because locally I have them installed.
#
# NOTE: Github Actions always set CI=true
#
if [ ! -z "${CI}" ] ; then
go install golang.org/x/lint/golint@latest
go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest
go install honnef.co/go/tools/cmd/staticcheck@latest
fi

# Run the static-check tool - we ignore errors in goserver/static.go
t=$(mktemp)
Expand Down
19 changes: 8 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,21 +229,18 @@ This seemed better than trying to return a string, unless the input looked like

## 30 PRINT "Installation"

### Build without Go Modules (Go before 1.11)
We don't pull in any external dependencies, except for the embedded examples,
so installation is simple.

Providing you have a working [go-installation](https://golang.org/) you should be able to install this software by running:

go get -u github.com/skx/gobasic

**NOTE** This will only install the command-line driver, rather than the HTTP-server, or the embedded example code.

### Build with Go Modules (Go 1.11 or higher)

git clone https://github.com/skx/gobasic ;# make sure to clone outside of GOPATH
git clone https://github.com/skx/gobasic
cd gobasic
go install

If you don't have a golang environment setup you should be able to download various binaries from the github release page:
You can also install directly via:

go install github.com/skx/gobasic@latest

If you don't have a golang environment setup you should be able to download a binary release from our release page:

* [Binary Releases](https://github.com/skx/gobasic/releases)

Expand Down
6 changes: 3 additions & 3 deletions embed/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func circleFunction(env builtin.Environment, args []object.Object) object.Object
if img == nil {
img = image.NewRGBA(image.Rect(0, 0, 600, 400))
black := color.RGBA{0, 0, 0, 255}
draw.Draw(img, img.Bounds(), &image.Uniform{black}, image.ZP, draw.Src)
draw.Draw(img, img.Bounds(), &image.Uniform{black}, image.Point{}, draw.Src)
}

// Create the colour
Expand Down Expand Up @@ -140,7 +140,7 @@ func plotFunction(env builtin.Environment, args []object.Object) object.Object {
if img == nil {
img = image.NewRGBA(image.Rect(0, 0, 600, 400))
black := color.RGBA{0, 0, 0, 255}
draw.Draw(img, img.Bounds(), &image.Uniform{black}, image.ZP, draw.Src)
draw.Draw(img, img.Bounds(), &image.Uniform{black}, image.Point{}, draw.Src)
}

// Draw the dot
Expand All @@ -158,7 +158,7 @@ func saveFunction(env builtin.Environment, args []object.Object) object.Object {
if img == nil {
img = image.NewRGBA(image.Rect(0, 0, 600, 400))
black := color.RGBA{0, 0, 0, 255}
draw.Draw(img, img.Bounds(), &image.Uniform{black}, image.ZP, draw.Src)
draw.Draw(img, img.Bounds(), &image.Uniform{black}, image.Point{}, draw.Src)
}

// Now write out the image.
Expand Down
106 changes: 75 additions & 31 deletions eval/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -606,52 +606,96 @@ func (e *Interpreter) term() object.Object {
if e.offset >= len(e.program) {
return object.Error("Hit end of program processing term()")
}

//
// We allow operations of the form:
// Have we handled this operation?
//
// NUMBER OP NUMBER
handled := false

//
// We can error on strings.
// We allow string "multiplication"
//
if f1.Type() != object.NUMBER ||
f2.Type() != object.NUMBER {
return object.Error("term() only handles integers")
}

// STRING * NUMBER
//
// Get the values.
// "STEVE " * 4 => "STEVE STEVE STEVE STEVE "
//
v1 := f1.(*object.NumberObject).Value
v2 := f2.(*object.NumberObject).Value
if f1.Type() == object.STRING &&
f2.Type() == object.NUMBER &&
tok.Type == token.ASTERISK {

// original value
val := f1.(*object.StringObject).Value
orig := val
// repeat
rep := f2.(*object.NumberObject).Value

for rep > 0 {

orig = orig + val
rep--
}

// Save the updated value
f1 = &object.StringObject{Value: orig}

// We've handled this
handled = true
}

//
// Handle the operator.
// We allow operations of the form:
//
if tok.Type == token.ASTERISK {
f1 = &object.NumberObject{Value: v1 * v2}
}
if tok.Type == token.POW {
f1 = &object.NumberObject{Value: math.Pow(v1, v2)}
}
if tok.Type == token.SLASH {
if v2 == 0 {
return object.Error("Division by zero")
// NUMBER OP NUMBER
//
if f1.Type() == object.NUMBER &&
f2.Type() == object.NUMBER {

//
// Get the values.
//
v1 := f1.(*object.NumberObject).Value
v2 := f2.(*object.NumberObject).Value

//
// Handle the operator.
//
if tok.Type == token.ASTERISK {
f1 = &object.NumberObject{Value: v1 * v2}
}
f1 = &object.NumberObject{Value: v1 / v2}
}
if tok.Type == token.MOD {
if tok.Type == token.POW {
f1 = &object.NumberObject{Value: math.Pow(v1, v2)}
}
if tok.Type == token.SLASH {
if v2 == 0 {
return object.Error("Division by zero")
}
f1 = &object.NumberObject{Value: v1 / v2}
}
if tok.Type == token.MOD {

d1 := int(v1)
d2 := int(v2)
d1 := int(v1)
d2 := int(v2)

if d2 == 0 {
return object.Error("MOD 0 is an error")
if d2 == 0 {
return object.Error("MOD 0 is an error")
}
f1 = &object.NumberObject{Value: float64(d1 % d2)}
}
f1 = &object.NumberObject{Value: float64(d1 % d2)}

if e.offset >= len(e.program) {
return object.Error("Hit end of program processing term()")
}

// we've handled the operation now
handled = true
}

if e.offset >= len(e.program) {
return object.Error("Hit end of program processing term()")
//
// If we didn't handle the operation then the types
// were invalid, so report that.
//
if !handled {
return object.Error("term() only handles string-multiplication and integer-operations")
}

// repeat?
Expand Down
8 changes: 4 additions & 4 deletions eval/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1157,7 +1157,7 @@ func TestMaths(t *testing.T) {
// TestMismatchedTypes tests that expr() errors on mismatched types.
func TestMismatchedTypes(t *testing.T) {
input := `10 LET a=3
20 LET b="steve"
20 LET b = "steve"
30 LET c = a + b
`
tokener := tokenizer.New(input)
Expand All @@ -1179,7 +1179,7 @@ func TestMismatchedTypes(t *testing.T) {
// TestMismatchedTypesTerm tests that term() errors on mismatched types.
func TestMismatchedTypesTerm(t *testing.T) {
input := `10 LET a="steve"
20 LET b = ( a * 2 ) + ( a * 33 )
20 LET b = ( a + 2 ) + ( a + 33 )
`
tokener := tokenizer.New(input)
e, err := New(tokener)
Expand All @@ -1192,8 +1192,8 @@ func TestMismatchedTypesTerm(t *testing.T) {
if err == nil {
t.Errorf("Expected to see an error, but didn't.")
}
if !strings.Contains(err.Error(), "handles integers") {
t.Errorf("Our error-message wasn't what we expected")
if !strings.Contains(err.Error(), "type mismatch") {
t.Errorf("Our error-message wasn't what we expected: %s", err.Error())
}
}

Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
module github.com/skx/gobasic

go 1.17
8 changes: 4 additions & 4 deletions goserver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func plotFunction(env builtin.Environment, args []object.Object) object.Object {
if img == nil {
img = image.NewRGBA(image.Rect(0, 0, 600, 400))
c := color.RGBA{255, 255, 255, 255}
draw.Draw(img, img.Bounds(), &image.Uniform{c}, image.ZP, draw.Src)
draw.Draw(img, img.Bounds(), &image.Uniform{c}, image.Point{}, draw.Src)
}

// Draw the dot
Expand All @@ -88,7 +88,7 @@ func saveFunction(env builtin.Environment, args []object.Object) object.Object {
if img == nil {
img = image.NewRGBA(image.Rect(0, 0, 600, 400))
c := color.RGBA{255, 255, 255, 255}
draw.Draw(img, img.Bounds(), &image.Uniform{c}, image.ZP, draw.Src)
draw.Draw(img, img.Bounds(), &image.Uniform{c}, image.Point{}, draw.Src)
}

// Generate a temporary filename
Expand Down Expand Up @@ -178,7 +178,7 @@ func circleFunction(env builtin.Environment, args []object.Object) object.Object
if img == nil {
img = image.NewRGBA(image.Rect(0, 0, 600, 400))
c := color.RGBA{255, 255, 255, 255}
draw.Draw(img, img.Bounds(), &image.Uniform{c}, image.ZP, draw.Src)
draw.Draw(img, img.Bounds(), &image.Uniform{c}, image.Point{}, draw.Src)
}

// Now circle-magic happens.
Expand Down Expand Up @@ -249,7 +249,7 @@ func lineFunction(env builtin.Environment, args []object.Object) object.Object {
if img == nil {
img = image.NewRGBA(image.Rect(0, 0, 600, 400))
c := color.RGBA{255, 255, 255, 255}
draw.Draw(img, img.Bounds(), &image.Uniform{c}, image.ZP, draw.Src)
draw.Draw(img, img.Bounds(), &image.Uniform{c}, image.Point{}, draw.Src)
}

var dx, dy, e, slope int
Expand Down

0 comments on commit 7a694a5

Please sign in to comment.