Add tons of builtin helpers and array operations.
Some checks failed
Test / build (push) Waiting to run
Build / build (push) Has been cancelled

This commit is contained in:
Chuck Smith
2024-03-24 12:11:46 -04:00
parent c9d96236dd
commit 99cea83f57
23 changed files with 413 additions and 49 deletions

10
object/builtin_args.go Normal file
View File

@@ -0,0 +1,10 @@
package object
// Args ...
func Args(args ...Object) Object {
elements := make([]Object, len(Arguments))
for i, arg := range Arguments {
elements[i] = &String{Value: arg}
}
return &Array{Elements: elements}
}

View File

@@ -1,17 +1,17 @@
package object
import "os"
// Exit ...
func Exit(args ...Object) Object {
var status int
if len(args) == 1 {
if args[0].Type() != INTEGER_OBJ {
return newError("argument to `exit` must be INTEGER, got %s",
args[0].Type())
}
os.Exit(int(args[0].(*Integer).Value))
} else {
os.Exit(0)
status = int(args[0].(*Integer).Value)
}
ExitFunction(status)
return nil
}

34
object/builtin_find.go Normal file
View File

@@ -0,0 +1,34 @@
package object
import (
"strings"
)
// Find ...
func Find(args ...Object) Object {
if len(args) != 2 {
return newError("wrong number of arguments. got=%d, want=2",
len(args))
}
if haystack, ok := args[0].(*String); ok {
if needle, ok := args[1].(*String); ok {
index := strings.Index(haystack.Value, needle.Value)
return &Integer{Value: int64(index)}
} else {
return newError("expected arg #2 to be `str` got got=%T", args[1])
}
} else if haystack, ok := args[0].(*Array); ok {
needle := args[1]
index := -1
for i, el := range haystack.Elements {
if cmp, ok := el.(Comparable); ok && cmp.Equal(needle) {
index = i
break
}
}
return &Integer{Value: int64(index)}
} else {
return newError("expected arg #1 to be `str` or `array` got got=%T", args[0])
}
}

View File

