builtins.go
// Copyright (c) 2020-2023 Ozan Hacıbekiroğlu.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
package ugo
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"sort"
"strconv"
"strings"
"unicode/utf8"
"github.com/ozanh/ugo/token"
)
var (
// PrintWriter is the default writer for printf and println builtins.
PrintWriter io.Writer = os.Stdout
)
// BuiltinType represents a builtin type
type BuiltinType byte
// Builtins
const (
BuiltinAppend BuiltinType = iota
BuiltinDelete
BuiltinCopy
BuiltinRepeat
BuiltinContains
BuiltinLen
BuiltinSort
BuiltinSortReverse
BuiltinError
BuiltinTypeName
BuiltinBool
BuiltinInt
BuiltinUint
BuiltinFloat
BuiltinChar
BuiltinString
BuiltinBytes
BuiltinChars
BuiltinPrintf
BuiltinPrintln
BuiltinSprintf
BuiltinGlobals
BuiltinIsError
BuiltinIsInt
BuiltinIsUint
BuiltinIsFloat
BuiltinIsChar
BuiltinIsBool
BuiltinIsString
BuiltinIsBytes
BuiltinIsMap
BuiltinIsSyncMap
BuiltinIsArray
BuiltinIsUndefined
BuiltinIsFunction
BuiltinIsCallable
BuiltinIsIterable
BuiltinWrongNumArgumentsError
BuiltinInvalidOperatorError
BuiltinIndexOutOfBoundsError
BuiltinNotIterableError
BuiltinNotIndexableError
BuiltinNotIndexAssignableError
BuiltinNotCallableError
BuiltinNotImplementedError
BuiltinZeroDivisionError
BuiltinTypeError
BuiltinMakeArray
BuiltinCap
)
// BuiltinsMap is list of builtin types, exported for REPL.
var BuiltinsMap = map[string]BuiltinType{
"append": BuiltinAppend,
"delete": BuiltinDelete,
"copy": BuiltinCopy,
"repeat": BuiltinRepeat,
"contains": BuiltinContains,
"len": BuiltinLen,
"sort": BuiltinSort,
"sortReverse": BuiltinSortReverse,
"error": BuiltinError,
"typeName": BuiltinTypeName,
"bool": BuiltinBool,
"int": BuiltinInt,
"uint": BuiltinUint,
"float": BuiltinFloat,
"char": BuiltinChar,
"string": BuiltinString,
"bytes": BuiltinBytes,
"chars": BuiltinChars,
"printf": BuiltinPrintf,
"println": BuiltinPrintln,
"sprintf": BuiltinSprintf,
"globals": BuiltinGlobals,
"isError": BuiltinIsError,
"isInt": BuiltinIsInt,
"isUint": BuiltinIsUint,
"isFloat": BuiltinIsFloat,
"isChar": BuiltinIsChar,
"isBool": BuiltinIsBool,
"isString": BuiltinIsString,
"isBytes": BuiltinIsBytes,
"isMap": BuiltinIsMap,
"isSyncMap": BuiltinIsSyncMap,
"isArray": BuiltinIsArray,
"isUndefined": BuiltinIsUndefined,
"isFunction": BuiltinIsFunction,
"isCallable": BuiltinIsCallable,
"isIterable": BuiltinIsIterable,
"WrongNumArgumentsError": BuiltinWrongNumArgumentsError,
"InvalidOperatorError": BuiltinInvalidOperatorError,
"IndexOutOfBoundsError": BuiltinIndexOutOfBoundsError,
"NotIterableError": BuiltinNotIterableError,
"NotIndexableError": BuiltinNotIndexableError,
"NotIndexAssignableError": BuiltinNotIndexAssignableError,
"NotCallableError": BuiltinNotCallableError,
"NotImplementedError": BuiltinNotImplementedError,
"ZeroDivisionError": BuiltinZeroDivisionError,
"TypeError": BuiltinTypeError,
":makeArray": BuiltinMakeArray,
"cap": BuiltinCap,
}
// BuiltinObjects is list of builtins, exported for REPL.
var BuiltinObjects = [...]Object{
// :makeArray is a private builtin function to help destructuring array assignments
BuiltinMakeArray: &BuiltinFunction{
Name: ":makeArray",
Value: funcPiOROe(builtinMakeArrayFunc),
ValueEx: funcPiOROeEx(builtinMakeArrayFunc),
},
BuiltinAppend: &BuiltinFunction{
Name: "append",
Value: callExAdapter(builtinAppendFunc),
ValueEx: builtinAppendFunc,
},
BuiltinDelete: &BuiltinFunction{
Name: "delete",
Value: funcPOsRe(builtinDeleteFunc),
ValueEx: funcPOsReEx(builtinDeleteFunc),
},
BuiltinCopy: &BuiltinFunction{
Name: "copy",
Value: funcPORO(builtinCopyFunc),
ValueEx: funcPOROEx(builtinCopyFunc),
},
BuiltinRepeat: &BuiltinFunction{
Name: "repeat",
Value: funcPOiROe(builtinRepeatFunc),
ValueEx: funcPOiROeEx(builtinRepeatFunc),
},
BuiltinContains: &BuiltinFunction{
Name: "contains",
Value: funcPOOROe(builtinContainsFunc),
ValueEx: funcPOOROeEx(builtinContainsFunc),
},
BuiltinLen: &BuiltinFunction{
Name: "len",
Value: funcPORO(builtinLenFunc),
ValueEx: funcPOROEx(builtinLenFunc),
},
BuiltinCap: &BuiltinFunction{
Name: "cap",
Value: funcPORO(builtinCapFunc),
ValueEx: funcPOROEx(builtinCapFunc),
},
BuiltinSort: &BuiltinFunction{
Name: "sort",
Value: funcPOROe(builtinSortFunc),
ValueEx: funcPOROeEx(builtinSortFunc),
},
BuiltinSortReverse: &BuiltinFunction{
Name: "sortReverse",
Value: funcPOROe(builtinSortReverseFunc),
ValueEx: funcPOROeEx(builtinSortReverseFunc),
},
BuiltinError: &BuiltinFunction{
Name: "error",
Value: funcPORO(builtinErrorFunc),
ValueEx: funcPOROEx(builtinErrorFunc),
},
BuiltinTypeName: &BuiltinFunction{
Name: "typeName",
Value: funcPORO(builtinTypeNameFunc),
ValueEx: funcPOROEx(builtinTypeNameFunc),
},
BuiltinBool: &BuiltinFunction{
Name: "bool",
Value: funcPORO(builtinBoolFunc),
ValueEx: funcPOROEx(builtinBoolFunc),
},
BuiltinInt: &BuiltinFunction{
Name: "int",
Value: funcPi64RO(builtinIntFunc),
ValueEx: funcPi64ROEx(builtinIntFunc),
},
BuiltinUint: &BuiltinFunction{
Name: "uint",
Value: funcPu64RO(builtinUintFunc),
ValueEx: funcPu64ROEx(builtinUintFunc),
},
BuiltinFloat: &BuiltinFunction{
Name: "float",
Value: funcPf64RO(builtinFloatFunc),
ValueEx: funcPf64ROEx(builtinFloatFunc),
},
BuiltinChar: &BuiltinFunction{
Name: "char",
Value: funcPOROe(builtinCharFunc),
ValueEx: funcPOROeEx(builtinCharFunc),
},
BuiltinString: &BuiltinFunction{
Name: "string",
Value: funcPORO(builtinStringFunc),
ValueEx: funcPOROEx(builtinStringFunc),
},
BuiltinBytes: &BuiltinFunction{
Name: "bytes",
Value: callExAdapter(builtinBytesFunc),
ValueEx: builtinBytesFunc,
},
BuiltinChars: &BuiltinFunction{
Name: "chars",
Value: funcPOROe(builtinCharsFunc),
ValueEx: funcPOROeEx(builtinCharsFunc),
},
BuiltinPrintf: &BuiltinFunction{
Name: "printf",
Value: callExAdapter(builtinPrintfFunc),
ValueEx: builtinPrintfFunc,
},
BuiltinPrintln: &BuiltinFunction{
Name: "println",
Value: callExAdapter(builtinPrintlnFunc),
ValueEx: builtinPrintlnFunc,
},
BuiltinSprintf: &BuiltinFunction{
Name: "sprintf",
Value: callExAdapter(builtinSprintfFunc),
ValueEx: builtinSprintfFunc,
},
BuiltinGlobals: &BuiltinFunction{
Name: "globals",
Value: callExAdapter(builtinGlobalsFunc),
ValueEx: builtinGlobalsFunc,
},
BuiltinIsError: &BuiltinFunction{
Name: "isError",
Value: callExAdapter(builtinIsErrorFunc),
ValueEx: builtinIsErrorFunc,
},
BuiltinIsInt: &BuiltinFunction{
Name: "isInt",
Value: funcPORO(builtinIsIntFunc),
ValueEx: funcPOROEx(builtinIsIntFunc),
},
BuiltinIsUint: &BuiltinFunction{
Name: "isUint",
Value: funcPORO(builtinIsUintFunc),
ValueEx: funcPOROEx(builtinIsUintFunc),
},
BuiltinIsFloat: &BuiltinFunction{
Name: "isFloat",
Value: funcPORO(builtinIsFloatFunc),
ValueEx: funcPOROEx(builtinIsFloatFunc),
},
BuiltinIsChar: &BuiltinFunction{
Name: "isChar",
Value: funcPORO(builtinIsCharFunc),
ValueEx: funcPOROEx(builtinIsCharFunc),
},
BuiltinIsBool: &BuiltinFunction{
Name: "isBool",
Value: funcPORO(builtinIsBoolFunc),
ValueEx: funcPOROEx(builtinIsBoolFunc),
},
BuiltinIsString: &BuiltinFunction{
Name: "isString",
Value: funcPORO(builtinIsStringFunc),
ValueEx: funcPOROEx(builtinIsStringFunc),
},
BuiltinIsBytes: &BuiltinFunction{
Name: "isBytes",
Value: funcPORO(builtinIsBytesFunc),
ValueEx: funcPOROEx(builtinIsBytesFunc),
},
BuiltinIsMap: &BuiltinFunction{
Name: "isMap",
Value: funcPORO(builtinIsMapFunc),
ValueEx: funcPOROEx(builtinIsMapFunc),
},
BuiltinIsSyncMap: &BuiltinFunction{
Name: "isSyncMap",
Value: funcPORO(builtinIsSyncMapFunc),
ValueEx: funcPOROEx(builtinIsSyncMapFunc),
},
BuiltinIsArray: &BuiltinFunction{
Name: "isArray",
Value: funcPORO(builtinIsArrayFunc),
ValueEx: funcPOROEx(builtinIsArrayFunc),
},
BuiltinIsUndefined: &BuiltinFunction{
Name: "isUndefined",
Value: funcPORO(builtinIsUndefinedFunc),
ValueEx: funcPOROEx(builtinIsUndefinedFunc),
},
BuiltinIsFunction: &BuiltinFunction{
Name: "isFunction",
Value: funcPORO(builtinIsFunctionFunc),
ValueEx: funcPOROEx(builtinIsFunctionFunc),
},
BuiltinIsCallable: &BuiltinFunction{
Name: "isCallable",
Value: funcPORO(builtinIsCallableFunc),
//ValueEx: funcPOROEx(builtinIsCallableFunc),
},
BuiltinIsIterable: &BuiltinFunction{
Name: "isIterable",
Value: funcPORO(builtinIsIterableFunc),
ValueEx: funcPOROEx(builtinIsIterableFunc),
},
BuiltinWrongNumArgumentsError: ErrWrongNumArguments,
BuiltinInvalidOperatorError: ErrInvalidOperator,
BuiltinIndexOutOfBoundsError: ErrIndexOutOfBounds,
BuiltinNotIterableError: ErrNotIterable,
BuiltinNotIndexableError: ErrNotIndexable,
BuiltinNotIndexAssignableError: ErrNotIndexAssignable,
BuiltinNotCallableError: ErrNotCallable,
BuiltinNotImplementedError: ErrNotImplemented,
BuiltinZeroDivisionError: ErrZeroDivision,
BuiltinTypeError: ErrType,
}
func builtinMakeArrayFunc(n int, arg Object) (Object, error) {
if n <= 0 {
return arg, nil
}
arr, ok := arg.(Array)
if !ok {
ret := make(Array, n)
for i := 1; i < n; i++ {
ret[i] = Undefined
}
ret[0] = arg
return ret, nil
}
length := len(arr)
if n <= length {
return arr[:n], nil
}
ret := make(Array, n)
x := copy(ret, arr)
for i := x; i < n; i++ {
ret[i] = Undefined
}
return ret, nil
}
func builtinAppendFunc(c Call) (Object, error) {
target, ok := c.shift()
if !ok {
return Undefined, ErrWrongNumArguments.NewError("want>=1 got=0")
}
switch obj := target.(type) {
case Array:
obj = append(obj, c.args...)
obj = append(obj, c.vargs...)
return obj, nil
case Bytes:
n := 0
for _, args := range [][]Object{c.args, c.vargs} {
for _, v := range args {
n++
switch vv := v.(type) {
case Int:
obj = append(obj, byte(vv))
case Uint:
obj = append(obj, byte(vv))
case Char:
obj = append(obj, byte(vv))
default:
return Undefined, NewArgumentTypeError(
strconv.Itoa(n),
"int|uint|char",
vv.TypeName(),
)
}
}
}
return obj, nil
case *UndefinedType:
ret := make(Array, 0, c.Len())
ret = append(ret, c.args...)
ret = append(ret, c.vargs...)
return ret, nil
default:
return Undefined, NewArgumentTypeError(
"1st",
"array",
obj.TypeName(),
)
}
}
func builtinDeleteFunc(arg Object, key string) (err error) {
if v, ok := arg.(IndexDeleter); ok {
err = v.IndexDelete(String(key))
} else {
err = NewArgumentTypeError(
"1st",
"map|syncMap|IndexDeleter",
arg.TypeName(),
)
}
return
}
func builtinCopyFunc(arg Object) Object {
if v, ok := arg.(Copier); ok {
return v.Copy()
}
return arg
}
func builtinRepeatFunc(arg Object, count int) (ret Object, err error) {
if count < 0 {
return nil, NewArgumentTypeError(
"2nd",
"non-negative integer",
"negative integer",
)
}
switch v := arg.(type) {
case Array:
out := make(Array, 0, len(v)*count)
for i := 0; i < count; i++ {
out = append(out, v...)
}
ret = out
case String:
ret = String(strings.Repeat(string(v), count))
case Bytes:
ret = Bytes(bytes.Repeat(v, count))
default:
err = NewArgumentTypeError(
"1st",
"array|string|bytes",
arg.TypeName(),
)
}
return
}
func builtinContainsFunc(arg0, arg1 Object) (Object, error) {
var ok bool
switch obj := arg0.(type) {
case Map:
_, ok = obj[arg1.String()]
case *SyncMap:
_, ok = obj.Get(arg1.String())
case Array:
for _, item := range obj {
if item.Equal(arg1) {
ok = true
break
}
}
case String:
ok = strings.Contains(string(obj), arg1.String())
case Bytes:
switch v := arg1.(type) {
case Int:
ok = bytes.Contains(obj, []byte{byte(v)})
case Uint:
ok = bytes.Contains(obj, []byte{byte(v)})
case Char:
ok = bytes.Contains(obj, []byte{byte(v)})
case String:
ok = bytes.Contains(obj, []byte(v))
case Bytes:
ok = bytes.Contains(obj, v)
default:
return Undefined, NewArgumentTypeError(
"2nd",
"int|uint|string|char|bytes",
arg1.TypeName(),
)
}
case *UndefinedType:
default:
return Undefined, NewArgumentTypeError(
"1st",
"map|array|string|bytes",
arg0.TypeName(),
)
}
return Bool(ok), nil
}
func builtinLenFunc(arg Object) Object {
var n int
if v, ok := arg.(LengthGetter); ok {
n = v.Len()
}
return Int(n)
}
func builtinCapFunc(arg Object) Object {
var n int
switch v := arg.(type) {
case Array:
n = cap(v)
case Bytes:
n = cap(v)
}
return Int(n)
}
func builtinSortFunc(arg Object) (ret Object, err error) {
switch obj := arg.(type) {
case Array:
sort.Slice(obj, func(i, j int) bool {
v, e := obj[i].BinaryOp(token.Less, obj[j])
if e != nil && err == nil {
err = e
return false
}
if v != nil {
return !v.IsFalsy()
}
return false
})
ret = arg
case String:
s := []rune(obj)
sort.Slice(s, func(i, j int) bool {
return s[i] < s[j]
})
ret = String(s)
case Bytes:
sort.Slice(obj, func(i, j int) bool {
return obj[i] < obj[j]
})
ret = arg
case *UndefinedType:
ret = Undefined
default:
ret = Undefined
err = NewArgumentTypeError(
"1st",
"array|string|bytes",
arg.TypeName(),
)
}
return
}
func builtinSortReverseFunc(arg Object) (Object, error) {
switch obj := arg.(type) {
case Array:
var err error
sort.Slice(obj, func(i, j int) bool {
v, e := obj[j].BinaryOp(token.Less, obj[i])
if e != nil && err == nil {
err = e
return false
}
if v != nil {
return !v.IsFalsy()
}
return false
})
if err != nil {
return nil, err
}
return obj, nil
case String:
s := []rune(obj)
sort.Slice(s, func(i, j int) bool {
return s[j] < s[i]
})
return String(s), nil
case Bytes:
sort.Slice(obj, func(i, j int) bool {
return obj[j] < obj[i]
})
return obj, nil
case *UndefinedType:
return Undefined, nil
}
return Undefined, NewArgumentTypeError(
"1st",
"array|string|bytes",
arg.TypeName(),
)
}
func builtinErrorFunc(arg Object) Object {
return &Error{Name: "error", Message: arg.String()}
}
func builtinTypeNameFunc(arg Object) Object { return String(arg.TypeName()) }
func builtinBoolFunc(arg Object) Object { return Bool(!arg.IsFalsy()) }
func builtinIntFunc(v int64) Object { return Int(v) }
func builtinUintFunc(v uint64) Object { return Uint(v) }
func builtinFloatFunc(v float64) Object { return Float(v) }
func builtinCharFunc(arg Object) (Object, error) {
v, ok := ToChar(arg)
if ok && v != utf8.RuneError {
return v, nil
}
if v == utf8.RuneError || arg == Undefined {
return Undefined, nil
}
return Undefined, NewArgumentTypeError(
"1st",
"numeric|string|bool",
arg.TypeName(),
)
}
func builtinStringFunc(arg Object) Object { return String(arg.String()) }
func builtinBytesFunc(c Call) (Object, error) {
size := c.Len()
switch size {
case 0:
return Bytes{}, nil
case 1:
if v, ok := ToBytes(c.Get(0)); ok {
return v, nil
}
}
out := make(Bytes, 0, size)
for _, args := range [][]Object{c.args, c.vargs} {
for i, obj := range args {
switch v := obj.(type) {
case Int:
out = append(out, byte(v))
case Uint:
out = append(out, byte(v))
case Char:
out = append(out, byte(v))
default:
return Undefined, NewArgumentTypeError(
strconv.Itoa(i+1),
"int|uint|char",
args[i].TypeName(),
)
}
}
}
return out, nil
}
func builtinCharsFunc(arg Object) (ret Object, err error) {
switch obj := arg.(type) {
case String:
s := string(obj)
ret = make(Array, 0, utf8.RuneCountInString(s))
sz := len(obj)
i := 0
for i < sz {
r, w := utf8.DecodeRuneInString(s[i:])
if r == utf8.RuneError {
return Undefined, nil
}
ret = append(ret.(Array), Char(r))
i += w
}
case Bytes:
ret = make(Array, 0, utf8.RuneCount(obj))
sz := len(obj)
i := 0
for i < sz {
r, w := utf8.DecodeRune(obj[i:])
if r == utf8.RuneError {
return Undefined, nil
}
ret = append(ret.(Array), Char(r))
i += w
}
default:
ret = Undefined
err = NewArgumentTypeError(
"1st",
"string|bytes",
arg.TypeName(),
)
}
return
}
func builtinPrintfFunc(c Call) (ret Object, err error) {
ret = Undefined
switch size := c.Len(); size {
case 0:
err = ErrWrongNumArguments.NewError("want>=1 got=0")
case 1:
_, err = fmt.Fprint(PrintWriter, c.Get(0).String())
default:
format, _ := c.shift()
vargs := make([]interface{}, 0, size-1)
for i := 0; i < size-1; i++ {
vargs = append(vargs, c.Get(i))
}
_, err = fmt.Fprintf(PrintWriter, format.String(), vargs...)
}
return
}
func builtinPrintlnFunc(c Call) (ret Object, err error) {
ret = Undefined
switch size := c.Len(); size {
case 0:
_, err = fmt.Fprintln(PrintWriter)
case 1:
_, err = fmt.Fprintln(PrintWriter, c.Get(0))
default:
vargs := make([]interface{}, 0, size)
for i := 0; i < size; i++ {
vargs = append(vargs, c.Get(i))
}
_, err = fmt.Fprintln(PrintWriter, vargs...)
}
return
}
func builtinSprintfFunc(c Call) (ret Object, err error) {
ret = Undefined
switch size := c.Len(); size {
case 0:
err = ErrWrongNumArguments.NewError("want>=1 got=0")
case 1:
ret = String(c.Get(0).String())
default:
format, _ := c.shift()
vargs := make([]interface{}, 0, size-1)
for i := 0; i < size-1; i++ {
vargs = append(vargs, c.Get(i))
}
ret = String(fmt.Sprintf(format.String(), vargs...))
}
return
}
func builtinGlobalsFunc(c Call) (Object, error) {
return c.VM().GetGlobals(), nil
}
func builtinIsErrorFunc(c Call) (ret Object, err error) {
ret = False
switch c.Len() {
case 1:
// We have Error, BuiltinError and also user defined error types.
if _, ok := c.Get(0).(error); ok {
ret = True
}
case 2:
if err, ok := c.Get(0).(error); ok {
if target, ok := c.Get(1).(error); ok {
ret = Bool(errors.Is(err, target))
}
}
default:
err = ErrWrongNumArguments.NewError(
"want=1..2 got=", strconv.Itoa(c.Len()))
}
return
}
func builtinIsIntFunc(arg Object) Object {
_, ok := arg.(Int)
return Bool(ok)
}
func builtinIsUintFunc(arg Object) Object {
_, ok := arg.(Uint)
return Bool(ok)
}
func builtinIsFloatFunc(arg Object) Object {
_, ok := arg.(Float)
return Bool(ok)
}
func builtinIsCharFunc(arg Object) Object {
_, ok := arg.(Char)
return Bool(ok)
}
func builtinIsBoolFunc(arg Object) Object {
_, ok := arg.(Bool)
return Bool(ok)
}
func builtinIsStringFunc(arg Object) Object {
_, ok := arg.(String)
return Bool(ok)
}
func builtinIsBytesFunc(arg Object) Object {
_, ok := arg.(Bytes)
return Bool(ok)
}
func builtinIsMapFunc(arg Object) Object {
_, ok := arg.(Map)
return Bool(ok)
}
func builtinIsSyncMapFunc(arg Object) Object {
_, ok := arg.(*SyncMap)
return Bool(ok)
}
func builtinIsArrayFunc(arg Object) Object {
_, ok := arg.(Array)
return Bool(ok)
}
func builtinIsUndefinedFunc(arg Object) Object {
_, ok := arg.(*UndefinedType)
return Bool(ok)
}
func builtinIsFunctionFunc(arg Object) Object {
_, ok := arg.(*CompiledFunction)
if ok {
return True
}
_, ok = arg.(*BuiltinFunction)
if ok {
return True
}
_, ok = arg.(*Function)
return Bool(ok)
}
func builtinIsCallableFunc(arg Object) Object { return Bool(arg.CanCall()) }
func builtinIsIterableFunc(arg Object) Object { return Bool(arg.CanIterate()) }
func callExAdapter(fn CallableExFunc) CallableFunc {
return func(args ...Object) (Object, error) {
return fn(Call{args: args})
}
}