starting over again. I'm just gonna keep doing this about once a year for the rest of my life
This commit is contained in:
parent
2d779f8182
commit
4f9baf7514
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
.goat
|
|
||||||
*.test
|
|
10
Makefile
10
Makefile
@ -1,10 +0,0 @@
|
|||||||
all: gen
|
|
||||||
|
|
||||||
test: gen
|
|
||||||
go test ./...
|
|
||||||
|
|
||||||
fmt: gen
|
|
||||||
go fmt ./...
|
|
||||||
|
|
||||||
gen:
|
|
||||||
(cd core && make gen)
|
|
41
README.md
41
README.md
@ -1,41 +0,0 @@
|
|||||||
# Ginger
|
|
||||||
|
|
||||||
A lisp-like language built on the go programming language. The ideas are still a
|
|
||||||
work-in-progress, and this repo is where I'm jotting down my notes:
|
|
||||||
|
|
||||||
# Language manifesto
|
|
||||||
|
|
||||||
* Anything written in go should be writeable in ginger in as many lines or
|
|
||||||
fewer.
|
|
||||||
|
|
||||||
* When deciding whether to be more go-like or more like an existing lisp
|
|
||||||
language, err on being go-like.
|
|
||||||
|
|
||||||
* The fewer built-in functions, the better. The standard library should be
|
|
||||||
easily discoverable and always importable so helper functions can be made
|
|
||||||
available.
|
|
||||||
|
|
||||||
* When choosing between adding a syntax rule/datatype and not adding a feature,
|
|
||||||
err on not adding the feature.
|
|
||||||
|
|
||||||
* It is not a goal to make ginger code be usable from go code.
|
|
||||||
|
|
||||||
* Naming should use words instead of symbols, except when those symbols are
|
|
||||||
existing go operators.
|
|
||||||
|
|
||||||
* Overloading functions should be used as little as possible. Possibly not at
|
|
||||||
all
|
|
||||||
|
|
||||||
# Documentation
|
|
||||||
|
|
||||||
See the [docs](/docs) folder for more details. Keep in mind that most of ginger
|
|
||||||
is still experimental and definitely not ready for the spotlight.
|
|
||||||
|
|
||||||
Here is a list of the docs more or less in the order they should be read for a
|
|
||||||
complete overview of the language:
|
|
||||||
|
|
||||||
* [syntax](/docs/syntax.md)
|
|
||||||
* [functions](/docs/functions.md)
|
|
||||||
* [compilation](/docs/compilation.md)
|
|
||||||
* [packages](/docs/packages.md)
|
|
||||||
* [go-interop](/docs/go-interop.md)
|
|
@ -1,2 +0,0 @@
|
|||||||
gen:
|
|
||||||
go run mathgen/mathgen.go > math.go
|
|
81
core/cast.go
81
core/cast.go
@ -1,81 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Int(e types.Elem) int {
|
|
||||||
return e.(types.GoType).V.(int)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Int8(e types.Elem) int8 {
|
|
||||||
return e.(types.GoType).V.(int8)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Int16(e types.Elem) int16 {
|
|
||||||
return e.(types.GoType).V.(int16)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Int32(e types.Elem) int32 {
|
|
||||||
return e.(types.GoType).V.(int32)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Int64(e types.Elem) int64 {
|
|
||||||
return e.(types.GoType).V.(int64)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Uint(e types.Elem) uint {
|
|
||||||
return e.(types.GoType).V.(uint)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Uint8(e types.Elem) uint8 {
|
|
||||||
return e.(types.GoType).V.(uint8)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Uint16(e types.Elem) uint16 {
|
|
||||||
return e.(types.GoType).V.(uint16)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Uint32(e types.Elem) uint32 {
|
|
||||||
return e.(types.GoType).V.(uint32)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Uint64(e types.Elem) uint64 {
|
|
||||||
return e.(types.GoType).V.(uint64)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Float32(e types.Elem) float32 {
|
|
||||||
return e.(types.GoType).V.(float32)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Float64(e types.Elem) float64 {
|
|
||||||
return e.(types.GoType).V.(float64)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Complex64(e types.Elem) complex64 {
|
|
||||||
return e.(types.GoType).V.(complex64)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Complex128(e types.Elem) complex128 {
|
|
||||||
return e.(types.GoType).V.(complex128)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Bool(e types.Elem) bool {
|
|
||||||
return e.(types.GoType).V.(bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Byte(e types.Elem) byte {
|
|
||||||
return e.(types.GoType).V.(byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Rune(e types.Elem) rune {
|
|
||||||
return e.(types.GoType).V.(rune)
|
|
||||||
}
|
|
||||||
|
|
||||||
func String(e types.Elem) string {
|
|
||||||
return e.(types.GoType).V.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Error(e types.Elem) error {
|
|
||||||
return e.(types.GoType).V.(error)
|
|
||||||
}
|
|
604
core/math.go
604
core/math.go
@ -1,604 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/seq"
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mathReduceFn func(types.Elem, types.Elem) types.Elem
|
|
||||||
|
|
||||||
func mathReduce(fn mathReduceFn, zero types.Elem, s seq.Seq) types.Elem {
|
|
||||||
reduceFn := func(acc, el types.Elem) (types.Elem, bool) {
|
|
||||||
return fn(acc, el), false
|
|
||||||
}
|
|
||||||
return seq.Reduce(reduceFn, zero, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func plusInt(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int(a) + Int(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func plusInt8(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int8(a) + Int8(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func plusInt16(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int16(a) + Int16(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func plusInt32(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int32(a) + Int32(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func plusInt64(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int64(a) + Int64(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func plusUint(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint(a) + Uint(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func plusUint8(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint8(a) + Uint8(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func plusUint16(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint16(a) + Uint16(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func plusUint32(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint32(a) + Uint32(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func plusUint64(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint64(a) + Uint64(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func plusFloat32(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Float32(a) + Float32(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func plusFloat64(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Float64(a) + Float64(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func plusComplex64(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Complex64(a) + Complex64(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func plusComplex128(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Complex128(a) + Complex128(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func plusString(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{String(a) + String(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Plus(s seq.Seq) types.Elem {
|
|
||||||
var first, zero types.Elem
|
|
||||||
|
|
||||||
if seq.Empty(s) {
|
|
||||||
return types.GoType{0}
|
|
||||||
}
|
|
||||||
first, _, _ = s.FirstRest()
|
|
||||||
|
|
||||||
var fn mathReduceFn
|
|
||||||
switch first.(types.GoType).V.(type) {
|
|
||||||
|
|
||||||
case int:
|
|
||||||
fn = plusInt
|
|
||||||
zero = types.GoType{int(0)}
|
|
||||||
|
|
||||||
case int8:
|
|
||||||
fn = plusInt8
|
|
||||||
zero = types.GoType{int8(0)}
|
|
||||||
|
|
||||||
case int16:
|
|
||||||
fn = plusInt16
|
|
||||||
zero = types.GoType{int16(0)}
|
|
||||||
|
|
||||||
case int32:
|
|
||||||
fn = plusInt32
|
|
||||||
zero = types.GoType{int32(0)}
|
|
||||||
|
|
||||||
case int64:
|
|
||||||
fn = plusInt64
|
|
||||||
zero = types.GoType{int64(0)}
|
|
||||||
|
|
||||||
case uint:
|
|
||||||
fn = plusUint
|
|
||||||
zero = types.GoType{uint(0)}
|
|
||||||
|
|
||||||
case uint8:
|
|
||||||
fn = plusUint8
|
|
||||||
zero = types.GoType{uint8(0)}
|
|
||||||
|
|
||||||
case uint16:
|
|
||||||
fn = plusUint16
|
|
||||||
zero = types.GoType{uint16(0)}
|
|
||||||
|
|
||||||
case uint32:
|
|
||||||
fn = plusUint32
|
|
||||||
zero = types.GoType{uint32(0)}
|
|
||||||
|
|
||||||
case uint64:
|
|
||||||
fn = plusUint64
|
|
||||||
zero = types.GoType{uint64(0)}
|
|
||||||
|
|
||||||
case float32:
|
|
||||||
fn = plusFloat32
|
|
||||||
zero = types.GoType{float32(0)}
|
|
||||||
|
|
||||||
case float64:
|
|
||||||
fn = plusFloat64
|
|
||||||
zero = types.GoType{float64(0)}
|
|
||||||
|
|
||||||
case complex64:
|
|
||||||
fn = plusComplex64
|
|
||||||
zero = types.GoType{complex64(0)}
|
|
||||||
|
|
||||||
case complex128:
|
|
||||||
fn = plusComplex128
|
|
||||||
zero = types.GoType{complex128(0)}
|
|
||||||
|
|
||||||
case string:
|
|
||||||
fn = plusString
|
|
||||||
zero = types.GoType{string("")}
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("$#v cannot have Plus called on it", first))
|
|
||||||
}
|
|
||||||
|
|
||||||
return mathReduce(fn, zero, s)
|
|
||||||
}
|
|
||||||
func minusInt(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int(a) - Int(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func minusInt8(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int8(a) - Int8(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func minusInt16(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int16(a) - Int16(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func minusInt32(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int32(a) - Int32(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func minusInt64(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int64(a) - Int64(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func minusUint(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint(a) - Uint(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func minusUint8(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint8(a) - Uint8(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func minusUint16(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint16(a) - Uint16(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func minusUint32(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint32(a) - Uint32(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func minusUint64(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint64(a) - Uint64(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func minusFloat32(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Float32(a) - Float32(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func minusFloat64(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Float64(a) - Float64(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func minusComplex64(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Complex64(a) - Complex64(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func minusComplex128(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Complex128(a) - Complex128(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Minus(s seq.Seq) types.Elem {
|
|
||||||
var first, zero types.Elem
|
|
||||||
|
|
||||||
if seq.Empty(s) {
|
|
||||||
panic("Minus cannot be called with no arguments")
|
|
||||||
}
|
|
||||||
zero, s, _ = s.FirstRest()
|
|
||||||
first = zero
|
|
||||||
|
|
||||||
var fn mathReduceFn
|
|
||||||
switch first.(types.GoType).V.(type) {
|
|
||||||
|
|
||||||
case int:
|
|
||||||
fn = minusInt
|
|
||||||
|
|
||||||
case int8:
|
|
||||||
fn = minusInt8
|
|
||||||
|
|
||||||
case int16:
|
|
||||||
fn = minusInt16
|
|
||||||
|
|
||||||
case int32:
|
|
||||||
fn = minusInt32
|
|
||||||
|
|
||||||
case int64:
|
|
||||||
fn = minusInt64
|
|
||||||
|
|
||||||
case uint:
|
|
||||||
fn = minusUint
|
|
||||||
|
|
||||||
case uint8:
|
|
||||||
fn = minusUint8
|
|
||||||
|
|
||||||
case uint16:
|
|
||||||
fn = minusUint16
|
|
||||||
|
|
||||||
case uint32:
|
|
||||||
fn = minusUint32
|
|
||||||
|
|
||||||
case uint64:
|
|
||||||
fn = minusUint64
|
|
||||||
|
|
||||||
case float32:
|
|
||||||
fn = minusFloat32
|
|
||||||
|
|
||||||
case float64:
|
|
||||||
fn = minusFloat64
|
|
||||||
|
|
||||||
case complex64:
|
|
||||||
fn = minusComplex64
|
|
||||||
|
|
||||||
case complex128:
|
|
||||||
fn = minusComplex128
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("$#v cannot have Minus called on it", first))
|
|
||||||
}
|
|
||||||
|
|
||||||
return mathReduce(fn, zero, s)
|
|
||||||
}
|
|
||||||
func multInt(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int(a) * Int(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func multInt8(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int8(a) * Int8(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func multInt16(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int16(a) * Int16(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func multInt32(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int32(a) * Int32(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func multInt64(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int64(a) * Int64(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func multUint(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint(a) * Uint(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func multUint8(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint8(a) * Uint8(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func multUint16(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint16(a) * Uint16(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func multUint32(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint32(a) * Uint32(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func multUint64(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint64(a) * Uint64(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func multFloat32(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Float32(a) * Float32(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func multFloat64(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Float64(a) * Float64(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func multComplex64(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Complex64(a) * Complex64(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func multComplex128(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Complex128(a) * Complex128(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Mult(s seq.Seq) types.Elem {
|
|
||||||
var first, zero types.Elem
|
|
||||||
|
|
||||||
if seq.Empty(s) {
|
|
||||||
return types.GoType{1}
|
|
||||||
}
|
|
||||||
first, _, _ = s.FirstRest()
|
|
||||||
|
|
||||||
var fn mathReduceFn
|
|
||||||
switch first.(types.GoType).V.(type) {
|
|
||||||
|
|
||||||
case int:
|
|
||||||
fn = multInt
|
|
||||||
zero = types.GoType{int(1)}
|
|
||||||
|
|
||||||
case int8:
|
|
||||||
fn = multInt8
|
|
||||||
zero = types.GoType{int8(1)}
|
|
||||||
|
|
||||||
case int16:
|
|
||||||
fn = multInt16
|
|
||||||
zero = types.GoType{int16(1)}
|
|
||||||
|
|
||||||
case int32:
|
|
||||||
fn = multInt32
|
|
||||||
zero = types.GoType{int32(1)}
|
|
||||||
|
|
||||||
case int64:
|
|
||||||
fn = multInt64
|
|
||||||
zero = types.GoType{int64(1)}
|
|
||||||
|
|
||||||
case uint:
|
|
||||||
fn = multUint
|
|
||||||
zero = types.GoType{uint(1)}
|
|
||||||
|
|
||||||
case uint8:
|
|
||||||
fn = multUint8
|
|
||||||
zero = types.GoType{uint8(1)}
|
|
||||||
|
|
||||||
case uint16:
|
|
||||||
fn = multUint16
|
|
||||||
zero = types.GoType{uint16(1)}
|
|
||||||
|
|
||||||
case uint32:
|
|
||||||
fn = multUint32
|
|
||||||
zero = types.GoType{uint32(1)}
|
|
||||||
|
|
||||||
case uint64:
|
|
||||||
fn = multUint64
|
|
||||||
zero = types.GoType{uint64(1)}
|
|
||||||
|
|
||||||
case float32:
|
|
||||||
fn = multFloat32
|
|
||||||
zero = types.GoType{float32(1)}
|
|
||||||
|
|
||||||
case float64:
|
|
||||||
fn = multFloat64
|
|
||||||
zero = types.GoType{float64(1)}
|
|
||||||
|
|
||||||
case complex64:
|
|
||||||
fn = multComplex64
|
|
||||||
zero = types.GoType{complex64(1)}
|
|
||||||
|
|
||||||
case complex128:
|
|
||||||
fn = multComplex128
|
|
||||||
zero = types.GoType{complex128(1)}
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("$#v cannot have Mult called on it", first))
|
|
||||||
}
|
|
||||||
|
|
||||||
return mathReduce(fn, zero, s)
|
|
||||||
}
|
|
||||||
func divInt(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int(a) / Int(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func divInt8(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int8(a) / Int8(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func divInt16(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int16(a) / Int16(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func divInt32(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int32(a) / Int32(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func divInt64(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int64(a) / Int64(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func divUint(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint(a) / Uint(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func divUint8(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint8(a) / Uint8(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func divUint16(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint16(a) / Uint16(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func divUint32(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint32(a) / Uint32(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func divUint64(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint64(a) / Uint64(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func divFloat32(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Float32(a) / Float32(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func divFloat64(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Float64(a) / Float64(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func divComplex64(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Complex64(a) / Complex64(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func divComplex128(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Complex128(a) / Complex128(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Div(s seq.Seq) types.Elem {
|
|
||||||
var first, zero types.Elem
|
|
||||||
|
|
||||||
if seq.Empty(s) {
|
|
||||||
panic("Div cannot be called with no arguments")
|
|
||||||
}
|
|
||||||
zero, s, _ = s.FirstRest()
|
|
||||||
first = zero
|
|
||||||
|
|
||||||
var fn mathReduceFn
|
|
||||||
switch first.(types.GoType).V.(type) {
|
|
||||||
|
|
||||||
case int:
|
|
||||||
fn = divInt
|
|
||||||
|
|
||||||
case int8:
|
|
||||||
fn = divInt8
|
|
||||||
|
|
||||||
case int16:
|
|
||||||
fn = divInt16
|
|
||||||
|
|
||||||
case int32:
|
|
||||||
fn = divInt32
|
|
||||||
|
|
||||||
case int64:
|
|
||||||
fn = divInt64
|
|
||||||
|
|
||||||
case uint:
|
|
||||||
fn = divUint
|
|
||||||
|
|
||||||
case uint8:
|
|
||||||
fn = divUint8
|
|
||||||
|
|
||||||
case uint16:
|
|
||||||
fn = divUint16
|
|
||||||
|
|
||||||
case uint32:
|
|
||||||
fn = divUint32
|
|
||||||
|
|
||||||
case uint64:
|
|
||||||
fn = divUint64
|
|
||||||
|
|
||||||
case float32:
|
|
||||||
fn = divFloat32
|
|
||||||
|
|
||||||
case float64:
|
|
||||||
fn = divFloat64
|
|
||||||
|
|
||||||
case complex64:
|
|
||||||
fn = divComplex64
|
|
||||||
|
|
||||||
case complex128:
|
|
||||||
fn = divComplex128
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("$#v cannot have Div called on it", first))
|
|
||||||
}
|
|
||||||
|
|
||||||
return mathReduce(fn, zero, s)
|
|
||||||
}
|
|
||||||
func modInt(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int(a) % Int(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func modInt8(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int8(a) % Int8(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func modInt16(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int16(a) % Int16(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func modInt32(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int32(a) % Int32(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func modInt64(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Int64(a) % Int64(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func modUint(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint(a) % Uint(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func modUint8(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint8(a) % Uint8(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func modUint16(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint16(a) % Uint16(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func modUint32(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint32(a) % Uint32(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func modUint64(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{Uint64(a) % Uint64(b)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Mod(s seq.Seq) types.Elem {
|
|
||||||
var first, zero types.Elem
|
|
||||||
|
|
||||||
if seq.Empty(s) {
|
|
||||||
panic("Mod cannot be called with no arguments")
|
|
||||||
}
|
|
||||||
zero, s, _ = s.FirstRest()
|
|
||||||
first = zero
|
|
||||||
|
|
||||||
var fn mathReduceFn
|
|
||||||
switch first.(types.GoType).V.(type) {
|
|
||||||
|
|
||||||
case int:
|
|
||||||
fn = modInt
|
|
||||||
|
|
||||||
case int8:
|
|
||||||
fn = modInt8
|
|
||||||
|
|
||||||
case int16:
|
|
||||||
fn = modInt16
|
|
||||||
|
|
||||||
case int32:
|
|
||||||
fn = modInt32
|
|
||||||
|
|
||||||
case int64:
|
|
||||||
fn = modInt64
|
|
||||||
|
|
||||||
case uint:
|
|
||||||
fn = modUint
|
|
||||||
|
|
||||||
case uint8:
|
|
||||||
fn = modUint8
|
|
||||||
|
|
||||||
case uint16:
|
|
||||||
fn = modUint16
|
|
||||||
|
|
||||||
case uint32:
|
|
||||||
fn = modUint32
|
|
||||||
|
|
||||||
case uint64:
|
|
||||||
fn = modUint64
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("$#v cannot have Mod called on it", first))
|
|
||||||
}
|
|
||||||
|
|
||||||
return mathReduce(fn, zero, s)
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
# mathgen, or why I'm so so sorry
|
|
||||||
|
|
||||||
Go's type system is a double-edged sword, and the inner edge will definitely get
|
|
||||||
you when you go to do generic math. I opted against using the `reflect` library,
|
|
||||||
and instead went for codegen. So while the result is barely readable, it will at
|
|
||||||
least be somewhat faster, theoretically.
|
|
||||||
|
|
||||||
This module generates the `math.go` file which resides in the core library. The
|
|
||||||
`mathgen.tpl` file is where most of the magic happens. It is virtually
|
|
||||||
unreadable. The biggest reason for this is that I want it to be able to pass
|
|
||||||
through `go fmt` unphased, so that it doesn't end up getting changed back and
|
|
||||||
forth throughout the code's history.
|
|
||||||
|
|
||||||
## Running
|
|
||||||
|
|
||||||
You can run code gen by running `make gen` in the `core/` directory. The
|
|
||||||
`Makefile` in the root of the project should also reference this, so running
|
|
||||||
that in the root should also result in the file getting regenerated.
|
|
@ -1,84 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"text/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Represents a type (like int or float32) which can have a math operation
|
|
||||||
// called upon it (like + or -)
|
|
||||||
type MathOpType struct {
|
|
||||||
CastFn string
|
|
||||||
Type string
|
|
||||||
}
|
|
||||||
|
|
||||||
// The standard types for which math operations work upon. These don't include
|
|
||||||
// byte and rune because those are aliases of int8 and int32, respectively
|
|
||||||
var MathOpTypes = []MathOpType{
|
|
||||||
{"Int", "int"},
|
|
||||||
{"Int8", "int8"},
|
|
||||||
{"Int16", "int16"},
|
|
||||||
{"Int32", "int32"},
|
|
||||||
{"Int64", "int64"},
|
|
||||||
{"Uint", "uint"},
|
|
||||||
{"Uint8", "uint8"},
|
|
||||||
{"Uint16", "uint16"},
|
|
||||||
{"Uint32", "uint32"},
|
|
||||||
{"Uint64", "uint64"},
|
|
||||||
{"Float32", "float32"},
|
|
||||||
{"Float64", "float64"},
|
|
||||||
{"Complex64", "complex64"},
|
|
||||||
{"Complex128", "complex128"},
|
|
||||||
}
|
|
||||||
|
|
||||||
var MathOpsIntOnly = []MathOpType{
|
|
||||||
{"Int", "int"},
|
|
||||||
{"Int8", "int8"},
|
|
||||||
{"Int16", "int16"},
|
|
||||||
{"Int32", "int32"},
|
|
||||||
{"Int64", "int64"},
|
|
||||||
{"Uint", "uint"},
|
|
||||||
{"Uint8", "uint8"},
|
|
||||||
{"Uint16", "uint16"},
|
|
||||||
{"Uint32", "uint32"},
|
|
||||||
{"Uint64", "uint64"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Represents a single math operation which can be performed (like + or -)
|
|
||||||
type MathOp struct {
|
|
||||||
Public string
|
|
||||||
Private string
|
|
||||||
Op string
|
|
||||||
|
|
||||||
// Will be the first item in the reduce, and allows for the function being
|
|
||||||
// called with an empty seq. If empty string than the first item in the
|
|
||||||
// given sequence is used and an empty sequence is not allowed
|
|
||||||
Unit string
|
|
||||||
|
|
||||||
// This is going to be the same for all ops, it's just convenient to have
|
|
||||||
// here
|
|
||||||
OpTypes []MathOpType
|
|
||||||
|
|
||||||
// This only applies for plus, which allows for adding two strings together
|
|
||||||
IncludeString bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var MathOps = []MathOp{
|
|
||||||
{"Plus", "plus", "+", "0", MathOpTypes, true},
|
|
||||||
{"Minus", "minus", "-", "", MathOpTypes, false},
|
|
||||||
|
|
||||||
{"Mult", "mult", "*", "1", MathOpTypes, false},
|
|
||||||
{"Div", "div", "/", "", MathOpTypes, false},
|
|
||||||
|
|
||||||
{"Mod", "mod", "%", "", MathOpsIntOnly, false},
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
tpl, err := template.ParseFiles("mathgen/mathgen.tpl")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if err := tpl.Execute(os.Stdout, MathOps); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/seq"
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mathReduceFn func(types.Elem, types.Elem) types.Elem
|
|
||||||
|
|
||||||
func mathReduce(fn mathReduceFn, zero types.Elem, s seq.Seq) types.Elem {
|
|
||||||
reduceFn := func(acc, el types.Elem) (types.Elem, bool) {
|
|
||||||
return fn(acc, el), false
|
|
||||||
}
|
|
||||||
return seq.Reduce(reduceFn, zero, s)
|
|
||||||
}
|
|
||||||
{{range .}}{{$mathOp := .}}{{range .OpTypes}}
|
|
||||||
func {{$mathOp.Private}}{{.CastFn}}(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{{`{`}}{{.CastFn}}(a) {{$mathOp.Op}} {{.CastFn}}(b)}
|
|
||||||
}
|
|
||||||
{{end}}{{if $mathOp.IncludeString}}
|
|
||||||
func {{$mathOp.Private}}String(a, b types.Elem) types.Elem {
|
|
||||||
return types.GoType{String(a) {{$mathOp.Op}} String(b)}
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
func {{$mathOp.Public}}(s seq.Seq) types.Elem {
|
|
||||||
var first, zero types.Elem
|
|
||||||
{{if (eq $mathOp.Unit "")}}
|
|
||||||
if seq.Empty(s) {
|
|
||||||
panic("{{$mathOp.Public}} cannot be called with no arguments")
|
|
||||||
}
|
|
||||||
zero, s, _ = s.FirstRest()
|
|
||||||
first = zero
|
|
||||||
{{else}}
|
|
||||||
if seq.Empty(s) {
|
|
||||||
return types.GoType{{`{`}}{{$mathOp.Unit}}}
|
|
||||||
}
|
|
||||||
first, _, _ = s.FirstRest()
|
|
||||||
{{end}}
|
|
||||||
var fn mathReduceFn
|
|
||||||
switch first.(types.GoType).V.(type) {
|
|
||||||
{{range .OpTypes}}
|
|
||||||
case {{.Type}}:
|
|
||||||
fn = {{$mathOp.Private}}{{.CastFn}}{{if (ne $mathOp.Unit "")}}
|
|
||||||
zero = types.GoType{{`{`}}{{.Type}}({{$mathOp.Unit}})}{{end}}
|
|
||||||
{{end}}{{if $mathOp.IncludeString}}
|
|
||||||
case string:
|
|
||||||
fn = {{$mathOp.Private}}String
|
|
||||||
zero = types.GoType{string("")}
|
|
||||||
{{end}}
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("$#v cannot have {{$mathOp.Public}} called on it", first))
|
|
||||||
}
|
|
||||||
|
|
||||||
return mathReduce(fn, zero, s)
|
|
||||||
}{{end}}
|
|
@ -1,113 +0,0 @@
|
|||||||
# Hello world
|
|
||||||
|
|
||||||
The following code in a file called `hello-world.gg` can be compiled to a static
|
|
||||||
binary which will output "Hello World!" and exit:
|
|
||||||
|
|
||||||
```
|
|
||||||
(. package "github.com/mediocregopher/ginger-hello-world"
|
|
||||||
(. defn main []
|
|
||||||
(: fmt.Println "Hello World!"))
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
While in the same directory as `hello-world.gg`, this can be compiled and run
|
|
||||||
with:
|
|
||||||
|
|
||||||
```
|
|
||||||
ginger run
|
|
||||||
```
|
|
||||||
|
|
||||||
and built into a static binary with:
|
|
||||||
|
|
||||||
```
|
|
||||||
ginger build
|
|
||||||
```
|
|
||||||
|
|
||||||
## package
|
|
||||||
|
|
||||||
The package string defines where in the namespace tree this file belongs. Code
|
|
||||||
in this file can directly reference variables, private or public, from other
|
|
||||||
files in the same package.
|
|
||||||
|
|
||||||
The physical layout of the files is not consequential, ginger only cares what
|
|
||||||
package they say they belong to, it's not required that the directory tree
|
|
||||||
matches the namespace tree.
|
|
||||||
|
|
||||||
## main
|
|
||||||
|
|
||||||
Every executable which ginger compiles needs one `main` function to use as the
|
|
||||||
entrypoint. Ginger looks for a `main` function in all the `.gg` files in the
|
|
||||||
current working directory in order to compile. If it finds one it uses that, if
|
|
||||||
it finds zero or more than one it errors and tells the user they need to specify
|
|
||||||
either a package or file name.
|
|
||||||
|
|
||||||
## Compilation
|
|
||||||
|
|
||||||
Ginger is first compiled into go code, and the subsequently uses the go tool to
|
|
||||||
compile that code into a static binary. Because of this ginger is able to use
|
|
||||||
any code from the go standard library, as well as any third-party go libraries
|
|
||||||
which may want to be used.
|
|
||||||
|
|
||||||
Ginger takes advantage of the `GOPATH` when compiling in order to find packages.
|
|
||||||
Upon starting up ginger will set the `GOPATH` as follows for the duration of the
|
|
||||||
run:
|
|
||||||
|
|
||||||
```
|
|
||||||
GOPATH=./target:$GOPATH
|
|
||||||
```
|
|
||||||
|
|
||||||
The following steps are then taken for compilation:
|
|
||||||
|
|
||||||
* Create a folder in the cwd called `target`, if it isn't already there. Also
|
|
||||||
create a `src` subfolder inside of that.
|
|
||||||
|
|
||||||
* Scan the cwd and any subdirectories (including `target`) for `.gg` files.
|
|
||||||
Translate them into `.go` files and place them according to their package name
|
|
||||||
in the `target/src` directory. So if a file has a package
|
|
||||||
`github.com/mediocregopher/foobar` it would be compiled and placed as
|
|
||||||
`target/src/github.com/mediocregopher/foobar/file.go`. The `main` function is
|
|
||||||
found and the package for it is determined in this step as well.
|
|
||||||
|
|
||||||
* As the cwd `.gg` files are scanned a set of packages which are required is
|
|
||||||
built. If any are not present in `target` at the end of the last step and are
|
|
||||||
not present as go projects in the `GOPATH` then they are searched for as
|
|
||||||
ginger projects in the `GOPATH`. Packages found will be translated into the
|
|
||||||
`target` directory (very important, this prevents the global GOPATH from
|
|
||||||
getting cluttered with tranlated `.go` files which aren't actually part of the
|
|
||||||
project). This step is repeated until all dependencies are met, or until
|
|
||||||
they're not and an error is thrown.
|
|
||||||
|
|
||||||
* At this point all necessary `.go` files to build the project are present in
|
|
||||||
the `GOPATH` (global or `target`). `go build` is called on the `main` package
|
|
||||||
and output to the `target` directory.
|
|
||||||
|
|
||||||
### Properties
|
|
||||||
|
|
||||||
Given those compilation steps ginger has the following properties:
|
|
||||||
|
|
||||||
* Dependencies for a ginger project, either go or ginger, can be installed
|
|
||||||
globally or to the `target` folder (sandboxed) by a dependency management tool
|
|
||||||
(probably built into ginger).
|
|
||||||
|
|
||||||
* It is easy to find exactly what your code is being translated to since it will
|
|
||||||
always be in the `target` directory.
|
|
||||||
|
|
||||||
* Translated code will not clutter up the global GOPATH.
|
|
||||||
|
|
||||||
* Only the `target` directory needs to be added to a `.gitignore` file.
|
|
||||||
|
|
||||||
* Some amount of ginger-to-ginger monkey patching may be possible. Not sure if
|
|
||||||
this is a good or bad thing.
|
|
||||||
|
|
||||||
* Compilation may take a while. There is some amount of hunting for `.gg` files
|
|
||||||
required, and all found ones *must* be compiled even if they're not going to
|
|
||||||
be used, since package names can be arbitrarily stated. There are some ways to
|
|
||||||
help this:
|
|
||||||
|
|
||||||
* Might be worth taking a shortcut like grepping through all files in the
|
|
||||||
`GOPATH` for the package string only compiling the ones which pass the
|
|
||||||
grep.
|
|
||||||
|
|
||||||
* Put placeholder `.go` files in the `target` directory to indicate that the
|
|
||||||
package isn't needed for subsequent installs. Not the *best* idea, since
|
|
||||||
changes to the dependency list in the project may not correctly process.
|
|
@ -1,86 +0,0 @@
|
|||||||
# Functions
|
|
||||||
|
|
||||||
Functions are first-class citizens in ginger. The simplest anonymous function
|
|
||||||
can be created like so:
|
|
||||||
|
|
||||||
```
|
|
||||||
(. fn [x]
|
|
||||||
(: + x 1))
|
|
||||||
```
|
|
||||||
|
|
||||||
This function takes in a single number and returns that number plus one. It
|
|
||||||
could be assigned to a variable on the package using `def`, or by using the
|
|
||||||
shortcut `defn`. The following two statements are equivalent:
|
|
||||||
|
|
||||||
```
|
|
||||||
(. def incr
|
|
||||||
(. fn [x]
|
|
||||||
(: + x 1)))
|
|
||||||
|
|
||||||
(. defn incr [x]
|
|
||||||
(: + x 1))
|
|
||||||
```
|
|
||||||
|
|
||||||
## Function returns
|
|
||||||
|
|
||||||
### Single returns
|
|
||||||
|
|
||||||
A function returns whatever the last expression in its execution returned. For
|
|
||||||
example:
|
|
||||||
|
|
||||||
```
|
|
||||||
(. defn abs [x]
|
|
||||||
(. if (: >= x 0)
|
|
||||||
x
|
|
||||||
(: * -1 x)))
|
|
||||||
```
|
|
||||||
|
|
||||||
This function will return the argument `x` if it is greater than or equal to 0,
|
|
||||||
or `(: * -1 x)` if it's not.
|
|
||||||
|
|
||||||
### Multiple returns
|
|
||||||
|
|
||||||
A function which wishes to return multiple arguments should return them as a
|
|
||||||
vector or list of arguments. The `let` function, which can be used to define
|
|
||||||
temporary variables in a scope, can deconstruct these multiple-returns:
|
|
||||||
|
|
||||||
```
|
|
||||||
# Returns two numbers which sum up to 10
|
|
||||||
(. defn sum-10 []
|
|
||||||
[4 6])
|
|
||||||
|
|
||||||
(. let [[foo bar] (: sum-10)
|
|
||||||
(: fmt.Printf "%d + %d = 10\n" foo bar))
|
|
||||||
```
|
|
||||||
|
|
||||||
Functions defined within a go library which return multiple values can also be
|
|
||||||
deconstructed in a `let` statement:
|
|
||||||
|
|
||||||
```
|
|
||||||
(. let [[conn err] (: net.Dial "tcp" "localhost:80")]
|
|
||||||
# do stuff)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Variadic function
|
|
||||||
|
|
||||||
Functions may take in any number of variables using the `...` syntax, similar to
|
|
||||||
how go does variadic functions. The variadic variable must be the last in the
|
|
||||||
function's argument list, and is used as a list inside of the function. A list
|
|
||||||
may also be used as the input for a variadic function, also using the `...`
|
|
||||||
syntax.
|
|
||||||
|
|
||||||
The following is an example of both defining a variadic function and both ways
|
|
||||||
of using it. `+` is a variadic function in this example:
|
|
||||||
|
|
||||||
```
|
|
||||||
(. defn avg [...x]
|
|
||||||
(/
|
|
||||||
(+ x...)
|
|
||||||
(len x)))
|
|
||||||
|
|
||||||
(: fmt.Println (avg 1 2 3 4 5))
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tail-recursion
|
|
||||||
|
|
||||||
TODO
|
|
@ -1,53 +0,0 @@
|
|||||||
# Go interop
|
|
||||||
|
|
||||||
Ginger translates down to go code, and many of its conventions and rules follow
|
|
||||||
from go's conventions and rules. In most cases these decisions were made to help
|
|
||||||
with interoperability with existing go code.
|
|
||||||
|
|
||||||
## Referencing go package variables/functions
|
|
||||||
|
|
||||||
See the package doc for more on this
|
|
||||||
|
|
||||||
## Types
|
|
||||||
|
|
||||||
Go types and ginger types share a lot of overlap:
|
|
||||||
|
|
||||||
* Ginger strings are of go's `string` type
|
|
||||||
|
|
||||||
* Ginger integers are of go's `int` type
|
|
||||||
|
|
||||||
* Ginger floats are of go's `float32` type
|
|
||||||
|
|
||||||
* Ginger characters are of go's `rune` type
|
|
||||||
|
|
||||||
* Ginger errors are of go's `error` type
|
|
||||||
|
|
||||||
## Casting
|
|
||||||
|
|
||||||
Each go type has a corresponding ginger casting function:
|
|
||||||
|
|
||||||
```
|
|
||||||
(: int64 5)
|
|
||||||
(: float64 5.5)
|
|
||||||
(: rune 'a')
|
|
||||||
```
|
|
||||||
|
|
||||||
## go-drop
|
|
||||||
|
|
||||||
the `go-drop` form can be used for furthur interoperability. The rationale
|
|
||||||
behind `go-drop` is that there are simply too many cases to be able to create
|
|
||||||
enough individual functions, or a few generic functions, that would cover all
|
|
||||||
cases. Instead we use a single function, `go-drop`, which lets us drop down into
|
|
||||||
go code and interact with it directly. There are a number of pre-made functions
|
|
||||||
which implement commonly needed behaviors, such as `StringSlice` and
|
|
||||||
`ByteSlice`, which cast from either go or ginger types into `[]string` and
|
|
||||||
`[]byte`, respectively.
|
|
||||||
|
|
||||||
```
|
|
||||||
(. go-drop
|
|
||||||
"func StringSlice(v ginger.Elem) []string {
|
|
||||||
ret := []string{}
|
|
||||||
// do some stuff
|
|
||||||
return ret
|
|
||||||
}")
|
|
||||||
```
|
|
@ -1,95 +0,0 @@
|
|||||||
# Packages
|
|
||||||
|
|
||||||
Ginger packages follow many of the same packaging rules as go packages. This
|
|
||||||
stems from ginger compiling down to go and needing to inter-op with go packages.
|
|
||||||
|
|
||||||
As discussed in the compilation doc, packages are defined as follows:
|
|
||||||
|
|
||||||
```
|
|
||||||
(. package "github.com/mediocregopher/awesome"
|
|
||||||
|
|
||||||
(. def AwesomeThing "totally")
|
|
||||||
|
|
||||||
(. defn AwesomeFunction []
|
|
||||||
(: rand))
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
This expression does not have to appear in a particular folder heirarchy, and
|
|
||||||
multiple packages can appear in a single file. A package's definition can be
|
|
||||||
split up into multiple package statements across different files. This is
|
|
||||||
discussed more in the compilation doc.
|
|
||||||
|
|
||||||
## Variable/Function naming
|
|
||||||
|
|
||||||
Variables and functions follow the go rule of upper camel casing for public
|
|
||||||
variables/functions and lower cammel casing for private variables/functions.
|
|
||||||
This rule is enforced by the go compiler for both go packages and translated
|
|
||||||
ginger packages.
|
|
||||||
|
|
||||||
### Private
|
|
||||||
|
|
||||||
A private variable/function can only be referenced from within a package. If a
|
|
||||||
package is split into multiple parts across a project a private
|
|
||||||
variable/function defined in one part can be used in another part.
|
|
||||||
|
|
||||||
### Public
|
|
||||||
|
|
||||||
A public variable/function can be used within a package without any extra
|
|
||||||
embelishment (in the above example, `(: AwesomeFunction)` could simply be called
|
|
||||||
from within the package).
|
|
||||||
|
|
||||||
Outside of a package variables/functions can be used as follows:
|
|
||||||
|
|
||||||
```
|
|
||||||
(. package "show-and-tell"
|
|
||||||
|
|
||||||
(. defn main []
|
|
||||||
(: fmt.Println
|
|
||||||
(: github.com/mediocregopher/awesome.AwesomeFunction)))
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
`show-and-tell.main` uses both the `Println` function from the `fmt` package and the
|
|
||||||
`AwesomeFunction` function from the `github.com/mediocregopher/awesome` package.
|
|
||||||
This syntax is rather cumbersome, however, and can be shortcutted using the
|
|
||||||
`alias` function in a package
|
|
||||||
|
|
||||||
```
|
|
||||||
(. package "show-and-tell"
|
|
||||||
|
|
||||||
(. alias "github.com/mediocregopher/awesome" "aw")
|
|
||||||
|
|
||||||
(. defn main []
|
|
||||||
(: fmt.Println
|
|
||||||
(: aw.AwesomeFunction)))
|
|
||||||
```
|
|
||||||
|
|
||||||
Like go, aliasing a package to `"."` imports it directly:
|
|
||||||
|
|
||||||
```
|
|
||||||
(. package "show-and-tell"
|
|
||||||
|
|
||||||
(. alias "github.com/mediocregopher/awesome" ".")
|
|
||||||
|
|
||||||
(. defn main []
|
|
||||||
(: fmt.Println
|
|
||||||
(: AwesomeFunction)))
|
|
||||||
```
|
|
||||||
|
|
||||||
Aliasing a package requires that you use it in the package you've aliased it in,
|
|
||||||
unless you alias to `"_"`.
|
|
||||||
|
|
||||||
## Idiomatic package usage
|
|
||||||
|
|
||||||
While it is not a requirement that your package namespaces follow the directory
|
|
||||||
heierarchy they show (in fact, you could have an entire project, with multiple
|
|
||||||
packages, all within a giant flat file), it's definitely recommended that you
|
|
||||||
do. It will make the code much easier to create a mental map of for newcomers to
|
|
||||||
it.
|
|
||||||
|
|
||||||
## Circular dependencies
|
|
||||||
|
|
||||||
Go enforces that a package may not have circular dependencies. That is,
|
|
||||||
`packageA` may not import `packageB` while `packageB` also imports `packageA`.
|
|
||||||
Ginger will also, be way of being translated to go, also enforce this rule.
|
|
144
docs/syntax.md
144
docs/syntax.md
@ -1,144 +0,0 @@
|
|||||||
# Syntax
|
|
||||||
|
|
||||||
This document describes the ginger syntax and data-structures, and how they are
|
|
||||||
evaluated.
|
|
||||||
|
|
||||||
# Goals
|
|
||||||
|
|
||||||
I have some immediate goals I'm trying to achieve with this syntax:
|
|
||||||
|
|
||||||
* Everything is strings (except numbers, functions, and composites). There is no
|
|
||||||
symbol type, atom type, keyword type, etc... they're all just strings.
|
|
||||||
|
|
||||||
* There is no `defmacro`. Macro creation and usage is simply an inherent feature
|
|
||||||
of the language syntax.
|
|
||||||
|
|
||||||
# Walkthrough
|
|
||||||
|
|
||||||
This is a number which evalutates to 5:
|
|
||||||
|
|
||||||
```
|
|
||||||
5
|
|
||||||
```
|
|
||||||
|
|
||||||
This is a string, it can contain anything:
|
|
||||||
|
|
||||||
```
|
|
||||||
"! I'm the king of the world !"
|
|
||||||
```
|
|
||||||
|
|
||||||
This is a list. It evaluates to a linked-list of four strings:
|
|
||||||
|
|
||||||
```
|
|
||||||
("a" "b" "c" "d")
|
|
||||||
```
|
|
||||||
|
|
||||||
This is a vector of those same elements. It's like a list, but has some slightly
|
|
||||||
different properties. We'll mostly be using lists:
|
|
||||||
|
|
||||||
```
|
|
||||||
["a" "b" "c" "d"]
|
|
||||||
```
|
|
||||||
|
|
||||||
This is a string
|
|
||||||
|
|
||||||
```
|
|
||||||
"+"
|
|
||||||
```
|
|
||||||
|
|
||||||
`:` is the evaluator. A string beginning with `:` is evaluated to whatever it
|
|
||||||
references. This evaluates to a function which adds its arguments:
|
|
||||||
|
|
||||||
```
|
|
||||||
":+"
|
|
||||||
```
|
|
||||||
|
|
||||||
This evaluates to list whose elements are a function and two numbers:
|
|
||||||
|
|
||||||
```
|
|
||||||
(":+" 1 2)
|
|
||||||
```
|
|
||||||
|
|
||||||
A list whose first element is a `:` calls the second element as a function with
|
|
||||||
the rest of the elements as arguments. This evaluates to the number 5:
|
|
||||||
|
|
||||||
```
|
|
||||||
(":" ":+" 1 2)
|
|
||||||
```
|
|
||||||
|
|
||||||
A bare string (lacking in `"`) is automatically prefixed with a `:`, if it
|
|
||||||
doesn't already have one. So `":+"`, `:+`, and `+`, are equivalent. `":"` and
|
|
||||||
`:` are also equivalent. This is equivalent to the previous example:
|
|
||||||
|
|
||||||
```
|
|
||||||
(: + 1 2)
|
|
||||||
```
|
|
||||||
|
|
||||||
The `fn` function can be used to define a new function. Note the `.` instead of
|
|
||||||
`:`. We'll cover that in a bit. This evaluates to an anonymous function which
|
|
||||||
adds one to its argument and returns it:
|
|
||||||
|
|
||||||
```
|
|
||||||
(. fn [x]
|
|
||||||
(: + x 1))
|
|
||||||
```
|
|
||||||
|
|
||||||
The `def` function can be used to bind some value to a new variable. This
|
|
||||||
defines a variable `foo` which evaluates to the string `"bar"`:
|
|
||||||
|
|
||||||
```
|
|
||||||
(. def foo "bar")
|
|
||||||
```
|
|
||||||
|
|
||||||
This defines a variable `incr` which evaluates to a function which adds one to
|
|
||||||
its argument:
|
|
||||||
|
|
||||||
```
|
|
||||||
(. def incr
|
|
||||||
(. fn [x]
|
|
||||||
(: + x 1)))
|
|
||||||
```
|
|
||||||
|
|
||||||
This uses `defn` as a shortcut for the above:
|
|
||||||
```
|
|
||||||
(. defn incr [x]
|
|
||||||
(: + x 1))
|
|
||||||
```
|
|
||||||
|
|
||||||
There are also maps. A map's keys can be any value(?). A map's values can be any
|
|
||||||
value. This evaluates to a map with 2 key/val pairs:
|
|
||||||
|
|
||||||
```
|
|
||||||
{ "foo" foo
|
|
||||||
"bar" (: incr 4) }
|
|
||||||
```
|
|
||||||
|
|
||||||
`.` is the half-evaluator. It only works on lists, and runs the function given
|
|
||||||
in the first argument with the unevaluated arguments (even if they have `:`).
|
|
||||||
You can generate new code to run (compile-time macros) using the normal `fn`.
|
|
||||||
The returned value is evaluated in place of the original. This evaluates to a
|
|
||||||
`let`-like function, except it forces you to use the capitalized variable names
|
|
||||||
in the body (utterly useless):
|
|
||||||
|
|
||||||
```
|
|
||||||
#
|
|
||||||
# map-alternate is a made up function which maps over every other element in a
|
|
||||||
# list, starting with the first.
|
|
||||||
# E.g. (: map-alternate (. fn [x] (: + x 1)) (1 2 3 4 5)) -> (2 2 4 4 6)
|
|
||||||
#
|
|
||||||
# capitalize is a made up function which looks for the first letter in a string
|
|
||||||
# and capitalizes it
|
|
||||||
#
|
|
||||||
(. defn caplet [mapping body...]
|
|
||||||
("." let
|
|
||||||
(: map-alternate
|
|
||||||
(. fn [x] (: capitalize x))
|
|
||||||
mapping)
|
|
||||||
body...))
|
|
||||||
|
|
||||||
#Usage
|
|
||||||
(. caplet [foo "this is foo"
|
|
||||||
dog "this is dog"]
|
|
||||||
(: println Foo)
|
|
||||||
(: println Dog))
|
|
||||||
```
|
|
76
eval/eval.go
76
eval/eval.go
@ -1,76 +0,0 @@
|
|||||||
// The eval package encompasses all that is necesary to do runtime evaluation of
|
|
||||||
// ginger structures. These are different than macros in that they aren't turned
|
|
||||||
// into go code, instead the compiled go code evaluates them at runtime
|
|
||||||
package eval
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/macros/pkgctx"
|
|
||||||
"github.com/mediocregopher/ginger/seq"
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Evaler is a function which can be used inside of eval. It must take in its
|
|
||||||
// arguments as a sequence of Elems, and return a resulting Elem
|
|
||||||
type Evaler func(seq.Seq) types.Elem
|
|
||||||
|
|
||||||
// Bail stops compilation. The given element should be the reason compilation
|
|
||||||
// has stopped
|
|
||||||
func Bail(el types.Elem, reason string) {
|
|
||||||
fmt.Fprintln(os.Stderr, reason)
|
|
||||||
time.Sleep(100 * time.Second)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bailf is like Bail, but takes in formatting
|
|
||||||
func Bailf(el types.Elem, format string, args ...interface{}) {
|
|
||||||
reason := fmt.Sprintf(format, args...)
|
|
||||||
Bail(el, reason)
|
|
||||||
}
|
|
||||||
|
|
||||||
var colon = types.GoType{":"}
|
|
||||||
|
|
||||||
// Eval takes in the pkgctx it is being executed in, as well as a single Elem to
|
|
||||||
// be evaluated, and returns the Elem it evaluates to
|
|
||||||
func Eval(p *pkgctx.PkgCtx, el types.Elem) types.Elem {
|
|
||||||
l, ok := el.(*seq.List)
|
|
||||||
if !ok {
|
|
||||||
return el
|
|
||||||
}
|
|
||||||
|
|
||||||
first, rest, ok := l.FirstRest()
|
|
||||||
if !ok || !first.Equal(colon) {
|
|
||||||
return el
|
|
||||||
}
|
|
||||||
|
|
||||||
fnEl, args, ok := rest.FirstRest()
|
|
||||||
if !ok {
|
|
||||||
Bail(el, "Empty list after colon, no function given")
|
|
||||||
}
|
|
||||||
|
|
||||||
var fnName string
|
|
||||||
if gt, ok := fnEl.(types.GoType); ok {
|
|
||||||
fnName, _ = gt.V.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
if fnName == "" || fnName[0] != ':' {
|
|
||||||
Bail(el, "Must give a function reference to execute")
|
|
||||||
}
|
|
||||||
fnName = fnName[1:]
|
|
||||||
|
|
||||||
fn, ok := p.CallMap[fnName]
|
|
||||||
if !ok {
|
|
||||||
Bailf(el, "Unknown function name %q", fnName)
|
|
||||||
}
|
|
||||||
|
|
||||||
evalArgFn := func(el types.Elem) types.Elem {
|
|
||||||
return Eval(p, el)
|
|
||||||
}
|
|
||||||
|
|
||||||
evaldArgs := seq.Map(evalArgFn, args)
|
|
||||||
|
|
||||||
return fn.(Evaler)(evaldArgs)
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
package eval
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "testing"
|
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/core"
|
|
||||||
"github.com/mediocregopher/ginger/macros/pkgctx"
|
|
||||||
"github.com/mediocregopher/ginger/parse"
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is NOT how I want eval to really work in the end, but I wanted to get
|
|
||||||
// something down before I kept thinking about it, so I would know what would
|
|
||||||
// work
|
|
||||||
|
|
||||||
func TestShittyPlus(t *T) {
|
|
||||||
p := &pkgctx.PkgCtx{
|
|
||||||
CallMap: map[string]interface{}{
|
|
||||||
"Plus": Evaler(core.Plus),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
m := map[string]types.Elem{
|
|
||||||
"(: Plus)": types.GoType{0},
|
|
||||||
"(: Plus 1 2 3)": types.GoType{6},
|
|
||||||
`(: Plus 1 2 3
|
|
||||||
(: Plus 1 2 3))`: types.GoType{12},
|
|
||||||
}
|
|
||||||
|
|
||||||
for input, output := range m {
|
|
||||||
parsed, err := parse.ParseString(input)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
evald := Eval(p, parsed)
|
|
||||||
if !evald.Equal(output) {
|
|
||||||
t.Fatalf("input: %q %#v != %#v", input, output, evald)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package macros
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mediocregopher/ginger/macros/pkgctx"
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Package(p *pkgctx.PkgCtx, el types.Elem) string {
|
|
||||||
return ""
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package macros
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/macros/pkgctx"
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Macro takes in a ginger structure and returns the go code which corresponds
|
|
||||||
// to it. The structure will contain everything in the calling list after the
|
|
||||||
// macro name (for example, (. jkjkNo error is returned, Bail can be called to stop compilation mid-way
|
|
||||||
// instead.
|
|
||||||
type Macro func(*pkgctx.PkgCtx, types.Elem) string
|
|
||||||
|
|
||||||
// Bail stops compilation. The given element should be the reason compilation
|
|
||||||
// has stopped
|
|
||||||
func Bail(el types.Elem, reason string) {
|
|
||||||
fmt.Fprintln(os.Stderr, reason)
|
|
||||||
time.Sleep(100 * time.Second)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bailf is like Bail, but takes in formatting
|
|
||||||
func Bailf(el types.Elem, format string, args ...interface{}) {
|
|
||||||
reason := fmt.Sprintf(format, args...)
|
|
||||||
Bail(el, reason)
|
|
||||||
}
|
|
@ -1,113 +0,0 @@
|
|||||||
package pkgctx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/seq"
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
coreAbs = "github.com/mediocregopher/ginger/core"
|
|
||||||
coreAlias = "gingercore"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PkgCtx is given to all macros and represents the package that they are being
|
|
||||||
// evaluated within.
|
|
||||||
type PkgCtx struct {
|
|
||||||
|
|
||||||
// Packages describes the external packages imported by this one. Each key
|
|
||||||
// is the absolute package path, the value is the alias for it (or empty
|
|
||||||
// string for no alias)
|
|
||||||
Packages map[string]string
|
|
||||||
|
|
||||||
// CallMap is a map used by Eval for making actual calls dynamically. The
|
|
||||||
// key is the string representation of the call to be used (for example,
|
|
||||||
// "fmt.Println") and must agree with the aliases being used in Packages.
|
|
||||||
// The value need not be set during actual compilation, but it is useful to
|
|
||||||
// use it during testing
|
|
||||||
CallMap map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns an empty PkgCtx
|
|
||||||
func New() *PkgCtx {
|
|
||||||
return &PkgCtx{
|
|
||||||
Packages: map[string]string{},
|
|
||||||
CallMap: map[string]interface{}{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A returns a new PkgCtx, which is a copy of a merge between p and p2. p2's
|
|
||||||
// keys overwrite any conflicting keys in p. p and p2 are unaffected by this
|
|
||||||
// operation
|
|
||||||
func (p *PkgCtx) MergeLeft(p2 *PkgCtx) *PkgCtx {
|
|
||||||
p3 := New()
|
|
||||||
for pkg := range p.Packages {
|
|
||||||
p3.Packages[pkg] = p.Packages[pkg]
|
|
||||||
}
|
|
||||||
for pkg := range p2.Packages {
|
|
||||||
p3.Packages[pkg] = p2.Packages[pkg]
|
|
||||||
}
|
|
||||||
for fn := range p.CallMap {
|
|
||||||
p3.CallMap[fn] = p.CallMap[fn]
|
|
||||||
}
|
|
||||||
for fn := range p2.CallMap {
|
|
||||||
p3.CallMap[fn] = p2.CallMap[fn]
|
|
||||||
}
|
|
||||||
return p3
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a copy of p
|
|
||||||
func (p *PkgCtx) Copy() *PkgCtx {
|
|
||||||
return p.MergeLeft(New())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PkgCtx) PopulateFromCode(el types.Elem) bool {
|
|
||||||
if s, ok := el.(seq.Seq); ok {
|
|
||||||
return seq.Traverse(p.PopulateFromCode, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
gt, ok := el.(types.GoType)
|
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
str, ok := gt.V.(string)
|
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(str) < 2 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if str[0] != ':' {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
str = str[1:]
|
|
||||||
|
|
||||||
// At this point str is a reference to something. We check if it's already
|
|
||||||
// pointing somewhere first
|
|
||||||
if _, ok = p.CallMap[str]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there isn't a '.' in the string, it's not directly referencing another
|
|
||||||
// package. Since it isn't in this context either, it must be referencing
|
|
||||||
// something in core
|
|
||||||
var i int
|
|
||||||
if i = strings.IndexRune(str, '.'); i < 1 {
|
|
||||||
p.Packages[coreAbs] = coreAlias
|
|
||||||
p.CallMap[str] = nil
|
|
||||||
|
|
||||||
// TODO there needs to be a CodeGen interface or something. The
|
|
||||||
// CallMap's value type needs to be that, because in all likelyhood the
|
|
||||||
// CallMap will be directly translated to a map variable in the
|
|
||||||
// generated package, with the values being different depending on what
|
|
||||||
// they're being used for (external functions will be references to
|
|
||||||
// those functions, local variables may just end up being just GoType's
|
|
||||||
// of the actual value.
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
229
parse/lex/lex.go
229
parse/lex/lex.go
@ -1,229 +0,0 @@
|
|||||||
// The lex package implements a lexical reader which can take in any io.Reader.
|
|
||||||
// It does not care about the meaning or logical validity of the tokens it
|
|
||||||
// parses out, it simply does its job.
|
|
||||||
package lex
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TokenType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
BareString TokenType = iota
|
|
||||||
QuotedString
|
|
||||||
Open
|
|
||||||
Close
|
|
||||||
Err
|
|
||||||
eof
|
|
||||||
)
|
|
||||||
|
|
||||||
var invalidBareStringRunes = map[rune]bool{
|
|
||||||
'"': true,
|
|
||||||
'\'': true,
|
|
||||||
'(': true,
|
|
||||||
')': true,
|
|
||||||
'[': true,
|
|
||||||
']': true,
|
|
||||||
'{': true,
|
|
||||||
'}': true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Token represents a single set of characters which *could* be a valid token of
|
|
||||||
// the given type
|
|
||||||
type Token struct {
|
|
||||||
Type TokenType
|
|
||||||
Val string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the token's value as an error, or nil if the token is not of type
|
|
||||||
// Err. If the token is nil returns io.EOF, since that is the ostensible meaning
|
|
||||||
func (t *Token) AsError() error {
|
|
||||||
if t == nil {
|
|
||||||
return io.EOF
|
|
||||||
}
|
|
||||||
if t.Type != Err {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New(t.Val)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
errInvalidUTF8 = errors.New("invalid utf8 character")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Lexer reads through an io.Reader and emits Tokens from it.
|
|
||||||
type Lexer struct {
|
|
||||||
r *bufio.Reader
|
|
||||||
outbuf *bytes.Buffer
|
|
||||||
ch chan *Token
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLexer constructs a new Lexer struct and returns it. r is internally
|
|
||||||
// wrapped with a bufio.Reader, unless it already is one. This will spawn a
|
|
||||||
// go-routine which reads from r until it hits an error, at which point it will
|
|
||||||
// end execution.
|
|
||||||
func NewLexer(r io.Reader) *Lexer {
|
|
||||||
var br *bufio.Reader
|
|
||||||
var ok bool
|
|
||||||
if br, ok = r.(*bufio.Reader); !ok {
|
|
||||||
br = bufio.NewReader(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
l := Lexer{
|
|
||||||
r: br,
|
|
||||||
ch: make(chan *Token),
|
|
||||||
outbuf: bytes.NewBuffer(make([]byte, 0, 1024)),
|
|
||||||
}
|
|
||||||
|
|
||||||
go l.spin()
|
|
||||||
|
|
||||||
return &l
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Lexer) spin() {
|
|
||||||
f := lexWhitespace
|
|
||||||
for {
|
|
||||||
f = f(l)
|
|
||||||
if f == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the next available token, or nil if EOF has been reached. If an error
|
|
||||||
// other than EOF has been reached it will be returned as the Err token type,
|
|
||||||
// and this method should not be called again after that.
|
|
||||||
func (l *Lexer) Next() *Token {
|
|
||||||
t := <-l.ch
|
|
||||||
if t.Type == eof {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Lexer) emit(t TokenType) {
|
|
||||||
str := l.outbuf.String()
|
|
||||||
l.ch <- &Token{
|
|
||||||
Type: t,
|
|
||||||
Val: str,
|
|
||||||
}
|
|
||||||
l.outbuf.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Lexer) peek() (rune, error) {
|
|
||||||
r, err := l.readRune()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if err = l.r.UnreadRune(); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Lexer) readRune() (rune, error) {
|
|
||||||
r, i, err := l.r.ReadRune()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
} else if r == unicode.ReplacementChar && i == 1 {
|
|
||||||
return 0, errInvalidUTF8
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Lexer) err(err error) lexerFunc {
|
|
||||||
if err == io.EOF {
|
|
||||||
l.ch <- &Token{eof, ""}
|
|
||||||
} else {
|
|
||||||
l.ch <- &Token{Err, err.Error()}
|
|
||||||
}
|
|
||||||
close(l.ch)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Lexer) errf(format string, args ...interface{}) lexerFunc {
|
|
||||||
s := fmt.Sprintf(format, args...)
|
|
||||||
l.ch <- &Token{Err, s}
|
|
||||||
close(l.ch)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type lexerFunc func(*Lexer) lexerFunc
|
|
||||||
|
|
||||||
func lexWhitespace(l *Lexer) lexerFunc {
|
|
||||||
r, err := l.readRune()
|
|
||||||
if err != nil {
|
|
||||||
return l.err(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if unicode.IsSpace(r) {
|
|
||||||
return lexWhitespace
|
|
||||||
}
|
|
||||||
|
|
||||||
l.outbuf.WriteRune(r)
|
|
||||||
|
|
||||||
switch r {
|
|
||||||
case '"':
|
|
||||||
return lexQuotedString
|
|
||||||
case '(':
|
|
||||||
l.emit(Open)
|
|
||||||
case ')':
|
|
||||||
l.emit(Close)
|
|
||||||
case '[':
|
|
||||||
l.emit(Open)
|
|
||||||
case ']':
|
|
||||||
l.emit(Close)
|
|
||||||
case '{':
|
|
||||||
l.emit(Open)
|
|
||||||
case '}':
|
|
||||||
l.emit(Close)
|
|
||||||
default:
|
|
||||||
return lexBareString
|
|
||||||
}
|
|
||||||
|
|
||||||
return lexWhitespace
|
|
||||||
}
|
|
||||||
|
|
||||||
func lexQuotedString(l *Lexer) lexerFunc {
|
|
||||||
r, err := l.readRune()
|
|
||||||
if err != nil {
|
|
||||||
l.emit(QuotedString)
|
|
||||||
return l.err(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.outbuf.WriteRune(r)
|
|
||||||
buf := l.outbuf.Bytes()
|
|
||||||
|
|
||||||
if r == '"' && buf[len(buf)-2] != '\\' {
|
|
||||||
l.emit(QuotedString)
|
|
||||||
return lexWhitespace
|
|
||||||
}
|
|
||||||
return lexQuotedString
|
|
||||||
}
|
|
||||||
|
|
||||||
func lexBareString(l *Lexer) lexerFunc {
|
|
||||||
r, err := l.peek()
|
|
||||||
if err != nil {
|
|
||||||
l.emit(BareString)
|
|
||||||
return l.err(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := invalidBareStringRunes[r]; ok || unicode.IsSpace(r) {
|
|
||||||
l.emit(BareString)
|
|
||||||
return lexWhitespace
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = l.readRune(); err != nil {
|
|
||||||
l.emit(BareString)
|
|
||||||
return l.err(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.outbuf.WriteRune(r)
|
|
||||||
return lexBareString
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
package lex
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
. "testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLexer(t *T) {
|
|
||||||
m := map[string][]Token{
|
|
||||||
"": {{eof, ""}},
|
|
||||||
" \t": {{eof, ""}},
|
|
||||||
"a b c": {{BareString, "a"},
|
|
||||||
{BareString, "b"},
|
|
||||||
{BareString, "c"},
|
|
||||||
{eof, ""}},
|
|
||||||
"\"foo\" bar": {{QuotedString, "\"foo\""},
|
|
||||||
{BareString, "bar"},
|
|
||||||
{eof, ""}},
|
|
||||||
"\"foo\nbar\" baz": {{QuotedString, "\"foo\nbar\""},
|
|
||||||
{BareString, "baz"},
|
|
||||||
{eof, ""}},
|
|
||||||
"( foo bar ) baz": {{Open, "("},
|
|
||||||
{BareString, "foo"},
|
|
||||||
{BareString, "bar"},
|
|
||||||
{Close, ")"},
|
|
||||||
{BareString, "baz"},
|
|
||||||
{eof, ""}},
|
|
||||||
"((foo-bar))": {{Open, "("},
|
|
||||||
{Open, "("},
|
|
||||||
{BareString, "foo-bar"},
|
|
||||||
{Close, ")"},
|
|
||||||
{Close, ")"},
|
|
||||||
{eof, ""}},
|
|
||||||
"(\"foo\nbar\")": {{Open, "("},
|
|
||||||
{QuotedString, "\"foo\nbar\""},
|
|
||||||
{Close, ")"},
|
|
||||||
{eof, ""}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for input, output := range m {
|
|
||||||
buf := bytes.NewBufferString(input)
|
|
||||||
l := NewLexer(buf)
|
|
||||||
for i := range output {
|
|
||||||
tok := l.Next()
|
|
||||||
if tok == nil {
|
|
||||||
if output[i].Type == eof {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t.Fatalf("input: %q (%d) %#v != %#v", input, i, output[i], tok)
|
|
||||||
}
|
|
||||||
if *tok != output[i] {
|
|
||||||
t.Fatalf("input: %s (%d) %#v != %#v", input, i, output[i], tok)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
155
parse/parse.go
155
parse/parse.go
@ -1,155 +0,0 @@
|
|||||||
// The parse package implements a syntax parser for the ginger syntax. It can
|
|
||||||
// read in any io.Reader and returns fully parsed Elem's from the types package
|
|
||||||
// that it finds.
|
|
||||||
package parse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/parse/lex"
|
|
||||||
"github.com/mediocregopher/ginger/seq"
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
const int_bits = int(unsafe.Sizeof(int(0)) * 8)
|
|
||||||
|
|
||||||
var closers = map[string]string{
|
|
||||||
"(": ")",
|
|
||||||
"[": "]",
|
|
||||||
"{": "}",
|
|
||||||
}
|
|
||||||
|
|
||||||
// The lexer only indicates a bare string, but technically an integer or a float
|
|
||||||
// is a bare string so we must try and convert to one of those first
|
|
||||||
func parseBareString(tok *lex.Token) types.Elem {
|
|
||||||
if i, err := strconv.ParseInt(tok.Val, 10, int_bits); err == nil {
|
|
||||||
return types.GoType{int(i)}
|
|
||||||
|
|
||||||
} else if int_bits == 64 {
|
|
||||||
// We don't want to bother with the next case if int_bits is 64
|
|
||||||
|
|
||||||
} else if i64, err := strconv.ParseInt(tok.Val, 10, 64); err == nil {
|
|
||||||
return types.GoType{int64(i64)}
|
|
||||||
}
|
|
||||||
|
|
||||||
if f32, err := strconv.ParseFloat(tok.Val, 32); err == nil {
|
|
||||||
return types.GoType{float32(f32)}
|
|
||||||
}
|
|
||||||
|
|
||||||
if f64, err := strconv.ParseFloat(tok.Val, 64); err == nil {
|
|
||||||
return types.GoType{float64(f64)}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tok.Val[0] != ':' {
|
|
||||||
return types.GoType{":" + tok.Val}
|
|
||||||
}
|
|
||||||
|
|
||||||
return types.GoType{tok.Val}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseQuotedString(tok *lex.Token) (types.Elem, error) {
|
|
||||||
s, err := strconv.Unquote(tok.Val)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return types.GoType{s}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Parser struct {
|
|
||||||
l *lex.Lexer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a NewParser, using the lex package as the tokenizer
|
|
||||||
func NewParser(r io.Reader) *Parser {
|
|
||||||
p := Parser{
|
|
||||||
l: lex.NewLexer(r),
|
|
||||||
}
|
|
||||||
return &p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reads a full element, and any sub-elements (if the top-level element is a
|
|
||||||
// data-structure) into an Elem and returns it. Returns any errors, including
|
|
||||||
// io.EOF, if it runs into them instead
|
|
||||||
func (p *Parser) ReadElem() (types.Elem, error) {
|
|
||||||
tok := p.l.Next()
|
|
||||||
return p.parseToken(tok)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parser) parseToken(tok *lex.Token) (types.Elem, error) {
|
|
||||||
if tok == nil {
|
|
||||||
return nil, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tok.Type {
|
|
||||||
case lex.Err:
|
|
||||||
return nil, tok.AsError()
|
|
||||||
case lex.BareString:
|
|
||||||
return parseBareString(tok), nil
|
|
||||||
case lex.QuotedString:
|
|
||||||
return parseQuotedString(tok)
|
|
||||||
case lex.Open:
|
|
||||||
series, err := p.readUntil(closers[tok.Val])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if tok.Val == "(" {
|
|
||||||
return seq.NewList(series...), nil
|
|
||||||
} else if tok.Val == "{" {
|
|
||||||
if len(series)%2 != 0 {
|
|
||||||
return nil, fmt.Errorf("hash must have even number of elements")
|
|
||||||
}
|
|
||||||
kvs := make([]*seq.KV, 0, len(series)/2)
|
|
||||||
for i := 0; i < len(series); i += 2 {
|
|
||||||
kv := seq.KV{series[i], series[i+1]}
|
|
||||||
kvs = append(kvs, &kv)
|
|
||||||
}
|
|
||||||
return seq.NewHashMap(kvs...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
panic("should never get here")
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("Unexpected %q", tok.Val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parser) readUntil(closer string) ([]types.Elem, error) {
|
|
||||||
series := make([]types.Elem, 0, 4)
|
|
||||||
for {
|
|
||||||
tok := p.l.Next()
|
|
||||||
switch err := tok.AsError(); err {
|
|
||||||
case nil:
|
|
||||||
case io.EOF:
|
|
||||||
return nil, fmt.Errorf("Unexpected EOF")
|
|
||||||
default:
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if tok.Type != lex.Close {
|
|
||||||
e, err := p.parseToken(tok)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
series = append(series, e)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if tok.Val != closer {
|
|
||||||
return nil, fmt.Errorf("Unexpected %q", tok.Val)
|
|
||||||
}
|
|
||||||
|
|
||||||
return series, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parses the first Elem it finds out of the given string and returns it
|
|
||||||
func ParseString(input string) (types.Elem, error) {
|
|
||||||
buf := bytes.NewBufferString(input)
|
|
||||||
p := NewParser(buf)
|
|
||||||
return p.ReadElem()
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
package parse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
. "testing"
|
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/seq"
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParse(t *T) {
|
|
||||||
m := map[string]types.Elem{
|
|
||||||
"1": types.GoType{int(1)},
|
|
||||||
"-1": types.GoType{int(-1)},
|
|
||||||
"+1": types.GoType{int(1)},
|
|
||||||
|
|
||||||
"1.5": types.GoType{float32(1.5)},
|
|
||||||
"-1.5": types.GoType{float32(-1.5)},
|
|
||||||
"+1.5": types.GoType{float32(1.5)},
|
|
||||||
"1.5e1": types.GoType{float32(15)},
|
|
||||||
|
|
||||||
"foo": types.GoType{":foo"},
|
|
||||||
|
|
||||||
"()": seq.NewList(),
|
|
||||||
|
|
||||||
"(foo)": seq.NewList(
|
|
||||||
types.GoType{":foo"},
|
|
||||||
),
|
|
||||||
|
|
||||||
"(foo (bar))": seq.NewList(
|
|
||||||
types.GoType{":foo"},
|
|
||||||
seq.NewList(types.GoType{":bar"}),
|
|
||||||
),
|
|
||||||
|
|
||||||
"{}": seq.NewHashMap(),
|
|
||||||
|
|
||||||
"{foo bar}": seq.NewHashMap(
|
|
||||||
seq.KeyVal(types.GoType{":foo"}, types.GoType{":bar"}),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
for input, output := range m {
|
|
||||||
parsed, err := ParseString(input)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !output.Equal(parsed) {
|
|
||||||
t.Fatalf("input: %q %#v != %#v", input, output, parsed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseMulti(t *T) {
|
|
||||||
m := map[string][]types.Elem{
|
|
||||||
"foo 4 bar": {
|
|
||||||
types.GoType{":foo"},
|
|
||||||
types.GoType{4},
|
|
||||||
types.GoType{":bar"},
|
|
||||||
},
|
|
||||||
|
|
||||||
"foo \"bar\"": {
|
|
||||||
types.GoType{":foo"},
|
|
||||||
types.GoType{"bar"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for input, output := range m {
|
|
||||||
buf := bytes.NewBufferString(input)
|
|
||||||
p := NewParser(buf)
|
|
||||||
parsed := make([]types.Elem, 0, len(output))
|
|
||||||
for {
|
|
||||||
el, err := p.ReadElem()
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
} else if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
parsed = append(parsed, el)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(output) != len(parsed) {
|
|
||||||
t.Fatalf("input: %q %#v != %#v", input, output, parsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range output {
|
|
||||||
if !output[i].Equal(parsed[i]) {
|
|
||||||
t.Fatalf("input: %q (%d) %#v != %#v", input, i, output[i], parsed[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
166
seq/hashmap.go
166
seq/hashmap.go
@ -1,166 +0,0 @@
|
|||||||
package seq
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Hash maps are built on top of hash sets. KeyVal implements Setable, but the
|
|
||||||
// Hash and Equal methods only apply to the key and ignore the value.
|
|
||||||
|
|
||||||
// Container for a key/value pair, used by HashMap to hold its data
|
|
||||||
type KV struct {
|
|
||||||
Key types.Elem
|
|
||||||
Val types.Elem
|
|
||||||
}
|
|
||||||
|
|
||||||
func KeyVal(key, val types.Elem) *KV {
|
|
||||||
return &KV{key, val}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of Hash for Setable. Only actually hashes the Key field
|
|
||||||
func (kv *KV) Hash(i uint32) uint32 {
|
|
||||||
return hash(kv.Key, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of Equal for Setable. Only actually compares the key field. If
|
|
||||||
// compared to another KV, only compares the other key as well.
|
|
||||||
func (kv *KV) Equal(v types.Elem) bool {
|
|
||||||
if kv2, ok := v.(*KV); ok {
|
|
||||||
return kv.Key.Equal(kv2.Key)
|
|
||||||
}
|
|
||||||
return kv.Key.Equal(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kv *KV) fullEqual(v types.Elem) bool {
|
|
||||||
kv2, ok := v.(*KV)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return kv.Key.Equal(kv2.Key) && kv.Val.Equal(kv2.Val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of String for Stringer
|
|
||||||
func (kv *KV) String() string {
|
|
||||||
return fmt.Sprintf("%v -> %v", kv.Key, kv.Val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashMaps are actually built on top of Sets, just with some added convenience
|
|
||||||
// methods for interacting with them as actual key/val stores
|
|
||||||
type HashMap struct {
|
|
||||||
set *Set
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a new HashMap of the given KVs (or possibly just an empty HashMap)
|
|
||||||
func NewHashMap(kvs ...*KV) *HashMap {
|
|
||||||
ints := make([]types.Elem, len(kvs))
|
|
||||||
for i := range kvs {
|
|
||||||
ints[i] = kvs[i]
|
|
||||||
}
|
|
||||||
return &HashMap{
|
|
||||||
set: NewSet(ints...),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of FirstRest for Seq interface. First return value will
|
|
||||||
// always be a *KV or nil. Completes in O(log(N)) time.
|
|
||||||
func (hm *HashMap) FirstRest() (types.Elem, Seq, bool) {
|
|
||||||
if hm == nil {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
el, nset, ok := hm.set.FirstRest()
|
|
||||||
return el, &HashMap{nset.(*Set)}, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of Equal for types.Elem interface. Completes in O(Nlog(M))
|
|
||||||
// time if e is another HashMap, where M is the size of the given HashMap
|
|
||||||
func (hm *HashMap) Equal(e types.Elem) bool {
|
|
||||||
// This can't just use Set's Equal because that would end up using KeyVal's
|
|
||||||
// Equal, which is not a true Equal
|
|
||||||
|
|
||||||
hm2, ok := e.(*HashMap)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var el types.Elem
|
|
||||||
s := Seq(hm)
|
|
||||||
size := uint64(0)
|
|
||||||
|
|
||||||
for {
|
|
||||||
el, s, ok = s.FirstRest()
|
|
||||||
if !ok {
|
|
||||||
return size == hm2.Size()
|
|
||||||
}
|
|
||||||
size++
|
|
||||||
|
|
||||||
kv := el.(*KV)
|
|
||||||
k, v := kv.Key, kv.Val
|
|
||||||
|
|
||||||
v2, ok := hm2.Get(k)
|
|
||||||
if !ok || !v.Equal(v2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a new HashMap with the given value set on the given key. Also returns
|
|
||||||
// whether or not this was the first time setting that key (false if it was
|
|
||||||
// already there and was overwritten). Has the same complexity as Set's SetVal
|
|
||||||
// method.
|
|
||||||
func (hm *HashMap) Set(key, val types.Elem) (*HashMap, bool) {
|
|
||||||
if hm == nil {
|
|
||||||
hm = NewHashMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
nset, ok := hm.set.SetVal(KeyVal(key, val))
|
|
||||||
return &HashMap{nset}, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a new HashMap with the given key removed from it. Also returns
|
|
||||||
// whether or not the key was already there (true if so, false if not). Has the
|
|
||||||
// same time complexity as Set's DelVal method.
|
|
||||||
func (hm *HashMap) Del(key types.Elem) (*HashMap, bool) {
|
|
||||||
if hm == nil {
|
|
||||||
hm = NewHashMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
nset, ok := hm.set.DelVal(KeyVal(key, nil))
|
|
||||||
return &HashMap{nset}, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a value for a given key from the HashMap, along with a boolean
|
|
||||||
// indicating whether or not the value was found. Has the same time complexity
|
|
||||||
// as Set's GetVal method.
|
|
||||||
func (hm *HashMap) Get(key types.Elem) (types.Elem, bool) {
|
|
||||||
if hm == nil {
|
|
||||||
return nil, false
|
|
||||||
} else if kv, ok := hm.set.GetVal(KeyVal(key, nil)); ok {
|
|
||||||
return kv.(*KV).Val, true
|
|
||||||
} else {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same as FirstRest, but returns values already casted, which may be convenient
|
|
||||||
// in some cases.
|
|
||||||
func (hm *HashMap) FirstRestKV() (*KV, *HashMap, bool) {
|
|
||||||
if el, nhm, ok := hm.FirstRest(); ok {
|
|
||||||
return el.(*KV), nhm.(*HashMap), true
|
|
||||||
} else {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of String for Stringer interface
|
|
||||||
func (hm *HashMap) String() string {
|
|
||||||
return ToString(hm, "{", "}")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the number of KVs in the HashMap. Has the same complexity as Set's
|
|
||||||
// Size method.
|
|
||||||
func (hm *HashMap) Size() uint64 {
|
|
||||||
return hm.set.Size()
|
|
||||||
}
|
|
@ -1,143 +0,0 @@
|
|||||||
package seq
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "testing"
|
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func kvints(kvs ...*KV) ([]*KV, []types.Elem) {
|
|
||||||
ints := make([]types.Elem, len(kvs))
|
|
||||||
for i := range kvs {
|
|
||||||
ints[i] = kvs[i]
|
|
||||||
}
|
|
||||||
return kvs, ints
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that HashMap implements types.Elem (compile-time check)
|
|
||||||
func TestHashMapElem(t *T) {
|
|
||||||
_ = types.Elem(NewHashMap())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test creating a Set and calling the Seq interface methods on it
|
|
||||||
func TestHashMapSeq(t *T) {
|
|
||||||
kvs, ints := kvints(
|
|
||||||
keyValV(1, "one"),
|
|
||||||
keyValV(2, "two"),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Testing creation and Seq interface methods
|
|
||||||
m := NewHashMap(kvs...)
|
|
||||||
ms := testSeqNoOrderGen(t, m, ints)
|
|
||||||
|
|
||||||
// ms should be empty at this point
|
|
||||||
assertEmpty(ms, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that the Equal method on HashMaps works
|
|
||||||
func TestHashMapEqual(t *T) {
|
|
||||||
hm, hm2 := NewHashMap(), NewHashMap()
|
|
||||||
assertValue(hm.Equal(hm2), true, t)
|
|
||||||
assertValue(hm2.Equal(hm), true, t)
|
|
||||||
|
|
||||||
hm = NewHashMap(keyValV(1, "one"), keyValV(2, "two"))
|
|
||||||
assertValue(hm.Equal(hm2), false, t)
|
|
||||||
assertValue(hm2.Equal(hm), false, t)
|
|
||||||
|
|
||||||
hm2 = NewHashMap(keyValV(1, "one"))
|
|
||||||
assertValue(hm.Equal(hm2), false, t)
|
|
||||||
assertValue(hm2.Equal(hm), false, t)
|
|
||||||
|
|
||||||
hm2 = NewHashMap(keyValV(1, "one"), keyValV(2, "three?"))
|
|
||||||
assertValue(hm.Equal(hm2), false, t)
|
|
||||||
assertValue(hm2.Equal(hm), false, t)
|
|
||||||
|
|
||||||
hm2 = NewHashMap(keyValV(1, "one"), keyValV(2, "two"))
|
|
||||||
assertValue(hm.Equal(hm2), true, t)
|
|
||||||
assertValue(hm2.Equal(hm), true, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test getting values from a HashMap
|
|
||||||
func TestHashMapGet(t *T) {
|
|
||||||
kvs := []*KV{
|
|
||||||
keyValV(1, "one"),
|
|
||||||
keyValV(2, "two"),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Degenerate case
|
|
||||||
m := NewHashMap()
|
|
||||||
assertEmpty(m, t)
|
|
||||||
v, ok := m.Get(types.GoType{1})
|
|
||||||
assertValue(v, nil, t)
|
|
||||||
assertValue(ok, false, t)
|
|
||||||
|
|
||||||
m = NewHashMap(kvs...)
|
|
||||||
v, ok = m.Get(types.GoType{1})
|
|
||||||
assertSeqContentsHashMap(m, kvs, t)
|
|
||||||
assertValue(v, types.GoType{"one"}, t)
|
|
||||||
assertValue(ok, true, t)
|
|
||||||
|
|
||||||
v, ok = m.Get(types.GoType{3})
|
|
||||||
assertSeqContentsHashMap(m, kvs, t)
|
|
||||||
assertValue(v, nil, t)
|
|
||||||
assertValue(ok, false, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test setting values on a HashMap
|
|
||||||
func TestHashMapSet(t *T) {
|
|
||||||
|
|
||||||
// Set on empty
|
|
||||||
m := NewHashMap()
|
|
||||||
m1, ok := m.Set(types.GoType{1}, types.GoType{"one"})
|
|
||||||
assertEmpty(m, t)
|
|
||||||
assertSeqContentsHashMap(m1, []*KV{keyValV(1, "one")}, t)
|
|
||||||
assertValue(ok, true, t)
|
|
||||||
|
|
||||||
// Set on same key
|
|
||||||
m2, ok := m1.Set(types.GoType{1}, types.GoType{"wat"})
|
|
||||||
assertSeqContentsHashMap(m1, []*KV{keyValV(1, "one")}, t)
|
|
||||||
assertSeqContentsHashMap(m2, []*KV{keyValV(1, "wat")}, t)
|
|
||||||
assertValue(ok, false, t)
|
|
||||||
|
|
||||||
// Set on second new key
|
|
||||||
m3, ok := m2.Set(types.GoType{2}, types.GoType{"two"})
|
|
||||||
assertSeqContentsHashMap(m2, []*KV{keyValV(1, "wat")}, t)
|
|
||||||
assertSeqContentsHashMap(m3, []*KV{keyValV(1, "wat"), keyValV(2, "two")}, t)
|
|
||||||
assertValue(ok, true, t)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test deleting keys from sets
|
|
||||||
func TestHashMapDel(t *T) {
|
|
||||||
|
|
||||||
kvs := []*KV{
|
|
||||||
keyValV(1, "one"),
|
|
||||||
keyValV(2, "two"),
|
|
||||||
keyValV(3, "three"),
|
|
||||||
}
|
|
||||||
kvs1 := []*KV{
|
|
||||||
keyValV(2, "two"),
|
|
||||||
keyValV(3, "three"),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Degenerate case
|
|
||||||
m := NewHashMap()
|
|
||||||
m1, ok := m.Del(types.GoType{1})
|
|
||||||
assertEmpty(m, t)
|
|
||||||
assertEmpty(m1, t)
|
|
||||||
assertValue(ok, false, t)
|
|
||||||
|
|
||||||
// Delete actual key
|
|
||||||
m = NewHashMap(kvs...)
|
|
||||||
m1, ok = m.Del(types.GoType{1})
|
|
||||||
assertSeqContentsHashMap(m, kvs, t)
|
|
||||||
assertSeqContentsHashMap(m1, kvs1, t)
|
|
||||||
assertValue(ok, true, t)
|
|
||||||
|
|
||||||
// Delete it again!
|
|
||||||
m2, ok := m1.Del(types.GoType{1})
|
|
||||||
assertSeqContentsHashMap(m1, kvs1, t)
|
|
||||||
assertSeqContentsHashMap(m2, kvs1, t)
|
|
||||||
assertValue(ok, false, t)
|
|
||||||
|
|
||||||
}
|
|
418
seq/hashset.go
418
seq/hashset.go
@ -1,418 +0,0 @@
|
|||||||
package seq
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"hash/crc32"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is an implementation of a persistent tree, which will then be used as
|
|
||||||
// the basis for vectors, hash maps, and hash sets.
|
|
||||||
|
|
||||||
type Setable interface {
|
|
||||||
|
|
||||||
// Returns an integer for the value. For two equivalent values (as defined
|
|
||||||
// by ==) Hash(i) should always return the same number. For multiple values
|
|
||||||
// of i, Hash should return different values if possible.
|
|
||||||
Hash(uint32) uint32
|
|
||||||
|
|
||||||
// Given an arbitrary value found in a Set, returns whether or not the two
|
|
||||||
// are equal
|
|
||||||
Equal(types.Elem) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns an arbitrary integer for the given value/iteration tuple
|
|
||||||
func hash(v types.Elem, i uint32) uint32 {
|
|
||||||
switch vt := v.(type) {
|
|
||||||
|
|
||||||
case Setable:
|
|
||||||
return vt.Hash(i) % ARITY
|
|
||||||
|
|
||||||
case types.GoType:
|
|
||||||
switch gvt := vt.V.(type) {
|
|
||||||
case uint:
|
|
||||||
return uint32(gvt) % ARITY
|
|
||||||
case uint8:
|
|
||||||
return uint32(gvt) % ARITY
|
|
||||||
case uint32:
|
|
||||||
return uint32(gvt) % ARITY
|
|
||||||
case uint64:
|
|
||||||
return uint32(gvt) % ARITY
|
|
||||||
case int:
|
|
||||||
return uint32(gvt) % ARITY
|
|
||||||
case int8:
|
|
||||||
return uint32(gvt) % ARITY
|
|
||||||
case int16:
|
|
||||||
return uint32(gvt) % ARITY
|
|
||||||
case int32:
|
|
||||||
return uint32(gvt) % ARITY
|
|
||||||
case int64:
|
|
||||||
return uint32(gvt) % ARITY
|
|
||||||
case float32:
|
|
||||||
return uint32(gvt) % ARITY
|
|
||||||
case float64:
|
|
||||||
return uint32(gvt) % ARITY
|
|
||||||
|
|
||||||
case string:
|
|
||||||
return crc32.ChecksumIEEE([]byte(gvt)) % ARITY
|
|
||||||
|
|
||||||
case []byte:
|
|
||||||
return crc32.ChecksumIEEE(gvt) % ARITY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := fmt.Sprintf("%s not hashable", reflect.TypeOf(v))
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The number of children each node in Set (implemented as a hash tree) can have
|
|
||||||
const ARITY = 32
|
|
||||||
|
|
||||||
// A Set is an implementation of Seq in the form of a persistant hash-tree. All
|
|
||||||
// public operations on it return a new, immutable form of the modified
|
|
||||||
// variable, leaving the old one intact. Immutability is implemented through
|
|
||||||
// node sharing, so operations aren't actually copying the entire hash-tree
|
|
||||||
// everytime, only the nodes which change, making the implementation very
|
|
||||||
// efficient compared to just copying.
|
|
||||||
//
|
|
||||||
// Items in sets need to be hashable and comparable. This means they either need
|
|
||||||
// to be some real numeric type (int, float32, etc...), string, []byte, or
|
|
||||||
// implement the Setable interface.
|
|
||||||
type Set struct {
|
|
||||||
|
|
||||||
// The value being held
|
|
||||||
val types.Elem
|
|
||||||
|
|
||||||
// Whether or not the held value has been set yet. Needed because the value
|
|
||||||
// could be nil
|
|
||||||
full bool
|
|
||||||
|
|
||||||
// Slice of kids of this node. Could be an empty slice
|
|
||||||
kids []*Set
|
|
||||||
|
|
||||||
// Number of values in this Set.
|
|
||||||
size uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a new Set of the given elements (or no elements, for an empty set)
|
|
||||||
func NewSet(vals ...types.Elem) *Set {
|
|
||||||
if len(vals) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
set := new(Set)
|
|
||||||
for i := range vals {
|
|
||||||
set.setValDirty(vals[i], 0)
|
|
||||||
}
|
|
||||||
set.size = uint64(len(vals))
|
|
||||||
return set
|
|
||||||
}
|
|
||||||
|
|
||||||
// Methods marked as "dirty" operate on the node in place, and potentially
|
|
||||||
// change it or its children.
|
|
||||||
|
|
||||||
// Dirty. Tries to set the val on this Set node, or initialize the kids slice if
|
|
||||||
// it can't. Returns whether or not the value was set and whether or not it was
|
|
||||||
// already set.
|
|
||||||
func (set *Set) shallowTrySetOrInit(val types.Elem) (bool, bool) {
|
|
||||||
if !set.full {
|
|
||||||
set.val = val
|
|
||||||
set.full = true
|
|
||||||
return true, false
|
|
||||||
} else if set.val.Equal(val) {
|
|
||||||
set.val = val
|
|
||||||
set.full = true
|
|
||||||
return true, true
|
|
||||||
} else if set.kids == nil {
|
|
||||||
set.kids = make([]*Set, ARITY)
|
|
||||||
}
|
|
||||||
return false, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// dirty (obviously). Sets a value on this node in place. Only used during
|
|
||||||
// initialization.
|
|
||||||
func (set *Set) setValDirty(val types.Elem, i uint32) {
|
|
||||||
if ok, _ := set.shallowTrySetOrInit(val); ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h := hash(val, i)
|
|
||||||
if kid := set.kids[h]; kid != nil {
|
|
||||||
kid.setValDirty(val, i+1)
|
|
||||||
} else {
|
|
||||||
set.kids[h] = NewSet(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a copy of this set node, including allocating and copying the kids
|
|
||||||
// slice.
|
|
||||||
func (set *Set) clone() *Set {
|
|
||||||
var newkids []*Set
|
|
||||||
if set.kids != nil {
|
|
||||||
newkids = make([]*Set, ARITY)
|
|
||||||
copy(newkids, set.kids)
|
|
||||||
}
|
|
||||||
cs := &Set{
|
|
||||||
val: set.val,
|
|
||||||
full: set.full,
|
|
||||||
kids: newkids,
|
|
||||||
size: set.size,
|
|
||||||
}
|
|
||||||
return cs
|
|
||||||
}
|
|
||||||
|
|
||||||
// The actual implementation of SetVal, because we need to pass i down the stack
|
|
||||||
func (set *Set) internalSetVal(val types.Elem, i uint32) (*Set, bool) {
|
|
||||||
if set == nil {
|
|
||||||
return NewSet(val), true
|
|
||||||
}
|
|
||||||
cset := set.clone()
|
|
||||||
if ok, prev := cset.shallowTrySetOrInit(val); ok {
|
|
||||||
return cset, !prev
|
|
||||||
}
|
|
||||||
|
|
||||||
h := hash(val, i)
|
|
||||||
newkid, ok := cset.kids[h].internalSetVal(val, i+1)
|
|
||||||
cset.kids[h] = newkid
|
|
||||||
return cset, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a new Set with the given value added to it. Also returns whether or
|
|
||||||
// not this is the first time setting this value (false if it was already there
|
|
||||||
// and was overwritten). Completes in O(log(N)) time.
|
|
||||||
func (set *Set) SetVal(val types.Elem) (*Set, bool) {
|
|
||||||
nset, ok := set.internalSetVal(val, 0)
|
|
||||||
if ok {
|
|
||||||
nset.size++
|
|
||||||
}
|
|
||||||
return nset, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// The actual implementation of DelVal, because we need to pass i down the stack
|
|
||||||
func (set *Set) internalDelVal(val types.Elem, i uint32) (*Set, bool) {
|
|
||||||
if set == nil {
|
|
||||||
return nil, false
|
|
||||||
} else if set.full && set.val.Equal(val) {
|
|
||||||
cset := set.clone()
|
|
||||||
cset.val = nil
|
|
||||||
cset.full = false
|
|
||||||
return cset, true
|
|
||||||
} else if set.kids == nil {
|
|
||||||
return set, false
|
|
||||||
}
|
|
||||||
|
|
||||||
h := hash(val, i)
|
|
||||||
if newkid, ok := set.kids[h].internalDelVal(val, i+1); ok {
|
|
||||||
cset := set.clone()
|
|
||||||
cset.kids[h] = newkid
|
|
||||||
return cset, true
|
|
||||||
}
|
|
||||||
return set, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a new Set with the given value removed from it and whether or not the
|
|
||||||
// value was actually removed. Completes in O(log(N)) time.
|
|
||||||
func (set *Set) DelVal(val types.Elem) (*Set, bool) {
|
|
||||||
nset, ok := set.internalDelVal(val, 0)
|
|
||||||
if ok && nset != nil {
|
|
||||||
nset.size--
|
|
||||||
}
|
|
||||||
return nset, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// The actual implementation of GetVal, because we need to pass i down the stack
|
|
||||||
func (set *Set) internalGetVal(val types.Elem, i uint32) (types.Elem, bool) {
|
|
||||||
if set == nil {
|
|
||||||
return nil, false
|
|
||||||
} else if set.full && set.val.Equal(val) {
|
|
||||||
return set.val, true
|
|
||||||
} else if set.kids == nil {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
h := hash(val, i)
|
|
||||||
return set.kids[h].internalGetVal(val, i+1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a value from the Set, along with a boolean indiciating whether or
|
|
||||||
// not the value was found. Completes in O(log(N)) time.
|
|
||||||
func (set *Set) GetVal(val types.Elem) (types.Elem, bool) {
|
|
||||||
return set.internalGetVal(val, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actual implementation of FirstRest. Because we need it to return a *Set
|
|
||||||
// instead of Seq for one case.
|
|
||||||
func (set *Set) internalFirstRest() (types.Elem, *Set, bool) {
|
|
||||||
if set == nil {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
if set.kids != nil {
|
|
||||||
var el types.Elem
|
|
||||||
var rest *Set
|
|
||||||
var ok bool
|
|
||||||
for i := range set.kids {
|
|
||||||
if el, rest, ok = set.kids[i].internalFirstRest(); ok {
|
|
||||||
cset := set.clone()
|
|
||||||
cset.kids[i] = rest
|
|
||||||
return el, cset, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're not nil, but we don't have a value and no kids had values. We might
|
|
||||||
// as well be nil.
|
|
||||||
if !set.full {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return set.val, nil, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of FirstRest for Seq interface. Completes in O(log(N)) time.
|
|
||||||
func (set *Set) FirstRest() (types.Elem, Seq, bool) {
|
|
||||||
el, restSet, ok := set.internalFirstRest()
|
|
||||||
if ok && restSet != nil {
|
|
||||||
restSet.size--
|
|
||||||
}
|
|
||||||
return el, Seq(restSet), ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of Equal for types.Elem interface. Completes in O(Nlog(M))
|
|
||||||
// time if e is another Set, where M is the size of the given Set
|
|
||||||
func (set *Set) Equal(e types.Elem) bool {
|
|
||||||
set2, ok := e.(*Set)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var el types.Elem
|
|
||||||
s := Seq(set)
|
|
||||||
size := uint64(0)
|
|
||||||
|
|
||||||
for {
|
|
||||||
el, s, ok = s.FirstRest()
|
|
||||||
if !ok {
|
|
||||||
return size == set2.Size()
|
|
||||||
}
|
|
||||||
size++
|
|
||||||
|
|
||||||
_, ok = set2.GetVal(el)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of String for Stringer interface
|
|
||||||
func (set *Set) String() string {
|
|
||||||
return ToString(set, "#{", "}#")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the number of elements in the Set. Completes in O(1) time.
|
|
||||||
func (set *Set) Size() uint64 {
|
|
||||||
if set == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return set.size
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a Set with all of the elements of the original Set along with
|
|
||||||
// everything in the given Seq. If an element is present in both the Set and the
|
|
||||||
// Seq, the element in the Seq overwrites. Completes in O(M*log(N)), with M
|
|
||||||
// being the number of elements in the Seq and N the number of elements in the
|
|
||||||
// Set
|
|
||||||
func (set *Set) Union(s Seq) *Set {
|
|
||||||
if set == nil {
|
|
||||||
return ToSet(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
cset := set.clone()
|
|
||||||
var el types.Elem
|
|
||||||
var ok bool
|
|
||||||
for {
|
|
||||||
if el, s, ok = s.FirstRest(); !ok {
|
|
||||||
return cset
|
|
||||||
} else if cset, ok = cset.SetVal(el); ok {
|
|
||||||
cset.size++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a Set with all of the elements in Seq that are also in Set. Completes
|
|
||||||
// in O(M*log(N)), with M being the number of elements in the Seq and N the
|
|
||||||
// number of elements in the Set
|
|
||||||
func (set *Set) Intersection(s Seq) *Set {
|
|
||||||
if set == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
iset := NewSet()
|
|
||||||
var el types.Elem
|
|
||||||
var ok bool
|
|
||||||
for {
|
|
||||||
if el, s, ok = s.FirstRest(); !ok {
|
|
||||||
return iset
|
|
||||||
} else if _, ok = set.GetVal(el); ok {
|
|
||||||
iset, _ = iset.SetVal(el)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a Set of all elements in the original Set that aren't in the Seq.
|
|
||||||
// Completes in O(M*log(N)), with M being the number of elements in the Seq and
|
|
||||||
// N the number of elements in the Set
|
|
||||||
func (set *Set) Difference(s Seq) *Set {
|
|
||||||
if set == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cset := set.clone()
|
|
||||||
var el types.Elem
|
|
||||||
var ok bool
|
|
||||||
for {
|
|
||||||
if el, s, ok = s.FirstRest(); !ok {
|
|
||||||
return cset
|
|
||||||
} else {
|
|
||||||
cset, _ = cset.DelVal(el)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a Set of all elements that are either in the original Set or the
|
|
||||||
// given Seq, but not in both. Completes in O(M*log(N)), with M being the number
|
|
||||||
// of elements in the Seq and N the number of elements in the Set.
|
|
||||||
func (set *Set) SymDifference(s Seq) *Set {
|
|
||||||
if set == nil {
|
|
||||||
return ToSet(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
cset := set.clone()
|
|
||||||
var cset2 *Set
|
|
||||||
var el types.Elem
|
|
||||||
var ok bool
|
|
||||||
for {
|
|
||||||
if el, s, ok = s.FirstRest(); !ok {
|
|
||||||
return cset
|
|
||||||
} else if cset2, ok = cset.DelVal(el); ok {
|
|
||||||
cset = cset2
|
|
||||||
} else {
|
|
||||||
cset, _ = cset.SetVal(el)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the elements in the Seq as a set. In general this completes in
|
|
||||||
// O(N*log(N)) time (I think...). If the given Seq is already a Set it will
|
|
||||||
// complete in O(1) time. If it is a HashMap it will complete in O(1) time, and
|
|
||||||
// the resultant Set will be comprised of all KVs
|
|
||||||
func ToSet(s Seq) *Set {
|
|
||||||
if set, ok := s.(*Set); ok {
|
|
||||||
return set
|
|
||||||
} else if hm, ok := s.(*HashMap); ok {
|
|
||||||
return hm.set
|
|
||||||
}
|
|
||||||
vals := ToSlice(s)
|
|
||||||
return NewSet(vals...)
|
|
||||||
}
|
|
@ -1,273 +0,0 @@
|
|||||||
package seq
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "testing"
|
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test that HashSet implements types.Elem (compile-time check)
|
|
||||||
func TestSetElem(t *T) {
|
|
||||||
_ = types.Elem(NewSet())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test creating a Set and calling the Seq interface methods on it
|
|
||||||
func TestSetSeq(t *T) {
|
|
||||||
ints := elemSliceV(nil, 1, "a", 5.0)
|
|
||||||
|
|
||||||
// Testing creation and Seq interface methods
|
|
||||||
s := NewSet(ints...)
|
|
||||||
ss := testSeqNoOrderGen(t, s, ints)
|
|
||||||
|
|
||||||
// ss should be empty at this point
|
|
||||||
s = ToSet(ss)
|
|
||||||
var nilpointer *Set
|
|
||||||
assertEmpty(s, t)
|
|
||||||
assertValue(s, nilpointer, t)
|
|
||||||
assertValue(len(ToSlice(s)), 0, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that the Equal method on Sets works
|
|
||||||
func TestSetEqual(t *T) {
|
|
||||||
s, s2 := NewSet(), NewSet()
|
|
||||||
assertValue(s.Equal(s2), true, t)
|
|
||||||
assertValue(s2.Equal(s), true, t)
|
|
||||||
|
|
||||||
s = NewSet(elemSliceV(0, 1, 2)...)
|
|
||||||
assertValue(s.Equal(s2), false, t)
|
|
||||||
assertValue(s2.Equal(s), false, t)
|
|
||||||
|
|
||||||
s2 = NewSet(elemSliceV(0, 1)...)
|
|
||||||
assertValue(s.Equal(s2), false, t)
|
|
||||||
assertValue(s2.Equal(s), false, t)
|
|
||||||
|
|
||||||
s2 = NewSet(elemSliceV(0, 1, 3)...)
|
|
||||||
assertValue(s.Equal(s2), false, t)
|
|
||||||
assertValue(s2.Equal(s), false, t)
|
|
||||||
|
|
||||||
s2 = NewSet(elemSliceV(0, 1, 2)...)
|
|
||||||
assertValue(s.Equal(s2), true, t)
|
|
||||||
assertValue(s2.Equal(s), true, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test setting a value on a Set
|
|
||||||
func TestSetVal(t *T) {
|
|
||||||
ints := elemSliceV(0, 1, 2, 3, 4)
|
|
||||||
ints1 := elemSliceV(0, 1, 2, 3, 4, 5)
|
|
||||||
|
|
||||||
// Degenerate case
|
|
||||||
s := NewSet()
|
|
||||||
assertEmpty(s, t)
|
|
||||||
s, ok := s.SetVal(types.GoType{0})
|
|
||||||
assertSeqContentsSet(s, elemSliceV(0), t)
|
|
||||||
assertValue(ok, true, t)
|
|
||||||
|
|
||||||
s = NewSet(ints...)
|
|
||||||
s1, ok := s.SetVal(types.GoType{5})
|
|
||||||
assertSeqContentsSet(s, ints, t)
|
|
||||||
assertSeqContentsSet(s1, ints1, t)
|
|
||||||
assertValue(ok, true, t)
|
|
||||||
|
|
||||||
s2, ok := s1.SetVal(types.GoType{5})
|
|
||||||
assertSeqContentsSet(s1, ints1, t)
|
|
||||||
assertSeqContentsSet(s2, ints1, t)
|
|
||||||
assertValue(ok, false, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test deleting a value from a Set
|
|
||||||
func TestDelVal(t *T) {
|
|
||||||
ints := elemSliceV(0, 1, 2, 3, 4)
|
|
||||||
ints1 := elemSliceV(0, 1, 2, 3)
|
|
||||||
ints2 := elemSliceV(1, 2, 3, 4)
|
|
||||||
ints3 := elemSliceV(1, 2, 3, 4, 5)
|
|
||||||
|
|
||||||
// Degenerate case
|
|
||||||
s := NewSet()
|
|
||||||
assertEmpty(s, t)
|
|
||||||
s, ok := s.DelVal(types.GoType{0})
|
|
||||||
assertEmpty(s, t)
|
|
||||||
assertValue(ok, false, t)
|
|
||||||
|
|
||||||
s = NewSet(ints...)
|
|
||||||
s1, ok := s.DelVal(types.GoType{4})
|
|
||||||
assertSeqContentsSet(s, ints, t)
|
|
||||||
assertSeqContentsSet(s1, ints1, t)
|
|
||||||
assertValue(ok, true, t)
|
|
||||||
|
|
||||||
s1, ok = s1.DelVal(types.GoType{4})
|
|
||||||
assertSeqContentsSet(s1, ints1, t)
|
|
||||||
assertValue(ok, false, t)
|
|
||||||
|
|
||||||
// 0 is the value on the root node of s, which is kind of a special case. We
|
|
||||||
// want to test deleting it and setting a new value (which should get put on
|
|
||||||
// the root node).
|
|
||||||
s2, ok := s.DelVal(types.GoType{0})
|
|
||||||
assertSeqContentsSet(s, ints, t)
|
|
||||||
assertSeqContentsSet(s2, ints2, t)
|
|
||||||
assertValue(ok, true, t)
|
|
||||||
|
|
||||||
s2, ok = s2.DelVal(types.GoType{0})
|
|
||||||
assertSeqContentsSet(s2, ints2, t)
|
|
||||||
assertValue(ok, false, t)
|
|
||||||
|
|
||||||
s3, ok := s2.SetVal(types.GoType{5})
|
|
||||||
assertSeqContentsSet(s2, ints2, t)
|
|
||||||
assertSeqContentsSet(s3, ints3, t)
|
|
||||||
assertValue(ok, true, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test getting values from a Set
|
|
||||||
func GetVal(t *T) {
|
|
||||||
//Degenerate case
|
|
||||||
s := NewSet()
|
|
||||||
v, ok := s.GetVal(types.GoType{1})
|
|
||||||
assertValue(v, nil, t)
|
|
||||||
assertValue(ok, false, t)
|
|
||||||
|
|
||||||
s = NewSet(elemSliceV(0, 1, 2, 3, 4)...)
|
|
||||||
v, ok = s.GetVal(types.GoType{1})
|
|
||||||
assertValue(v, 1, t)
|
|
||||||
assertValue(ok, true, t)
|
|
||||||
|
|
||||||
// After delete
|
|
||||||
s, _ = s.DelVal(types.GoType{1})
|
|
||||||
v, ok = s.GetVal(types.GoType{1})
|
|
||||||
assertValue(v, nil, t)
|
|
||||||
assertValue(ok, false, t)
|
|
||||||
|
|
||||||
// After set
|
|
||||||
s, _ = s.SetVal(types.GoType{1})
|
|
||||||
v, ok = s.GetVal(types.GoType{1})
|
|
||||||
assertValue(v, 1, t)
|
|
||||||
assertValue(ok, true, t)
|
|
||||||
|
|
||||||
// After delete root node
|
|
||||||
s, _ = s.DelVal(types.GoType{0})
|
|
||||||
v, ok = s.GetVal(types.GoType{0})
|
|
||||||
assertValue(v, nil, t)
|
|
||||||
assertValue(ok, false, t)
|
|
||||||
|
|
||||||
// After set root node
|
|
||||||
s, _ = s.SetVal(types.GoType{5})
|
|
||||||
v, ok = s.GetVal(types.GoType{5})
|
|
||||||
assertValue(v, 5, t)
|
|
||||||
assertValue(ok, true, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that Size functions properly for all cases
|
|
||||||
func TestSetSize(t *T) {
|
|
||||||
// Degenerate case
|
|
||||||
s := NewSet()
|
|
||||||
assertValue(s.Size(), uint64(0), t)
|
|
||||||
|
|
||||||
// Initialization case
|
|
||||||
s = NewSet(elemSliceV(0, 1, 2)...)
|
|
||||||
assertValue(s.Size(), uint64(3), t)
|
|
||||||
|
|
||||||
// Setting (both value not in and a value already in)
|
|
||||||
s, _ = s.SetVal(types.GoType{3})
|
|
||||||
assertValue(s.Size(), uint64(4), t)
|
|
||||||
s, _ = s.SetVal(types.GoType{3})
|
|
||||||
assertValue(s.Size(), uint64(4), t)
|
|
||||||
|
|
||||||
// Deleting (both value already in and a value not in)
|
|
||||||
s, _ = s.DelVal(types.GoType{3})
|
|
||||||
assertValue(s.Size(), uint64(3), t)
|
|
||||||
s, _ = s.DelVal(types.GoType{3})
|
|
||||||
assertValue(s.Size(), uint64(3), t)
|
|
||||||
|
|
||||||
// Deleting and setting the root node
|
|
||||||
s, _ = s.DelVal(types.GoType{0})
|
|
||||||
assertValue(s.Size(), uint64(2), t)
|
|
||||||
s, _ = s.SetVal(types.GoType{5})
|
|
||||||
assertValue(s.Size(), uint64(3), t)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that Union functions properly
|
|
||||||
func TestUnion(t *T) {
|
|
||||||
// Degenerate case
|
|
||||||
empty := NewSet()
|
|
||||||
assertEmpty(empty.Union(empty), t)
|
|
||||||
|
|
||||||
ints1 := elemSliceV(0, 1, 2)
|
|
||||||
ints2 := elemSliceV(3, 4, 5)
|
|
||||||
intsu := append(ints1, ints2...)
|
|
||||||
s1 := NewSet(ints1...)
|
|
||||||
s2 := NewSet(ints2...)
|
|
||||||
|
|
||||||
assertSeqContentsSet(s1.Union(empty), ints1, t)
|
|
||||||
assertSeqContentsSet(empty.Union(s1), ints1, t)
|
|
||||||
|
|
||||||
su := s1.Union(s2)
|
|
||||||
assertSeqContentsSet(s1, ints1, t)
|
|
||||||
assertSeqContentsSet(s2, ints2, t)
|
|
||||||
assertSeqContentsSet(su, intsu, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that Intersection functions properly
|
|
||||||
func TestIntersection(t *T) {
|
|
||||||
// Degenerate case
|
|
||||||
empty := NewSet()
|
|
||||||
assertEmpty(empty.Intersection(empty), t)
|
|
||||||
|
|
||||||
ints1 := elemSliceV(0, 1, 2)
|
|
||||||
ints2 := elemSliceV(1, 2, 3)
|
|
||||||
ints3 := elemSliceV(4, 5, 6)
|
|
||||||
intsi := elemSliceV(1, 2)
|
|
||||||
s1 := NewSet(ints1...)
|
|
||||||
s2 := NewSet(ints2...)
|
|
||||||
s3 := NewSet(ints3...)
|
|
||||||
|
|
||||||
assertEmpty(s1.Intersection(empty), t)
|
|
||||||
assertEmpty(empty.Intersection(s1), t)
|
|
||||||
|
|
||||||
si := s1.Intersection(s2)
|
|
||||||
assertEmpty(s1.Intersection(s3), t)
|
|
||||||
assertSeqContentsSet(s1, ints1, t)
|
|
||||||
assertSeqContentsSet(s2, ints2, t)
|
|
||||||
assertSeqContentsSet(s3, ints3, t)
|
|
||||||
assertSeqContentsSet(si, intsi, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that Difference functions properly
|
|
||||||
func TestDifference(t *T) {
|
|
||||||
// Degenerate case
|
|
||||||
empty := NewSet()
|
|
||||||
assertEmpty(empty.Difference(empty), t)
|
|
||||||
|
|
||||||
ints1 := elemSliceV(0, 1, 2, 3)
|
|
||||||
ints2 := elemSliceV(2, 3, 4)
|
|
||||||
intsd := elemSliceV(0, 1)
|
|
||||||
s1 := NewSet(ints1...)
|
|
||||||
s2 := NewSet(ints2...)
|
|
||||||
|
|
||||||
assertSeqContentsSet(s1.Difference(empty), ints1, t)
|
|
||||||
assertEmpty(empty.Difference(s1), t)
|
|
||||||
|
|
||||||
sd := s1.Difference(s2)
|
|
||||||
assertSeqContentsSet(s1, ints1, t)
|
|
||||||
assertSeqContentsSet(s2, ints2, t)
|
|
||||||
assertSeqContentsSet(sd, intsd, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that SymDifference functions properly
|
|
||||||
func TestSymDifference(t *T) {
|
|
||||||
// Degenerate case
|
|
||||||
empty := NewSet()
|
|
||||||
assertEmpty(empty.SymDifference(empty), t)
|
|
||||||
|
|
||||||
ints1 := elemSliceV(0, 1, 2, 3)
|
|
||||||
ints2 := elemSliceV(2, 3, 4)
|
|
||||||
intsd := elemSliceV(0, 1, 4)
|
|
||||||
s1 := NewSet(ints1...)
|
|
||||||
s2 := NewSet(ints2...)
|
|
||||||
|
|
||||||
assertSeqContentsSet(s1.SymDifference(empty), ints1, t)
|
|
||||||
assertSeqContentsSet(empty.SymDifference(s1), ints1, t)
|
|
||||||
|
|
||||||
sd := s1.SymDifference(s2)
|
|
||||||
assertSeqContentsSet(s1, ints1, t)
|
|
||||||
assertSeqContentsSet(s2, ints2, t)
|
|
||||||
assertSeqContentsSet(sd, intsd, t)
|
|
||||||
}
|
|
160
seq/lazy.go
160
seq/lazy.go
@ -1,160 +0,0 @@
|
|||||||
package seq
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Lazy is an implementation of a Seq which only actually evaluates its
|
|
||||||
// contents as those contents become needed. Lazys can be chained together, so
|
|
||||||
// if you have three steps in a pipeline there aren't two intermediate Seqs
|
|
||||||
// created, only the final resulting one. Lazys are also thread-safe, so
|
|
||||||
// multiple routines can interact with the same Lazy pointer at the same time
|
|
||||||
// but the contents will only be evalutated once.
|
|
||||||
type Lazy struct {
|
|
||||||
this types.Elem
|
|
||||||
next *Lazy
|
|
||||||
ok bool
|
|
||||||
ch chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Given a Thunk, returns a Lazy around that Thunk.
|
|
||||||
func NewLazy(t Thunk) *Lazy {
|
|
||||||
l := &Lazy{ch: make(chan struct{})}
|
|
||||||
go func() {
|
|
||||||
l.ch <- struct{}{}
|
|
||||||
el, next, ok := t()
|
|
||||||
l.this = el
|
|
||||||
l.next = NewLazy(next)
|
|
||||||
l.ok = ok
|
|
||||||
close(l.ch)
|
|
||||||
}()
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of FirstRest for Seq interface. Completes in O(1) time.
|
|
||||||
func (l *Lazy) FirstRest() (types.Elem, Seq, bool) {
|
|
||||||
if l == nil {
|
|
||||||
return nil, l, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reading from the channel tells the Lazy to populate the data and prepare
|
|
||||||
// the next item in the seq, it closes the channel when it's done that.
|
|
||||||
if _, ok := <-l.ch; ok {
|
|
||||||
<-l.ch
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.ok {
|
|
||||||
return l.this, l.next, true
|
|
||||||
} else {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of Equal for types.Elem interface. Treats a List as another
|
|
||||||
// Lazy. Completes in O(N) time if e is another List or List.
|
|
||||||
func (l *Lazy) Equal(e types.Elem) bool {
|
|
||||||
var ls2 *List
|
|
||||||
if l2, ok := e.(*Lazy); ok {
|
|
||||||
ls2 = ToList(l2)
|
|
||||||
} else if ls2, ok = e.(*List); ok {
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
ls := ToList(l)
|
|
||||||
return ls.Equal(ls2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of String for Stringer
|
|
||||||
func (l *Lazy) String() string {
|
|
||||||
return ToString(l, "<<", ">>")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thunks are the building blocks a Lazy. A Thunk returns an element, another
|
|
||||||
// Thunk, and a boolean representing if the call yielded any results or if it
|
|
||||||
// was actually empty (true indicates it yielded results).
|
|
||||||
type Thunk func() (types.Elem, Thunk, bool)
|
|
||||||
|
|
||||||
func mapThunk(fn func(types.Elem) types.Elem, s Seq) Thunk {
|
|
||||||
return func() (types.Elem, Thunk, bool) {
|
|
||||||
el, ns, ok := s.FirstRest()
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return fn(el), mapThunk(fn, ns), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lazy implementation of Map
|
|
||||||
func LMap(fn func(types.Elem) types.Elem, s Seq) Seq {
|
|
||||||
return NewLazy(mapThunk(fn, s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterThunk(fn func(types.Elem) bool, s Seq) Thunk {
|
|
||||||
return func() (types.Elem, Thunk, bool) {
|
|
||||||
for {
|
|
||||||
el, ns, ok := s.FirstRest()
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
if keep := fn(el); keep {
|
|
||||||
return el, filterThunk(fn, ns), true
|
|
||||||
} else {
|
|
||||||
s = ns
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lazy implementation of Filter
|
|
||||||
func LFilter(fn func(types.Elem) bool, s Seq) Seq {
|
|
||||||
return NewLazy(filterThunk(fn, s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func takeThunk(n uint64, s Seq) Thunk {
|
|
||||||
return func() (types.Elem, Thunk, bool) {
|
|
||||||
el, ns, ok := s.FirstRest()
|
|
||||||
if !ok || n == 0 {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
return el, takeThunk(n-1, ns), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lazy implementation of Take
|
|
||||||
func LTake(n uint64, s Seq) Seq {
|
|
||||||
return NewLazy(takeThunk(n, s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func takeWhileThunk(fn func(types.Elem) bool, s Seq) Thunk {
|
|
||||||
return func() (types.Elem, Thunk, bool) {
|
|
||||||
el, ns, ok := s.FirstRest()
|
|
||||||
if !ok || !fn(el) {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
return el, takeWhileThunk(fn, ns), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lazy implementation of TakeWhile
|
|
||||||
func LTakeWhile(fn func(types.Elem) bool, s Seq) Seq {
|
|
||||||
return NewLazy(takeWhileThunk(fn, s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func toLazyThunk(s Seq) Thunk {
|
|
||||||
return func() (types.Elem, Thunk, bool) {
|
|
||||||
el, ns, ok := s.FirstRest()
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
return el, toLazyThunk(ns), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the Seq as a Lazy. Pointless for linked-lists, but possibly useful
|
|
||||||
// for other implementations where FirstRest might be costly and the same Seq
|
|
||||||
// needs to be iterated over many times.
|
|
||||||
func ToLazy(s Seq) *Lazy {
|
|
||||||
return NewLazy(toLazyThunk(s))
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package seq
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test that Lazy implements types.Elem (compile-time check)
|
|
||||||
func TestLazyElem(t *T) {
|
|
||||||
_ = types.Elem(NewLazy(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test lazy operation and thread-safety
|
|
||||||
func TestLazyBasic(t *T) {
|
|
||||||
ch := make(chan types.GoType)
|
|
||||||
mapfn := func(el types.Elem) types.Elem {
|
|
||||||
i := el.(types.GoType)
|
|
||||||
ch <- i
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
intl := elemSliceV(0, 1, 2, 3, 4)
|
|
||||||
l := NewList(intl...)
|
|
||||||
ml := LMap(mapfn, l)
|
|
||||||
|
|
||||||
// ml is a lazy list of intl, which will write to ch the first time any of
|
|
||||||
// the elements are read. This for loop ensures ml is thread-safe
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
go func() {
|
|
||||||
mlintl := ToSlice(ml)
|
|
||||||
if !intSlicesEq(mlintl, intl) {
|
|
||||||
panic("contents not right")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// This loop and subsequent close ensure that ml only ever "creates" each
|
|
||||||
// element once
|
|
||||||
for _, el := range intl {
|
|
||||||
select {
|
|
||||||
case elch := <-ch:
|
|
||||||
assertValue(el, elch, t)
|
|
||||||
case <-time.After(1 * time.Millisecond):
|
|
||||||
t.Fatalf("Took too long reading result")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that arbitrary Seqs can turn into Lazy
|
|
||||||
func TestToLazy(t *T) {
|
|
||||||
intl := elemSliceV(0, 1, 2, 3, 4)
|
|
||||||
l := NewList(intl...)
|
|
||||||
ll := ToLazy(l)
|
|
||||||
assertSeqContents(ll, intl, t)
|
|
||||||
}
|
|
172
seq/list.go
172
seq/list.go
@ -1,172 +0,0 @@
|
|||||||
package seq
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A List is an implementation of Seq in the form of a single-linked-list, and
|
|
||||||
// is used as the underlying structure for Seqs for most methods that return a
|
|
||||||
// Seq. It is probably the most efficient and simplest of the implementations.
|
|
||||||
// Even though, conceptually, all Seq operations return a new Seq, the old Seq
|
|
||||||
// can actually share nodes with the new Seq (if both are Lists), thereby saving
|
|
||||||
// memory and copies.
|
|
||||||
type List struct {
|
|
||||||
el types.Elem
|
|
||||||
next *List
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a new List comprised of the given elements (or no elements, for an
|
|
||||||
// empty list)
|
|
||||||
func NewList(els ...types.Elem) *List {
|
|
||||||
elsl := len(els)
|
|
||||||
if elsl == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var cur *List
|
|
||||||
for i := 0; i < elsl; i++ {
|
|
||||||
cur = &List{els[elsl-i-1], cur}
|
|
||||||
}
|
|
||||||
return cur
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of FirstRest for Seq interface. Completes in O(1) time.
|
|
||||||
func (l *List) FirstRest() (types.Elem, Seq, bool) {
|
|
||||||
if l == nil {
|
|
||||||
return nil, l, false
|
|
||||||
} else {
|
|
||||||
return l.el, l.next, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of Equal for types.Elem interface. Completes in O(N) time if e
|
|
||||||
// is another List.
|
|
||||||
func (l *List) Equal(e types.Elem) bool {
|
|
||||||
l2, ok := e.(*List)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var el, el2 types.Elem
|
|
||||||
var ok2 bool
|
|
||||||
|
|
||||||
s, s2 := Seq(l), Seq(l2)
|
|
||||||
|
|
||||||
for {
|
|
||||||
el, s, ok = s.FirstRest()
|
|
||||||
el2, s2, ok2 = s2.FirstRest()
|
|
||||||
|
|
||||||
if !ok && !ok2 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ok != ok2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !el.Equal(el2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implementation of String for Stringer interface.
|
|
||||||
func (l *List) String() string {
|
|
||||||
return ToString(l, "(", ")")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepends the given element to the front of the list, returning a copy of the
|
|
||||||
// new list. Completes in O(1) time.
|
|
||||||
func (l *List) Prepend(el types.Elem) *List {
|
|
||||||
return &List{el, l}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepends the argument Seq to the beginning of the callee List, returning a
|
|
||||||
// copy of the new List. Completes in O(N) time, N being the length of the
|
|
||||||
// argument Seq
|
|
||||||
func (l *List) PrependSeq(s Seq) *List {
|
|
||||||
var first, cur, prev *List
|
|
||||||
var el types.Elem
|
|
||||||
var ok bool
|
|
||||||
for {
|
|
||||||
el, s, ok = s.FirstRest()
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
cur = &List{el, nil}
|
|
||||||
if first == nil {
|
|
||||||
first = cur
|
|
||||||
}
|
|
||||||
if prev != nil {
|
|
||||||
prev.next = cur
|
|
||||||
}
|
|
||||||
prev = cur
|
|
||||||
}
|
|
||||||
|
|
||||||
// prev will be nil if s is empty
|
|
||||||
if prev == nil {
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
prev.next = l
|
|
||||||
return first
|
|
||||||
}
|
|
||||||
|
|
||||||
// Appends the given element to the end of the List, returning a copy of the new
|
|
||||||
// List. While most methods on List don't actually copy much data, this one
|
|
||||||
// copies the entire list. Completes in O(N) time.
|
|
||||||
func (l *List) Append(el types.Elem) *List {
|
|
||||||
var first, cur, prev *List
|
|
||||||
for l != nil {
|
|
||||||
cur = &List{l.el, nil}
|
|
||||||
if first == nil {
|
|
||||||
first = cur
|
|
||||||
}
|
|
||||||
if prev != nil {
|
|
||||||
prev.next = cur
|
|
||||||
}
|
|
||||||
prev = cur
|
|
||||||
l = l.next
|
|
||||||
}
|
|
||||||
final := &List{el, nil}
|
|
||||||
if prev == nil {
|
|
||||||
return final
|
|
||||||
}
|
|
||||||
prev.next = final
|
|
||||||
return first
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the nth index element (starting at 0), with bool being false if i is
|
|
||||||
// out of bounds. Completes in O(N) time.
|
|
||||||
func (l *List) Nth(n uint64) (types.Elem, bool) {
|
|
||||||
var el types.Elem
|
|
||||||
var ok bool
|
|
||||||
s := Seq(l)
|
|
||||||
for i := uint64(0); ; i++ {
|
|
||||||
el, s, ok = s.FirstRest()
|
|
||||||
if !ok {
|
|
||||||
return nil, false
|
|
||||||
} else if i == n {
|
|
||||||
return el, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the elements in the Seq as a List. Has similar properties as
|
|
||||||
// ToSlice. In general this completes in O(N) time. If the given Seq is already
|
|
||||||
// a List it will complete in O(1) time.
|
|
||||||
func ToList(s Seq) *List {
|
|
||||||
var ok bool
|
|
||||||
var l *List
|
|
||||||
if l, ok = s.(*List); ok {
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
var el types.Elem
|
|
||||||
for ret := NewList(); ; {
|
|
||||||
if el, s, ok = s.FirstRest(); ok {
|
|
||||||
ret = ret.Prepend(el)
|
|
||||||
} else {
|
|
||||||
return Reverse(ret).(*List)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
184
seq/list_test.go
184
seq/list_test.go
@ -1,184 +0,0 @@
|
|||||||
package seq
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "testing"
|
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test that List implements types.Elem (compile-time check)
|
|
||||||
func TestListElem(t *T) {
|
|
||||||
_ = types.Elem(NewList())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Asserts that the given list is properly formed and has all of its size fields
|
|
||||||
// filled in correctly
|
|
||||||
func assertSaneList(l *List, t *T) {
|
|
||||||
if Size(l) == 0 {
|
|
||||||
var nilpointer *List
|
|
||||||
assertValue(l, nilpointer, t)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
size := Size(l)
|
|
||||||
assertValue(Size(l.next), size-1, t)
|
|
||||||
assertSaneList(l.next, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test creating a list and calling the Seq interface methods on it
|
|
||||||
func TestListSeq(t *T) {
|
|
||||||
ints := elemSliceV(1, "a", 5.0)
|
|
||||||
|
|
||||||
// Testing creation and Seq interface methods
|
|
||||||
l := NewList(ints...)
|
|
||||||
sl := testSeqGen(t, l, ints)
|
|
||||||
|
|
||||||
// sl should be empty at this point
|
|
||||||
l = ToList(sl)
|
|
||||||
var nilpointer *List
|
|
||||||
assertEmpty(l, t)
|
|
||||||
assertValue(l, nilpointer, t)
|
|
||||||
assertValue(len(ToSlice(l)), 0, t)
|
|
||||||
|
|
||||||
// Testing creation of empty List.
|
|
||||||
emptyl := NewList()
|
|
||||||
assertValue(emptyl, nilpointer, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that the Equal method on Lists works
|
|
||||||
func TestListEqual(t *T) {
|
|
||||||
l, l2 := NewList(), NewList()
|
|
||||||
assertValue(l.Equal(l2), true, t)
|
|
||||||
assertValue(l2.Equal(l), true, t)
|
|
||||||
|
|
||||||
l2 = NewList(elemSliceV(1, 2, 3)...)
|
|
||||||
assertValue(l.Equal(l2), false, t)
|
|
||||||
assertValue(l2.Equal(l), false, t)
|
|
||||||
|
|
||||||
l = NewList(elemSliceV(1, 2, 3, 4)...)
|
|
||||||
assertValue(l.Equal(l2), false, t)
|
|
||||||
assertValue(l2.Equal(l), false, t)
|
|
||||||
|
|
||||||
l2 = NewList(elemSliceV(1, 2, 3, 4)...)
|
|
||||||
assertValue(l.Equal(l2), true, t)
|
|
||||||
assertValue(l2.Equal(l), true, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the string representation of a List
|
|
||||||
func TestStringSeq(t *T) {
|
|
||||||
l := NewList(elemSliceV(0, 1, 2, 3)...)
|
|
||||||
assertValue(l.String(), "( 0 1 2 3 )", t)
|
|
||||||
|
|
||||||
l = NewList(elemSliceV(
|
|
||||||
0, 1, 2,
|
|
||||||
NewList(elemSliceV(3, 4)...),
|
|
||||||
5,
|
|
||||||
NewList(elemSliceV(6, 7, 8)...))...)
|
|
||||||
assertValue(l.String(), "( 0 1 2 ( 3 4 ) 5 ( 6 7 8 ) )", t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test prepending an element to the beginning of a list
|
|
||||||
func TestPrepend(t *T) {
|
|
||||||
// Normal case
|
|
||||||
intl := elemSliceV(3, 2, 1, 0)
|
|
||||||
l := NewList(intl...)
|
|
||||||
nl := l.Prepend(types.GoType{4})
|
|
||||||
assertSaneList(l, t)
|
|
||||||
assertSaneList(nl, t)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertSeqContents(nl, elemSliceV(4, 3, 2, 1, 0), t)
|
|
||||||
|
|
||||||
// Degenerate case
|
|
||||||
l = NewList()
|
|
||||||
nl = l.Prepend(types.GoType{0})
|
|
||||||
assertEmpty(l, t)
|
|
||||||
assertSaneList(nl, t)
|
|
||||||
assertSeqContents(nl, elemSliceV(0), t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test prepending a Seq to the beginning of a list
|
|
||||||
func TestPrependSeq(t *T) {
|
|
||||||
//Normal case
|
|
||||||
intl1 := elemSliceV(3, 4)
|
|
||||||
intl2 := elemSliceV(0, 1, 2)
|
|
||||||
l1 := NewList(intl1...)
|
|
||||||
l2 := NewList(intl2...)
|
|
||||||
nl := l1.PrependSeq(l2)
|
|
||||||
assertSaneList(l1, t)
|
|
||||||
assertSaneList(l2, t)
|
|
||||||
assertSaneList(nl, t)
|
|
||||||
assertSeqContents(l1, intl1, t)
|
|
||||||
assertSeqContents(l2, intl2, t)
|
|
||||||
assertSeqContents(nl, elemSliceV(0, 1, 2, 3, 4), t)
|
|
||||||
|
|
||||||
// Degenerate cases
|
|
||||||
blank1 := NewList()
|
|
||||||
blank2 := NewList()
|
|
||||||
nl = blank1.PrependSeq(blank2)
|
|
||||||
assertEmpty(blank1, t)
|
|
||||||
assertEmpty(blank2, t)
|
|
||||||
assertEmpty(nl, t)
|
|
||||||
|
|
||||||
nl = blank1.PrependSeq(l1)
|
|
||||||
assertEmpty(blank1, t)
|
|
||||||
assertSaneList(nl, t)
|
|
||||||
assertSeqContents(nl, intl1, t)
|
|
||||||
|
|
||||||
nl = l1.PrependSeq(blank1)
|
|
||||||
assertEmpty(blank1, t)
|
|
||||||
assertSaneList(nl, t)
|
|
||||||
assertSeqContents(nl, intl1, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test appending to the end of a List
|
|
||||||
func TestAppend(t *T) {
|
|
||||||
// Normal case
|
|
||||||
intl := elemSliceV(3, 2, 1)
|
|
||||||
l := NewList(intl...)
|
|
||||||
nl := l.Append(types.GoType{0})
|
|
||||||
assertSaneList(l, t)
|
|
||||||
assertSaneList(nl, t)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertSeqContents(nl, elemSliceV(3, 2, 1, 0), t)
|
|
||||||
|
|
||||||
// Edge case (algorithm gets weird here)
|
|
||||||
l = NewList(elemSliceV(1)...)
|
|
||||||
nl = l.Append(types.GoType{0})
|
|
||||||
assertSaneList(l, t)
|
|
||||||
assertSaneList(nl, t)
|
|
||||||
assertSeqContents(l, elemSliceV(1), t)
|
|
||||||
assertSeqContents(nl, elemSliceV(1, 0), t)
|
|
||||||
|
|
||||||
// Degenerate case
|
|
||||||
l = NewList()
|
|
||||||
nl = l.Append(types.GoType{0})
|
|
||||||
assertEmpty(l, t)
|
|
||||||
assertSaneList(nl, t)
|
|
||||||
assertSeqContents(nl, elemSliceV(0), t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test retrieving items from a List
|
|
||||||
func TestNth(t *T) {
|
|
||||||
// Normal case, in bounds
|
|
||||||
intl := elemSliceV(0, 2, 4, 6, 8)
|
|
||||||
l := NewList(intl...)
|
|
||||||
r, ok := l.Nth(3)
|
|
||||||
assertSaneList(l, t)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertValue(r, 6, t)
|
|
||||||
assertValue(ok, true, t)
|
|
||||||
|
|
||||||
// Normal case, out of bounds
|
|
||||||
r, ok = l.Nth(8)
|
|
||||||
assertSaneList(l, t)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertValue(r, nil, t)
|
|
||||||
assertValue(ok, false, t)
|
|
||||||
|
|
||||||
// Degenerate case
|
|
||||||
l = NewList()
|
|
||||||
r, ok = l.Nth(0)
|
|
||||||
assertEmpty(l, t)
|
|
||||||
assertValue(r, nil, t)
|
|
||||||
assertValue(ok, false, t)
|
|
||||||
}
|
|
312
seq/seq.go
312
seq/seq.go
@ -1,312 +0,0 @@
|
|||||||
// This package describes ginger datastructures, and many of the operations
|
|
||||||
// which can be used on those data structures
|
|
||||||
package seq
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The general interface which most operations will actually operate on. Acts as
|
|
||||||
// an interface onto any data structure
|
|
||||||
type Seq interface {
|
|
||||||
|
|
||||||
// Returns the "first" element in the data structure as well as a Seq
|
|
||||||
// containing a copy of the rest of the elements in the data structure. The
|
|
||||||
// "first" element can be random for structures which don't have a concept
|
|
||||||
// of order (like Set). Calling FirstRest on an empty Seq (Size() == 0) will
|
|
||||||
// return "first" as nil, the same empty Seq , and false. The third return
|
|
||||||
// value is true in all other cases.
|
|
||||||
FirstRest() (types.Elem, Seq, bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the number of elements contained in the data structure. In general
|
|
||||||
// this completes in O(N) time, except for Set and HashMap for which it
|
|
||||||
// completes in O(1)
|
|
||||||
func Size(s Seq) uint64 {
|
|
||||||
switch st := s.(type) {
|
|
||||||
case *Set:
|
|
||||||
return st.Size()
|
|
||||||
case *HashMap:
|
|
||||||
return st.Size()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
var ok bool
|
|
||||||
for i := uint64(0); ; {
|
|
||||||
if _, s, ok = s.FirstRest(); ok {
|
|
||||||
i++
|
|
||||||
} else {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns whether or not the given Seq is empty. This is accomplished using
|
|
||||||
// FirstRest and NOT just by naively returning Size(s) == 0.
|
|
||||||
func Empty(s Seq) bool {
|
|
||||||
_, _, ok := s.FirstRest()
|
|
||||||
return !ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the elements in the Seq as a slice. If the underlying Seq has any
|
|
||||||
// implicit order to it that order will be kept. An empty Seq will return an
|
|
||||||
// empty slice; nil is never returned. In general this completes in O(N) time.
|
|
||||||
func ToSlice(s Seq) []types.Elem {
|
|
||||||
var el types.Elem
|
|
||||||
var ok bool
|
|
||||||
for ret := make([]types.Elem, 0, 8); ; {
|
|
||||||
if el, s, ok = s.FirstRest(); ok {
|
|
||||||
ret = append(ret, el)
|
|
||||||
} else {
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turns a Seq into a string, with each element separated by a space and with a
|
|
||||||
// dstart and dend wrapping the whole thing
|
|
||||||
func ToString(s Seq, dstart, dend string) string {
|
|
||||||
buf := bytes.NewBufferString(dstart)
|
|
||||||
buf.WriteString(" ")
|
|
||||||
var el types.Elem
|
|
||||||
var strel fmt.Stringer
|
|
||||||
var rest Seq
|
|
||||||
var ok bool
|
|
||||||
for {
|
|
||||||
if el, rest, ok = s.FirstRest(); ok {
|
|
||||||
if strel, ok = el.(fmt.Stringer); ok {
|
|
||||||
buf.WriteString(strel.String())
|
|
||||||
} else {
|
|
||||||
buf.WriteString(fmt.Sprintf("%v", el))
|
|
||||||
}
|
|
||||||
buf.WriteString(" ")
|
|
||||||
s = rest
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf.WriteString(dend)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a reversed copy of the List. Completes in O(N) time.
|
|
||||||
func Reverse(s Seq) Seq {
|
|
||||||
l := NewList()
|
|
||||||
var el types.Elem
|
|
||||||
var ok bool
|
|
||||||
for {
|
|
||||||
if el, s, ok = s.FirstRest(); ok {
|
|
||||||
l = l.Prepend(el)
|
|
||||||
} else {
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a Seq consisting of the result of applying fn to each element in the
|
|
||||||
// given Seq. Completes in O(N) time.
|
|
||||||
func Map(fn func(types.Elem) types.Elem, s Seq) Seq {
|
|
||||||
l := NewList()
|
|
||||||
var el types.Elem
|
|
||||||
var ok bool
|
|
||||||
for {
|
|
||||||
if el, s, ok = s.FirstRest(); ok {
|
|
||||||
l = l.Prepend(fn(el))
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Reverse(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A function used in a reduce. The first argument is the accumulator, the
|
|
||||||
// second is an element from the Seq being reduced over. The ReduceFn returns
|
|
||||||
// the accumulator to be used in the next iteration, wherein that new
|
|
||||||
// accumulator will be called alongside the next element in the Seq. ReduceFn
|
|
||||||
// also returns a boolean representing whether or not the reduction should stop
|
|
||||||
// at this step. If true, the reductions will stop and any remaining elements in
|
|
||||||
// the Seq will be ignored.
|
|
||||||
type ReduceFn func(acc, el types.Elem) (types.Elem, bool)
|
|
||||||
|
|
||||||
// Reduces over the given Seq using ReduceFn, with acc as the first accumulator
|
|
||||||
// value in the reduce. See ReduceFn for more details on how it works. The
|
|
||||||
// return value is the result of the reduction. Completes in O(N) time.
|
|
||||||
func Reduce(fn ReduceFn, acc types.Elem, s Seq) types.Elem {
|
|
||||||
var el types.Elem
|
|
||||||
var ok, stop bool
|
|
||||||
for {
|
|
||||||
if el, s, ok = s.FirstRest(); ok {
|
|
||||||
acc, stop = fn(acc, el)
|
|
||||||
if stop {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the first element in Seq for which fn returns true, or nil. The
|
|
||||||
// returned boolean indicates whether or not a matching element was found.
|
|
||||||
// Completes in O(N) time.
|
|
||||||
func Any(fn func(el types.Elem) bool, s Seq) (types.Elem, bool) {
|
|
||||||
var el types.Elem
|
|
||||||
var ok bool
|
|
||||||
for {
|
|
||||||
if el, s, ok = s.FirstRest(); ok {
|
|
||||||
if fn(el) {
|
|
||||||
return el, true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if fn returns true for all elements in the Seq. Completes in
|
|
||||||
// O(N) time.
|
|
||||||
func All(fn func(types.Elem) bool, s Seq) bool {
|
|
||||||
var el types.Elem
|
|
||||||
var ok bool
|
|
||||||
for {
|
|
||||||
if el, s, ok = s.FirstRest(); ok {
|
|
||||||
if !fn(el) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a Seq containing all elements in the given Seq for which fn returned
|
|
||||||
// true. Completes in O(N) time.
|
|
||||||
func Filter(fn func(el types.Elem) bool, s Seq) Seq {
|
|
||||||
l := NewList()
|
|
||||||
var el types.Elem
|
|
||||||
var ok bool
|
|
||||||
for {
|
|
||||||
if el, s, ok = s.FirstRest(); ok {
|
|
||||||
if fn(el) {
|
|
||||||
l = l.Prepend(el)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Reverse(l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flattens the given Seq into a single, one-dimensional Seq. This method only
|
|
||||||
// flattens Seqs found in the top level of the given Seq, it does not recurse
|
|
||||||
// down to multiple layers. Completes in O(N*M) time, where N is the number of
|
|
||||||
// elements in the Seq and M is how large the Seqs in those elements actually
|
|
||||||
// are.
|
|
||||||
func Flatten(s Seq) Seq {
|
|
||||||
l := NewList()
|
|
||||||
var el types.Elem
|
|
||||||
var ok bool
|
|
||||||
for {
|
|
||||||
if el, s, ok = s.FirstRest(); ok {
|
|
||||||
if els, ok := el.(Seq); ok {
|
|
||||||
l = l.PrependSeq(Reverse(els))
|
|
||||||
} else {
|
|
||||||
l = l.Prepend(el)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Reverse(l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a Seq containing the first n elements in the given Seq. If n is
|
|
||||||
// greater than the length of the given Seq then the whole Seq is returned.
|
|
||||||
// Completes in O(N) time.
|
|
||||||
func Take(n uint64, s Seq) Seq {
|
|
||||||
l := NewList()
|
|
||||||
var el types.Elem
|
|
||||||
var ok bool
|
|
||||||
for i := uint64(0); i < n; i++ {
|
|
||||||
el, s, ok = s.FirstRest()
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
l = l.Prepend(el)
|
|
||||||
}
|
|
||||||
return Reverse(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Goes through each item in the given Seq until an element returns false from
|
|
||||||
// pred. Returns a new Seq containing these truthful elements. Completes in O(N)
|
|
||||||
// time.
|
|
||||||
func TakeWhile(pred func(types.Elem) bool, s Seq) Seq {
|
|
||||||
l := NewList()
|
|
||||||
var el types.Elem
|
|
||||||
var ok bool
|
|
||||||
for {
|
|
||||||
el, s, ok = s.FirstRest()
|
|
||||||
if !ok || !pred(el) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
l = l.Prepend(el)
|
|
||||||
}
|
|
||||||
return Reverse(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a Seq the is the previous Seq without the first n elements. If n is
|
|
||||||
// greater than the length of the Seq, returns an empty Seq. Completes in O(N)
|
|
||||||
// time.
|
|
||||||
func Drop(n uint64, s Seq) Seq {
|
|
||||||
var ok bool
|
|
||||||
for i := uint64(0); i < n; i++ {
|
|
||||||
_, s, ok = s.FirstRest()
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drops elements from the given Seq until pred returns false for an element.
|
|
||||||
// Returns a Seq of the remaining elements (including the one which returned
|
|
||||||
// false). Completes in O(N) time.
|
|
||||||
func DropWhile(pred func(types.Elem) bool, s Seq) Seq {
|
|
||||||
var el types.Elem
|
|
||||||
var curs Seq
|
|
||||||
var ok bool
|
|
||||||
for {
|
|
||||||
el, curs, ok = s.FirstRest()
|
|
||||||
if !ok || !pred(el) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
s = curs
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Runs pred on each element in the sequence, descending recursively if it
|
|
||||||
// encounters another Seq (without calling pred on that Seq). This amounts to a
|
|
||||||
// depth-first traverse. If pred ever returns false the traverse will stop.
|
|
||||||
// Returns false if the Traverse was stopped by pred, true otherwise.
|
|
||||||
func Traverse(pred func(types.Elem) bool, s Seq) bool {
|
|
||||||
var el types.Elem
|
|
||||||
var ok bool
|
|
||||||
for {
|
|
||||||
el, s, ok = s.FirstRest()
|
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
var predRet bool
|
|
||||||
if inners, ok := el.(Seq); ok {
|
|
||||||
predRet = Traverse(pred, inners)
|
|
||||||
} else {
|
|
||||||
predRet = pred(el)
|
|
||||||
}
|
|
||||||
if !predRet {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
427
seq/seq_test.go
427
seq/seq_test.go
@ -1,427 +0,0 @@
|
|||||||
package seq
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "testing"
|
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Tests the FirstRest, Size, Empty, and ToSlice methods of a Seq
|
|
||||||
func testSeqGen(t *T, s Seq, ints []types.Elem) Seq {
|
|
||||||
intsl := uint64(len(ints))
|
|
||||||
for i := range ints {
|
|
||||||
assertSaneList(ToList(s), t)
|
|
||||||
assertValue(Size(s), intsl-uint64(i), t)
|
|
||||||
assertSeqContents(s, ints[i:], t)
|
|
||||||
|
|
||||||
first, rest, ok := s.FirstRest()
|
|
||||||
assertValue(ok, true, t)
|
|
||||||
assertValue(first, ints[i], t)
|
|
||||||
|
|
||||||
empty := Empty(s)
|
|
||||||
assertValue(empty, false, t)
|
|
||||||
|
|
||||||
s = rest
|
|
||||||
}
|
|
||||||
empty := Empty(s)
|
|
||||||
assertValue(empty, true, t)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests the FirstRest, Size, and ToSlice methods of an unordered Seq
|
|
||||||
func testSeqNoOrderGen(t *T, s Seq, ints []types.Elem) Seq {
|
|
||||||
intsl := uint64(len(ints))
|
|
||||||
|
|
||||||
m := map[types.Elem]bool{}
|
|
||||||
for i := range ints {
|
|
||||||
m[ints[i]] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range ints {
|
|
||||||
assertSaneList(ToList(s), t)
|
|
||||||
assertValue(Size(s), intsl-uint64(i), t)
|
|
||||||
assertSeqContentsNoOrderMap(s, m, t)
|
|
||||||
|
|
||||||
first, rest, ok := s.FirstRest()
|
|
||||||
assertValue(ok, true, t)
|
|
||||||
assertInMap(first, m, t)
|
|
||||||
|
|
||||||
delete(m, first)
|
|
||||||
s = rest
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test reversing a Seq
|
|
||||||
func TestReverse(t *T) {
|
|
||||||
// Normal case
|
|
||||||
intl := elemSliceV(3, 2, 1)
|
|
||||||
l := NewList(intl...)
|
|
||||||
nl := Reverse(l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertSeqContents(nl, elemSliceV(1, 2, 3), t)
|
|
||||||
|
|
||||||
// Degenerate case
|
|
||||||
l = NewList()
|
|
||||||
nl = Reverse(l)
|
|
||||||
assertEmpty(l, t)
|
|
||||||
assertEmpty(nl, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testMapGen(t *T, mapFn func(func(types.Elem) types.Elem, Seq) Seq) {
|
|
||||||
fn := func(n types.Elem) types.Elem {
|
|
||||||
return types.GoType{n.(types.GoType).V.(int) + 1}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normal case
|
|
||||||
intl := elemSliceV(1, 2, 3)
|
|
||||||
l := NewList(intl...)
|
|
||||||
nl := mapFn(fn, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertSeqContents(nl, elemSliceV(2, 3, 4), t)
|
|
||||||
|
|
||||||
// Degenerate case
|
|
||||||
l = NewList()
|
|
||||||
nl = mapFn(fn, l)
|
|
||||||
assertEmpty(l, t)
|
|
||||||
assertEmpty(nl, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test mapping over a Seq
|
|
||||||
func TestMap(t *T) {
|
|
||||||
testMapGen(t, Map)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test lazily mapping over a Seq
|
|
||||||
func TestLMap(t *T) {
|
|
||||||
testMapGen(t, LMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test reducing over a Seq
|
|
||||||
func TestReduce(t *T) {
|
|
||||||
fn := func(acc, el types.Elem) (types.Elem, bool) {
|
|
||||||
acci := acc.(types.GoType).V.(int)
|
|
||||||
eli := el.(types.GoType).V.(int)
|
|
||||||
return types.GoType{acci + eli}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normal case
|
|
||||||
intl := elemSliceV(1, 2, 3, 4)
|
|
||||||
l := NewList(intl...)
|
|
||||||
r := Reduce(fn, types.GoType{0}, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertValue(r, 10, t)
|
|
||||||
|
|
||||||
// Short-circuit case
|
|
||||||
fns := func(acc, el types.Elem) (types.Elem, bool) {
|
|
||||||
acci := acc.(types.GoType).V.(int)
|
|
||||||
eli := el.(types.GoType).V.(int)
|
|
||||||
return types.GoType{acci + eli}, eli > 2
|
|
||||||
}
|
|
||||||
r = Reduce(fns, types.GoType{0}, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertValue(r, 6, t)
|
|
||||||
|
|
||||||
// Degenerate case
|
|
||||||
l = NewList()
|
|
||||||
r = Reduce(fn, types.GoType{0}, l)
|
|
||||||
assertEmpty(l, t)
|
|
||||||
assertValue(r, 0, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the Any function
|
|
||||||
func TestAny(t *T) {
|
|
||||||
fn := func(el types.Elem) bool {
|
|
||||||
return el.(types.GoType).V.(int) > 3
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value found case
|
|
||||||
intl := elemSliceV(1, 2, 3, 4)
|
|
||||||
l := NewList(intl...)
|
|
||||||
r, ok := Any(fn, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertValue(r, 4, t)
|
|
||||||
assertValue(ok, true, t)
|
|
||||||
|
|
||||||
// Value not found case
|
|
||||||
intl = elemSliceV(1, 2, 3)
|
|
||||||
l = NewList(intl...)
|
|
||||||
r, ok = Any(fn, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertValue(r, nil, t)
|
|
||||||
assertValue(ok, false, t)
|
|
||||||
|
|
||||||
// Degenerate case
|
|
||||||
l = NewList()
|
|
||||||
r, ok = Any(fn, l)
|
|
||||||
assertEmpty(l, t)
|
|
||||||
assertValue(r, nil, t)
|
|
||||||
assertValue(ok, false, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the All function
|
|
||||||
func TestAll(t *T) {
|
|
||||||
fn := func(el types.Elem) bool {
|
|
||||||
return el.(types.GoType).V.(int) > 3
|
|
||||||
}
|
|
||||||
|
|
||||||
// All match case
|
|
||||||
intl := elemSliceV(4, 5, 6)
|
|
||||||
l := NewList(intl...)
|
|
||||||
ok := All(fn, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertValue(ok, true, t)
|
|
||||||
|
|
||||||
// Not all match case
|
|
||||||
intl = elemSliceV(3, 4, 2, 5)
|
|
||||||
l = NewList(intl...)
|
|
||||||
ok = All(fn, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertValue(ok, false, t)
|
|
||||||
|
|
||||||
// Degenerate case
|
|
||||||
l = NewList()
|
|
||||||
ok = All(fn, l)
|
|
||||||
assertEmpty(l, t)
|
|
||||||
assertValue(ok, true, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testFilterGen(t *T, filterFn func(func(types.Elem) bool, Seq) Seq) {
|
|
||||||
fn := func(el types.Elem) bool {
|
|
||||||
return el.(types.GoType).V.(int)%2 != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normal case
|
|
||||||
intl := elemSliceV(1, 2, 3, 4, 5)
|
|
||||||
l := NewList(intl...)
|
|
||||||
r := filterFn(fn, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertSeqContents(r, elemSliceV(1, 3, 5), t)
|
|
||||||
|
|
||||||
// Degenerate cases
|
|
||||||
l = NewList()
|
|
||||||
r = filterFn(fn, l)
|
|
||||||
assertEmpty(l, t)
|
|
||||||
assertEmpty(r, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the Filter function
|
|
||||||
func TestFilter(t *T) {
|
|
||||||
testFilterGen(t, Filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the lazy Filter function
|
|
||||||
func TestLFilter(t *T) {
|
|
||||||
testFilterGen(t, LFilter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test Flatten-ing of a Seq
|
|
||||||
func TestFlatten(t *T) {
|
|
||||||
// Normal case
|
|
||||||
intl1 := elemSliceV(0, 1, 2)
|
|
||||||
intl2 := elemSliceV(3, 4, 5)
|
|
||||||
l1 := NewList(intl1...)
|
|
||||||
l2 := NewList(intl2...)
|
|
||||||
blank := NewList()
|
|
||||||
intl := elemSliceV(-1, l1, l2, 6, blank, 7)
|
|
||||||
l := NewList(intl...)
|
|
||||||
nl := Flatten(l)
|
|
||||||
assertSeqContents(l1, intl1, t)
|
|
||||||
assertSeqContents(l2, intl2, t)
|
|
||||||
assertEmpty(blank, t)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertSeqContents(nl, elemSliceV(-1, 0, 1, 2, 3, 4, 5, 6, 7), t)
|
|
||||||
|
|
||||||
// Degenerate case
|
|
||||||
nl = Flatten(blank)
|
|
||||||
assertEmpty(blank, t)
|
|
||||||
assertEmpty(nl, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testTakeGen(t *T, takeFn func(uint64, Seq) Seq) {
|
|
||||||
// Normal case
|
|
||||||
intl := elemSliceV(0, 1, 2, 3, 4)
|
|
||||||
l := NewList(intl...)
|
|
||||||
nl := takeFn(3, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertSeqContents(nl, elemSliceV(0, 1, 2), t)
|
|
||||||
|
|
||||||
// Edge cases
|
|
||||||
nl = takeFn(5, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertSeqContents(nl, intl, t)
|
|
||||||
|
|
||||||
nl = takeFn(6, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertSeqContents(nl, intl, t)
|
|
||||||
|
|
||||||
// Degenerate cases
|
|
||||||
empty := NewList()
|
|
||||||
nl = takeFn(1, empty)
|
|
||||||
assertEmpty(empty, t)
|
|
||||||
assertEmpty(nl, t)
|
|
||||||
|
|
||||||
nl = takeFn(0, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertEmpty(nl, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test taking from a Seq
|
|
||||||
func TestTake(t *T) {
|
|
||||||
testTakeGen(t, Take)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test lazily taking from a Seq
|
|
||||||
func TestLTake(t *T) {
|
|
||||||
testTakeGen(t, LTake)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testTakeWhileGen(t *T, takeWhileFn func(func(types.Elem) bool, Seq) Seq) {
|
|
||||||
pred := func(el types.Elem) bool {
|
|
||||||
return el.(types.GoType).V.(int) < 3
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normal case
|
|
||||||
intl := elemSliceV(0, 1, 2, 3, 4, 5)
|
|
||||||
l := NewList(intl...)
|
|
||||||
nl := takeWhileFn(pred, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertSeqContents(nl, elemSliceV(0, 1, 2), t)
|
|
||||||
|
|
||||||
// Edge cases
|
|
||||||
intl = elemSliceV(5, 5, 5)
|
|
||||||
l = NewList(intl...)
|
|
||||||
nl = takeWhileFn(pred, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertEmpty(nl, t)
|
|
||||||
|
|
||||||
intl = elemSliceV(0, 1, 2)
|
|
||||||
l = NewList(intl...)
|
|
||||||
nl = takeWhileFn(pred, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertSeqContents(nl, elemSliceV(0, 1, 2), t)
|
|
||||||
|
|
||||||
// Degenerate case
|
|
||||||
l = NewList()
|
|
||||||
nl = takeWhileFn(pred, l)
|
|
||||||
assertEmpty(l, t)
|
|
||||||
assertEmpty(nl, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test taking from a Seq until a given condition
|
|
||||||
func TestTakeWhile(t *T) {
|
|
||||||
testTakeWhileGen(t, TakeWhile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test lazily taking from a Seq until a given condition
|
|
||||||
func TestLTakeWhile(t *T) {
|
|
||||||
testTakeWhileGen(t, LTakeWhile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test dropping from a Seq
|
|
||||||
func TestDrop(t *T) {
|
|
||||||
// Normal case
|
|
||||||
intl := elemSliceV(0, 1, 2, 3, 4)
|
|
||||||
l := NewList(intl...)
|
|
||||||
nl := Drop(3, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertSeqContents(nl, elemSliceV(3, 4), t)
|
|
||||||
|
|
||||||
// Edge cases
|
|
||||||
nl = Drop(5, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertEmpty(nl, t)
|
|
||||||
|
|
||||||
nl = Drop(6, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertEmpty(nl, t)
|
|
||||||
|
|
||||||
// Degenerate cases
|
|
||||||
empty := NewList()
|
|
||||||
nl = Drop(1, empty)
|
|
||||||
assertEmpty(empty, t)
|
|
||||||
assertEmpty(nl, t)
|
|
||||||
|
|
||||||
nl = Drop(0, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertSeqContents(nl, intl, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test dropping from a Seq until a given condition
|
|
||||||
func TestDropWhile(t *T) {
|
|
||||||
pred := func(el types.Elem) bool {
|
|
||||||
return el.(types.GoType).V.(int) < 3
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normal case
|
|
||||||
intl := elemSliceV(0, 1, 2, 3, 4, 5)
|
|
||||||
l := NewList(intl...)
|
|
||||||
nl := DropWhile(pred, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertSeqContents(nl, elemSliceV(3, 4, 5), t)
|
|
||||||
|
|
||||||
// Edge cases
|
|
||||||
intl = elemSliceV(5, 5, 5)
|
|
||||||
l = NewList(intl...)
|
|
||||||
nl = DropWhile(pred, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertSeqContents(nl, intl, t)
|
|
||||||
|
|
||||||
intl = elemSliceV(0, 1, 2)
|
|
||||||
l = NewList(intl...)
|
|
||||||
nl = DropWhile(pred, l)
|
|
||||||
assertSeqContents(l, intl, t)
|
|
||||||
assertEmpty(nl, t)
|
|
||||||
|
|
||||||
// Degenerate case
|
|
||||||
l = NewList()
|
|
||||||
nl = DropWhile(pred, l)
|
|
||||||
assertEmpty(l, t)
|
|
||||||
assertEmpty(nl, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test Traversing a Seq until a given condition
|
|
||||||
func TestTraverse(t *T) {
|
|
||||||
var acc int
|
|
||||||
pred := func(el types.Elem) bool {
|
|
||||||
acc += el.(types.GoType).V.(int)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
l := NewList()
|
|
||||||
acc = 0
|
|
||||||
Traverse(pred, l)
|
|
||||||
assertValue(acc, 0, t)
|
|
||||||
|
|
||||||
l2 := NewList(elemSliceV(0, 1, 2, 3)...)
|
|
||||||
acc = 0
|
|
||||||
Traverse(pred, l2)
|
|
||||||
assertValue(acc, 6, t)
|
|
||||||
|
|
||||||
l3 := NewList(
|
|
||||||
types.GoType{1},
|
|
||||||
types.GoType{2},
|
|
||||||
NewList(elemSliceV(4, 5, 6)...),
|
|
||||||
types.GoType{3},
|
|
||||||
)
|
|
||||||
acc = 0
|
|
||||||
Traverse(pred, l3)
|
|
||||||
assertValue(acc, 21, t)
|
|
||||||
|
|
||||||
pred = func(el types.Elem) bool {
|
|
||||||
i := el.(types.GoType).V.(int)
|
|
||||||
if i > 4 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
acc += i
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
acc = 0
|
|
||||||
Traverse(pred, l2)
|
|
||||||
assertValue(acc, 6, t)
|
|
||||||
|
|
||||||
acc = 0
|
|
||||||
Traverse(pred, l3)
|
|
||||||
assertValue(acc, 7, t)
|
|
||||||
}
|
|
113
seq/util.go
113
seq/util.go
@ -1,113 +0,0 @@
|
|||||||
package seq
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/mediocregopher/ginger/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Returns whether or not two types.Elem slices contain the same elements
|
|
||||||
func intSlicesEq(a, b []types.Elem) bool {
|
|
||||||
if len(a) != len(b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i := range a {
|
|
||||||
if a[i] != b[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func elemSliceV(a ...interface{}) []types.Elem {
|
|
||||||
ret := make([]types.Elem, 0, len(a))
|
|
||||||
for i := range a {
|
|
||||||
if e, ok := a[i].(types.Elem); ok {
|
|
||||||
ret = append(ret, e)
|
|
||||||
} else {
|
|
||||||
ret = append(ret, types.GoType{a[i]})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// Asserts that the given Seq is empty (contains no elements)
|
|
||||||
func assertEmpty(s Seq, t *testing.T) {
|
|
||||||
if Size(s) != 0 {
|
|
||||||
t.Fatalf("Seq isn't empty: %v", ToSlice(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Asserts that the given Seq has the given elements
|
|
||||||
func assertSeqContents(s Seq, intl []types.Elem, t *testing.T) {
|
|
||||||
if ls := ToSlice(s); !intSlicesEq(ls, intl) {
|
|
||||||
t.Fatalf("Slice contents wrong: %v not %v", ls, intl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Asserts that the given Seq has all elements, and only the elements, in the
|
|
||||||
// given map
|
|
||||||
func assertSeqContentsNoOrderMap(s Seq, m map[types.Elem]bool, t *testing.T) {
|
|
||||||
ls := ToSlice(s)
|
|
||||||
if len(ls) != len(m) {
|
|
||||||
t.Fatalf("Slice contents wrong: %v not %v", ls, m)
|
|
||||||
}
|
|
||||||
for i := range ls {
|
|
||||||
if _, ok := m[ls[i]]; !ok {
|
|
||||||
t.Fatalf("Slice contents wrong: %v not %v", ls, m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Asserts that the given Seq has all the elements, and only the elements
|
|
||||||
// (duplicates removed), in the given slice, although no necessarily in the
|
|
||||||
// order given in the slice
|
|
||||||
func assertSeqContentsSet(s Seq, ints []types.Elem, t *testing.T) {
|
|
||||||
m := map[types.Elem]bool{}
|
|
||||||
for i := range ints {
|
|
||||||
m[ints[i]] = true
|
|
||||||
}
|
|
||||||
assertSeqContentsNoOrderMap(s, m, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertSeqContentsHashMap(s Seq, kvs []*KV, t *testing.T) {
|
|
||||||
m := map[KV]bool{}
|
|
||||||
for i := range kvs {
|
|
||||||
m[*kvs[i]] = true
|
|
||||||
}
|
|
||||||
ls := ToSlice(s)
|
|
||||||
if len(ls) != len(m) {
|
|
||||||
t.Fatalf("Slice contents wrong: %v not %v", ls, m)
|
|
||||||
}
|
|
||||||
for i := range ls {
|
|
||||||
kv := ls[i].(*KV)
|
|
||||||
if _, ok := m[*kv]; !ok {
|
|
||||||
t.Fatalf("Slice contents wrong: %v not %v", ls, m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Asserts that v1 is the same as v2
|
|
||||||
func assertValue(v1, v2 interface{}, t *testing.T) {
|
|
||||||
if gv1, ok := v1.(types.GoType); ok {
|
|
||||||
v1 = gv1.V
|
|
||||||
}
|
|
||||||
if gv2, ok := v2.(types.GoType); ok {
|
|
||||||
v2 = gv2.V
|
|
||||||
}
|
|
||||||
if v1 != v2 {
|
|
||||||
t.Logf("Value wrong: %v not %v", v1, v2)
|
|
||||||
panic("bail")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Asserts that v1 is a key in the given map
|
|
||||||
func assertInMap(v1 types.Elem, m map[types.Elem]bool, t *testing.T) {
|
|
||||||
if _, ok := m[v1]; !ok {
|
|
||||||
t.Fatalf("Value not in set: %v not in %v", v1, m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func keyValV(k, v interface{}) *KV {
|
|
||||||
return KeyVal(types.GoType{k}, types.GoType{v})
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
// This package describes ginger's base types and the interfaces covered by them
|
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Elem is a generic type which can be used as a wrapper type for all ginger
|
|
||||||
// types, both base types and data structures
|
|
||||||
type Elem interface {
|
|
||||||
|
|
||||||
// Returns whether one element is equal to another. Since all ginger values
|
|
||||||
// are immutable, this must be a deep-equals check.
|
|
||||||
Equal(Elem) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Number can either be either an Int or a Float
|
|
||||||
type Number interface {
|
|
||||||
Elem
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wraps a go type like int, string, or []byte. GoType is a struct whose only
|
|
||||||
// field is an interface{}, so using a pointer to is not necessary. Just pass
|
|
||||||
// around the value type.
|
|
||||||
type GoType struct {
|
|
||||||
V interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g GoType) Equal(e Elem) bool {
|
|
||||||
if g2, ok := e.(GoType); ok {
|
|
||||||
return g.V == g2.V
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g GoType) String() string {
|
|
||||||
return fmt.Sprintf("%v", g.V)
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user