@@ -7,7 +7,7 @@ func First(args ...Object) Object {
len(args))
}
if args[0].Type() != ARRAY_OBJ {
return newError("argument to `first` must be ARRAY, got %s",
return newError("argument to `first` must be array, got %s",
args[0].Type())
}

27
object/builtin_join.go Normal file
View File

@@ -0,0 +1,27 @@
package object
import (
"strings"
)
// Join ...
func Join(args ...Object) Object {
if len(args) != 2 {
return newError("wrong number of arguments. got=%d, want=1",
len(args))
}
if arr, ok := args[0].(*Array); ok {
if sep, ok := args[1].(*String); ok {
a := make([]string, len(arr.Elements))
for i, el := range arr.Elements {
a[i] = el.String()
}
return &String{Value: strings.Join(a, sep.Value)}
} else {
return newError("expected arg #2 to be `str` got got=%T", args[1])
}
} else {
return newError("expected arg #1 to be `array` got got=%T", args[0])
}
}

View File

@@ -7,7 +7,7 @@ func Last(args ...Object) Object {
len(args))
}
if args[0].Type() != ARRAY_OBJ {
return newError("argument to `last` must be ARRAY, got %s",
return newError("argument to `last` must be array, got %s",
args[0].Type())
}

16
object/builtin_lower.go Normal file
View File

@@ -0,0 +1,16 @@
package object
import "strings"
// Lower ...
func Lower(args ...Object) Object {
if len(args) != 1 {
return newError("wrong number of arguments. got=%d, want=1",
len(args))
}
if str, ok := args[0].(*String); ok {
return &String{Value: strings.ToLower(str.Value)}
}
return newError("expected `str` argument to `lower` got=%T", args[0])
}

View File

@@ -7,7 +7,7 @@ func Pop(args ...Object) Object {
len(args))
}
if args[0].Type() != ARRAY_OBJ {
return newError("argument to `pop` must be ARRAY, got %s",
return newError("argument to `pop` must be array, got %s",
args[0].Type())
}

View File

@@ -7,7 +7,7 @@ func Push(args ...Object) Object {
len(args))
}
if args[0].Type() != ARRAY_OBJ {
return newError("argument to `push` must be ARRAY, got %s",
return newError("argument to `push` must be array, got %s",
args[0].Type())
}

View File

@@ -7,7 +7,7 @@ func Rest(args ...Object) Object {
len(args))
}
if args[0].Type() != ARRAY_OBJ {
return newError("argument to `rest` must be ARRAY, got %s",
return newError("argument to `rest` must be array, got %s",
args[0].Type())
}

36
object/builtin_split.go Normal file
View File

@@ -0,0 +1,36 @@
package object
import (
"strings"
)
// Split ...
func Split(args ...Object) Object {
if len(args) < 1 {
return newError("wrong number of arguments. got=%d, want=1",
len(args))
}
if obj, ok := args[0].(*String); ok {
var sep string
s := obj.Value
if len(args) == 2 {
if obj, ok := args[1].(*String); ok {
sep = obj.Value
} else {
return newError("expected arg #2 to be `str` got=%T", args[1])
}
}
tokens := strings.Split(s, sep)
elements := make([]Object, len(tokens))
for i, token := range tokens {
elements[i] = &String{Value: token}
}
return &Array{Elements: elements}
} else {
return newError("expected arg #1 to be `str` got got=%T", args[0])
}
}

10
object/builtin_typeof.go Normal file
View File

@@ -0,0 +1,10 @@
package object
// TypeOf ...
func TypeOf(args ...Object) Object {
if len(args) != 1 {
return newError("wrong number of arguments. got=%d, want=1", len(args))
}
return &String{Value: string(args[0].Type())}
}

16
object/builtin_upper.go Normal file
View File

@@ -0,0 +1,16 @@
package object
import "strings"
// Upper ...
func Upper(args ...Object) Object {
if len(args) != 1 {
return newError("wrong number of arguments. got=%d, want=1",
len(args))
}
if str, ok := args[0].(*String); ok {
return &String{Value: strings.ToUpper(str.Value)}
}
return newError("expected `str` argument to `upper` got=%T", args[0])
}

View File

@@ -20,6 +20,13 @@ var Builtins = map[string]*Builtin{
"bool": {Name: "bool", Fn: Bool},
"int": {Name: "int", Fn: Int},
"str": {Name: "str", Fn: Str},
"typeof": {Name: "typeof", Fn: TypeOf},
"args": {Name: "args", Fn: Args},
"lower": {Name: "lower", Fn: Lower},
"upper": {Name: "upper", Fn: Upper},
"join": {Name: "join", Fn: Join},
"split": {Name: "split", Fn: Split},
"find": {Name: "find", Fn: Find},
}
// BuiltinsIndex ...

View File

@@ -12,20 +12,27 @@ import (
type ObjectType string
const (
INTEGER_OBJ = "INTEGER"
BOOLEAN_OBJ = "BOOLEAN"
NULL_OBJ = "NULL"
RETURN_VALUE_OBJ = "RETURN_VALUE"
ERROR_OBJ = "ERROR"
FUNCTION_OBJ = "FUNCTION"
STRING_OBJ = "STRING"
BUILTIN_OBJ = "BUILTIN"
ARRAY_OBJ = "ARRAY"
HASH_OBJ = "HASH"
INTEGER_OBJ = "int"
BOOLEAN_OBJ = "bool"
NULL_OBJ = "null"
RETURN_VALUE_OBJ = "return"
ERROR_OBJ = "error"
FUNCTION_OBJ = "fn"
STRING_OBJ = "str"
BUILTIN_OBJ = "builtin"
ARRAY_OBJ = "array"
HASH_OBJ = "hash"
COMPILED_FUNCTION_OBJ = "COMPILED_FUNCTION"
CLOSURE_OBJ = "CLOSURE"
CLOSURE_OBJ = "closure"
)
// Comparable is the interface for comparing two Object and their underlying
// values. It is the responsibility of the caller (left) to check for types.
// Returns `true` iif the types and values are identical, `false` otherwise.
type Comparable interface {
Equal(other Object) bool
}
// Immutable is the interface for all immutable objects which must implement
// the Clone() method used by binding names to values.
type Immutable interface {
@@ -60,6 +67,12 @@ func (i *Integer) Clone() Object {
func (i *Integer) String() string {
return i.Inspect()
}
func (i *Integer) Equal(other Object) bool {
if obj, ok := other.(*Integer); ok {
return i.Value == obj.Value
}
return false
}
type Boolean struct {
Value bool
@@ -77,6 +90,12 @@ func (b *Boolean) Clone() Object {
func (b *Boolean) String() string {
return b.Inspect()
}
func (b *Boolean) Equal(other Object) bool {
if obj, ok := other.(*Boolean); ok {
return b.Value == obj.Value
}
return false
}
type Null struct{}
@@ -89,6 +108,10 @@ func (n *Null) Inspect() string {
func (n *Null) String() string {
return n.Inspect()
}
func (n *Null) Equal(other Object) bool {
_, ok := other.(*Null)
return ok
}
type ReturnValue struct {
Value Object
@@ -167,6 +190,12 @@ func (s *String) Clone() Object {
func (s *String) String() string {
return s.Value
}
func (s *String) Equal(other Object) bool {
if obj, ok := other.(*String); ok {
return s.Value == obj.Value
}
return false
}
type BuiltinFunction func(args ...Object) Object
@@ -209,6 +238,25 @@ func (ao *Array) Inspect() string {
func (ao *Array) String() string {
return ao.Inspect()
}
func (ao *Array) Equal(other Object) bool {
if obj, ok := other.(*Array); ok {
if len(ao.Elements) != len(obj.Elements) {
return false
}
for i, el := range ao.Elements {
cmp, ok := el.(Comparable)
if !ok {
return false
}
if !cmp.Equal(obj.Elements[i]) {
return false
}
}
return true
}
return false
}
type HashKey struct {
Type ObjectType
@@ -271,6 +319,31 @@ func (h *Hash) Inspect() string {
func (h *Hash) String() string {
return h.Inspect()
}
func (h *Hash) Equal(other Object) bool {
if obj, ok := other.(*Hash); ok {
if len(h.Pairs) != len(obj.Pairs) {
return false
}
for _, pair := range h.Pairs {
left := pair.Value
hashed := left.(Hashable)
right, ok := obj.Pairs[hashed.HashKey()]
if !ok {
return false
}
cmp, ok := left.(Comparable)
if !ok {
return false
}
if !cmp.Equal(right.Value) {
return false
}
}
return true
}
return false
}
func (cf *CompiledFunction) Type() ObjectType {
return COMPILED_FUNCTION_OBJ

10
object/state.go Normal file
View File

@@ -0,0 +1,10 @@
package object
import "io"
var (
Arguments []string
StandardInput io.Reader
StandardOutput io.Writer
ExitFunction func(int)
)