stdlib

package
v0.33.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 23, 2026 License: MIT Imports: 36 Imported by: 1

Documentation

Index

Constants

View Source
const (
	RE_IGNORECASE = 2  // re.I or re.IGNORECASE
	RE_MULTILINE  = 8  // re.M or re.MULTILINE
	RE_DOTALL     = 16 // re.S or re.DOTALL
)

Flag constants matching Python's re module

View Source
const (
	ContextlibLibraryName  = "contextlib"
	DifflibLibraryName     = "difflib"
	IOLibraryName          = "io"
	JSONLibraryName        = "json"
	ReLibraryName          = "re"
	TimeLibraryName        = "time"
	DatetimeLibraryName    = "datetime"
	MathLibraryName        = "math"
	Base64LibraryName      = "base64"
	HashlibLibraryName     = "hashlib"
	HmacLibraryName        = "hmac"
	RandomLibraryName      = "random"
	URLLibLibraryName      = "urllib"
	URLParseLibraryName    = "urllib.parse"
	StringLibraryName      = "string"
	UUIDLibraryName        = "uuid"
	HTMLLibraryName        = "html"
	StatisticsLibraryName  = "statistics"
	FunctoolsLibraryName   = "functools"
	TextwrapLibraryName    = "textwrap"
	PlatformLibraryName    = "platform"
	ItertoolsLibraryName   = "itertools"
	CollectionsLibraryName = "collections"
	EnumLibraryName        = "enum"
	DataclassesLibraryName = "dataclasses"
	TypingLibraryName      = "typing"
)

Library names as constants for easy reference

View Source
const CopyLibraryName = "copy"
View Source
const MsgpackLibraryName = "msgpack"

MsgpackLibraryName is the import name for the MessagePack library.

Variables

View Source
var (
	DatetimeClass *object.Class
	DateClass     *object.Class
)

Forward declarations for classes (set in init)

View Source
var (
	HashlibSHA256Builtin = makeHashBuiltin("sha256", `sha256([data]) - Create a SHA-256 hash object

Returns a hash object. Call .hexdigest() or .digest() to get the result, or
.update() to feed more data.

Example:
  import hashlib
  print(hashlib.sha256("hello").hexdigest())`)
	HashlibSHA1Builtin = makeHashBuiltin("sha1", `sha1([data]) - Create a SHA-1 hash object

Returns a hash object. Call .hexdigest() or .digest() to get the result.`)
	HashlibMD5Builtin = makeHashBuiltin("md5", `md5([data]) - Create an MD5 hash object

Returns a hash object. Call .hexdigest() or .digest() to get the result.`)
)

Package-level references to the constructor builtins so that hmac can identify them when passed as the digestmod argument (e.g. hashlib.sha256).

View Source
var AutoMarker = object.NewSentinel("auto", "auto")

AutoMarker is the sentinel returned by auto(); the class compiler replaces it with a sequential value.

View Source
var Base64Library = object.NewLibrary(Base64LibraryName, map[string]*object.Builtin{
	"b64encode": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}

			data, errObj := coerceToBytes(args[0])
			if errObj != nil {
				return errObj
			}

			if errObj := object.CheckAllocFor("Base64 result", int64(base64.StdEncoding.EncodedLen(len(data)))); errObj != nil {
				return errObj
			}
			encoded := base64.StdEncoding.EncodeToString(data)
			return object.NewBytesFromString(encoded)
		},
		HelpText: `b64encode(s) - Encode bytes-like object to Base64

Accepts a Bytes value or a String (UTF-8 encoded) and returns the Base64
representation as a string.`,
	},
	"b64decode": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			var encoded string
			switch v := args[0].(type) {
			case *object.String:
				encoded = v.StringValue()
			case *object.Bytes:
				encoded = string(v.BytesValue())
			default:
				return errors.NewTypeError("STRING or BYTES", args[0].Type().String())
			}
			decoded, decodeErr := base64.StdEncoding.DecodeString(encoded)
			if decodeErr != nil {
				return errors.NewError("base64 decode error: %s", decodeErr.Error())
			}
			return object.NewBytes(decoded)
		},
		HelpText: `b64decode(s) - Decode a Base64 encoded string

Returns the decoded bytes as a Bytes value. Use .decode() on the result to
obtain a string when the underlying data is text.`,
	},
}, nil, "Base64 encoding and decoding library")
View Source
var CollectionsLibrary = object.NewLibrary(CollectionsLibraryName, map[string]*object.Builtin{
	"Counter": {
		Fn: counterConstructor,
		HelpText: `Counter([iterable_or_mapping], **kwargs) - Count elements

A dict of element -> count, as in Python. Missing elements count 0.

Example:
  c = collections.Counter([1, 1, 2, 3, 3, 3])
  c[3]                -> 3
  c[4]                -> 0
  c.most_common(2)    -> [(3, 3), (1, 2)]
  c.update([1]); c.total() -> 7`,
	},
	"most_common": {
		Fn:       counterMostCommonFn,
		HelpText: `most_common(counter[, n]) - Same as counter.most_common(n)`,
	},

	"defaultdict": defaultDictBuiltin,
	"DefaultDict": defaultDictBuiltin,

	"OrderedDict": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			od := object.NewDict()

			if len(args) > 1 {
				return errors.NewTypeErrorTagged("OrderedDict expected at most 1 argument, got %d", len(args))
			}

			if len(args) == 1 {
				switch arg := args[0].(type) {
				case *object.List:
					if err := defaultDictAddPairs(od, arg.Elements); err != nil {
						return err
					}
				case *object.Tuple:
					if err := defaultDictAddPairs(od, arg.Elements); err != nil {
						return err
					}
				case *object.Dict:
					od.StoreFrom(arg)
				default:
					return errors.NewTypeError("list of tuples or dict", args[0].Type().String())
				}
			}

			names := kwargs.Keys()
			sort.Strings(names)
			for _, name := range names {
				od.SetByString(name, kwargs.Get(name))
			}
			return od
		},
		HelpText: `OrderedDict([items]) - Dict that remembers insertion order

Creates a dict that maintains insertion order.
Note: Scriptling dicts already maintain order, so this is equivalent to dict().

Example:
  od = collections.OrderedDict([("a", 1), ("b", 2)])`,
	},
	"namedtuple": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			typename, ok := args[0].(*object.String)
			if !ok {
				return errors.NewTypeError("STRING", args[0].Type().String())
			}

			var fieldNames []string
			switch fn := args[1].(type) {
			case *object.List:
				for _, elem := range fn.Elements {
					if s, ok := elem.(*object.String); ok {
						fieldNames = append(fieldNames, s.StringValue())
					} else {
						return errors.NewError("field names must be strings")
					}
				}
			case *object.Tuple:
				for _, elem := range fn.Elements {
					if s, ok := elem.(*object.String); ok {
						fieldNames = append(fieldNames, s.StringValue())
					} else {
						return errors.NewError("field names must be strings")
					}
				}
			case *object.String:

				fields := strings.FieldsFunc(fn.StringValue(), func(r rune) bool {
					return r == ' ' || r == ','
				})
				for _, f := range fields {
					f = strings.TrimSpace(f)
					if f != "" {
						fieldNames = append(fieldNames, f)
					}
				}
			default:
				return errors.NewTypeError("list, tuple, or string", args[1].Type().String())
			}

			// Create a NamedTuple class. ntClass is declared before the
			// method closures that reference it (_replace) and assigned once
			// the method map is complete.
			var ntClass *object.Class
			methods := make(map[string]object.Object)

			// defaults=(...) applies to the rightmost fields, as in Python.
			var defaults []object.Object
			if d := kwargs.Get("defaults"); d != nil {
				switch dv := d.(type) {
				case *object.List:
					defaults = dv.Elements
				case *object.Tuple:
					defaults = dv.Elements
				case *object.Null:
				default:
					return errors.NewTypeError("list or tuple", d.Type().String())
				}
				if len(defaults) > len(fieldNames) {
					return errors.NewTypeErrorTagged("Got more default values than field names")
				}
			}

			methods["__init__"] = &object.Builtin{
				Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
					positional := args[1:]
					if len(positional) > len(fieldNames) {
						return errors.NewTypeErrorTagged("%s() takes %d positional arguments but %d were given", typename.StringValue(), len(fieldNames), len(positional))
					}
					values := make([]object.Object, len(fieldNames))
					copy(values, positional)
					for name, v := range kwargs.Kwargs {
						idx := -1
						for i, fn := range fieldNames {
							if fn == name {
								idx = i
								break
							}
						}
						if idx < 0 {
							return errors.NewTypeErrorTagged("%s() got an unexpected keyword argument '%s'", typename.StringValue(), name)
						}
						if values[idx] != nil {
							return errors.NewTypeErrorTagged("%s() got multiple values for argument '%s'", typename.StringValue(), name)
						}
						values[idx] = v
					}
					for i := range values {
						if values[i] != nil {
							continue
						}
						if di := i - (len(fieldNames) - len(defaults)); di >= 0 {
							values[i] = defaults[di]
							continue
						}
						return errors.NewTypeErrorTagged("%s() missing required argument: '%s'", typename.StringValue(), fieldNames[i])
					}
					args = append([]object.Object{args[0]}, values...)
					nt := args[0].(*object.Instance)

					for i, name := range fieldNames {
						nt.SetField(name, args[i+1])
					}
					nt.SetField("__typename__", typename)

					fieldNameObjs := make([]object.Object, len(fieldNames))
					for i, name := range fieldNames {
						fieldNameObjs[i] = object.NewString(name)
					}
					object.TrackSlice(fieldNameObjs)
					fieldsTuple := &object.Tuple{Elements: fieldNameObjs}
					nt.SetField("__fields__", fieldsTuple)
					nt.SetField("_fields", fieldsTuple)

					parts := make([]string, 0, len(fieldNames))
					for i, name := range fieldNames {
						parts = append(parts, name+"="+reprValue(args[i+1]))
					}
					nt.SetField("__str_repr__", object.NewString(typename.StringValue()+"("+strings.Join(parts, ", ")+")"))
					return &object.Null{}
				},
			}

			methods["__getitem__"] = &object.Builtin{
				Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
					if err := errors.ExactArgs(args, 2); err != nil {
						return err
					}
					nt := args[0].(*object.Instance)
					if sl, ok := args[1].(*object.Slice); ok {
						vals := make([]object.Object, len(fieldNames))
						for i, name := range fieldNames {
							vals[i], _ = nt.GetField(name)
						}
						return &object.Tuple{Elements: sliceElements(vals, sl)}
					}
					if idx, ok := args[1].(*object.Integer); ok {

						i := int(idx.IntValue())
						if i < 0 {
							i += len(fieldNames)
						}
						if i < 0 || i >= len(fieldNames) {
							return &object.Exception{
								Message:       "tuple index out of range",
								ExceptionType: object.ExceptionTypeIndexError,
								Raised:        true,
							}
						}
						if v, exists := nt.GetField(fieldNames[i]); exists {
							return v
						}
						return &object.Null{}
					}
					key := args[1].Inspect()

					if key == "__typename__" || key == "__fields__" {
						return &object.Null{}
					}
					if value, exists := nt.GetField(key); exists {
						return value
					}
					return &object.Exception{
						Message:       "tuple index out of range",
						ExceptionType: object.ExceptionTypeIndexError,
						Raised:        true,
					}
				},
				HelpText: `__getitem__(key) - Get field value (supports nt[key] syntax)`,
			}

			methods["__len__"] = &object.Builtin{
				Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
					if err := errors.ExactArgs(args, 1); err != nil {
						return err
					}
					return object.NewInteger(int64(len(fieldNames)))
				},
				HelpText: `__len__() - The field count`,
			}

			methods["__iter__"] = &object.Builtin{
				Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
					nt := args[0].(*object.Instance)
					values := make([]object.Object, 0, len(fieldNames))
					for _, name := range fieldNames {
						if v, exists := nt.GetField(name); exists {
							values = append(values, v)
						} else {
							values = append(values, &object.Null{})
						}
					}
					i := 0
					return object.NewIterator(func() (object.Object, bool) {
						if i >= len(values) {
							return nil, false
						}
						v := values[i]
						i++
						return v, true
					})
				},
				HelpText: `__iter__() - Iterate field values`,
			}

			methods["__repr__"] = &object.Builtin{
				Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
					nt := args[0].(*object.Instance)
					parts := make([]string, 0, len(fieldNames))
					for _, name := range fieldNames {
						if v, exists := nt.GetField(name); exists {
							parts = append(parts, name+"="+reprValue(v))
						}
					}
					return object.NewString(typename.StringValue() + "(" + strings.Join(parts, ", ") + ")")
				},
			}

			methods["_replace"] = &object.Builtin{
				Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
					nt := args[0].(*object.Instance)
					fields := nt.FieldsSnapshot()
					for name, v := range kwargs.Kwargs {
						known := false
						for _, fn := range fieldNames {
							if fn == name {
								known = true
								break
							}
						}
						if !known {
							return errors.NewValueError("Got unexpected field names: %s", name)
						}
						fields[name] = v
					}

					parts := make([]string, 0, len(fieldNames))
					for _, name := range fieldNames {
						parts = append(parts, name+"="+reprValue(fields[name]))
					}
					fields["__str_repr__"] = object.NewString(typename.StringValue() + "(" + strings.Join(parts, ", ") + ")")
					return object.NewInstanceWithFields(ntClass, fields)
				},
				HelpText: `_replace(**changes) - Return a new instance with fields replaced`,
			}

			methods["_asdict"] = &object.Builtin{
				Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
					nt := args[0].(*object.Instance)
					d := &object.Dict{Pairs: make(map[string]object.DictPair, len(fieldNames))}
					for _, name := range fieldNames {
						if v, exists := nt.GetField(name); exists {
							d.SetByString(name, v)
						}
					}
					return d
				},
				HelpText: `_asdict() - Return fields as a dict`,
			}

			fieldValues := func(nt *object.Instance) []object.Object {
				vals := make([]object.Object, len(fieldNames))
				for i, name := range fieldNames {
					v, _ := nt.GetField(name)
					if v == nil {
						v = &object.Null{}
					}
					vals[i] = v
				}
				return vals
			}

			otherValues := func(o object.Object) ([]object.Object, bool) {
				switch ov := o.(type) {
				case *object.Tuple:
					return ov.Elements, true
				case *object.Instance:
					if tn, has := ov.GetField("__typename__"); has && tn != nil {
						if fs, ok := ov.GetField("__fields__"); ok {
							if ft, ok := fs.(*object.Tuple); ok {
								vals := make([]object.Object, len(ft.Elements))
								for i, f := range ft.Elements {
									vals[i], _ = ov.GetField(f.(*object.String).StringValue())
								}
								return vals, true
							}
						}
					}
				}
				return nil, false
			}

			methods["__eq__"] = &object.Builtin{
				Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
					if len(args) != 2 {
						return errors.NewArgumentError(len(args), 2)
					}
					ov, ok := otherValues(args[1])
					if !ok || len(ov) != len(fieldNames) {
						return object.NewBoolean(false)
					}
					for i, av := range fieldValues(args[0].(*object.Instance)) {
						if av.Inspect() != ov[i].Inspect() {
							return object.NewBoolean(false)
						}
					}
					return object.NewBoolean(true)
				},
				HelpText: `__eq__(other) - Compare field values`,
			}

			orderMethod := func(name string, keep func(cmp int) bool) object.Object {
				return &object.Builtin{
					Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
						if len(args) != 2 {
							return errors.NewArgumentError(len(args), 2)
						}
						ov, ok := otherValues(args[1])
						if !ok {
							return errors.NewTypeErrorTagged("'%s' not supported between instances of '%s' and '%s'", name, typename.StringValue(), args[1].Type().String())
						}
						cmp, okc := compareSequences(fieldValues(args[0].(*object.Instance)), ov, 0)
						if okc == compareTooDeep {
							return &object.Exception{
								Message:       "maximum recursion depth exceeded in comparison",
								ExceptionType: "RecursionError",
								Raised:        true,
							}
						}
						if okc != compareOK {
							return errors.NewTypeErrorTagged("'%s' not supported between values of incomparable types in '%s'", name, typename.StringValue())
						}
						return object.NewBoolean(keep(cmp))
					},
					HelpText: name + "(other) - Tuple ordering over the field values",
				}
			}
			methods["__lt__"] = orderMethod("<", func(c int) bool { return c < 0 })
			methods["__le__"] = orderMethod("<=", func(c int) bool { return c <= 0 })
			methods["__gt__"] = orderMethod(">", func(c int) bool { return c > 0 })
			methods["__ge__"] = orderMethod(">=", func(c int) bool { return c >= 0 })

			methods["__frozen__"] = object.NewBoolean(true)

			classFields := make([]object.Object, len(fieldNames))
			for i, name := range fieldNames {
				classFields[i] = object.NewString(name)
			}
			methods["_fields"] = &object.Tuple{Elements: classFields}

			methods["__hash__"] = &object.Builtin{
				Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
					if err := errors.ExactArgs(args, 1); err != nil {
						return err
					}
					nt := args[0].(*object.Instance)
					h := uint64(14695981039346656037)
					for _, name := range fieldNames {
						if v, exists := nt.GetField(name); exists {
							for _, c := range v.Inspect() {
								h ^= uint64(c)
								h *= 1099511628211
							}
						}
					}
					return object.NewInteger(int64(h))
				},
				HelpText: `__hash__() - Hash by field values`,
			}

			ntClass = &object.Class{
				Name:    typename.StringValue(),
				Methods: methods,
			}

			return ntClass
		},
		HelpText: `namedtuple(typename, field_names) - Create a named tuple class

Creates a class for creating named tuple instances with direct attribute access.

Example:
  Point = collections.namedtuple("Point", ["x", "y"])
  p = Point(1, 2)
  p.x      # 1 (direct attribute access)
  p["y"]   # 2 (dict-style access)
  p.x()    # Also works for backward compatibility`,
	},
	"ChainMap": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			chainMap := &object.Dict{Pairs: make(map[string]object.DictPair)}

			maps := make([]object.Object, len(args))
			for i, arg := range args {
				if _, ok := arg.(*object.Dict); !ok {
					return errors.NewTypeError("DICT", arg.Type().String())
				}
				maps[i] = arg
			}

			for i := len(args) - 1; i >= 0; i-- {
				d := args[i].(*object.Dict)
				chainMap.StoreFrom(d)
			}

			return chainMap
		},
		HelpText: `ChainMap(*maps) - Group multiple dicts for single lookup

Creates a single dict view over multiple dicts. First dict has priority.

Example:
  d1 = {"a": 1}
  d2 = {"b": 2, "a": 10}
  cm = collections.ChainMap(d1, d2)
  cm["a"]  # 1 (from d1)
  cm["b"]  # 2 (from d2)`,
	},
}, map[string]object.Object{
	"Counter": CounterClass,
	"deque":   DequeClass,
}, "Python-compatible collections library for specialized container datatypes")

CollectionsLibrary provides Python-like collections functions

View Source
var ContextlibLibrary = object.NewLibrary(ContextlibLibraryName, map[string]*object.Builtin{
	"suppress": {
		Fn: suppressNew,
		HelpText: `suppress(*exc_types) - Context manager that silently suppresses the given exception types.

If no exception types are given, all exceptions are suppressed.
Use Exception to suppress all standard exceptions.

Example:
    with contextlib.suppress(ValueError):
        int("not a number")  # silently ignored`,
	},
}, nil, "Utilities for common tasks involving the with statement")
View Source
var CopyLibrary = object.NewLibrary(CopyLibraryName, map[string]*object.Builtin{
	"copy": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			return shallowCopy(args[0])
		},
		HelpText: `copy(x) - Return a shallow copy of x

Same as the copy() builtin: a new list/dict/set/instance with the same
top-level contents. Nested objects are shared (use deepcopy).`,
	},
	"deepcopy": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if len(args) != 1 && len(args) != 2 {
				return errors.NewError("deepcopy() takes 1 or 2 arguments (%d given)", len(args))
			}
			return deepCopy(ctx, args[0], make(map[object.Object]object.Object))
		},
		HelpText: `deepcopy(x) - Return a deep copy of x

Recursively copies lists, tuples, dicts, sets and instances, preserving
shared references (two references to one list stay shared in the copy)
and handling cycles. A class can define __deepcopy__(self) to control its
own copying. Values Python treats as atomic — scalars, bytes, frozen
sets, functions, classes, library modules — are returned as-is.

Example:
  import copy
  original = {"items": [1, 2], "meta": {"on": True}}
  dup = copy.deepcopy(original)
  dup["items"].append(3)
  original["items"]        # [1, 2]: unchanged`,
	},
}, nil, "Shallow and deep copy operations, like Python's copy module")

CopyLibrary provides copy.copy (shallow) and copy.deepcopy, like Python's copy module. deepcopy is cycle-safe (an identity memo), honours a __deepcopy__ method on instances, and shares values Python considers atomic: scalars, bytes, frozen sets, functions, classes and modules.

View Source
var CounterClass = &object.Class{
	Name: "Counter",
	Methods: map[string]object.Object{
		"__init__": counterMethod("__init__([iterable_or_mapping], **kwargs)", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}
			_, d := counterSelf(args)
			if len(args) == 2 {
				if err := counterApply(ctx, d, args[1], 1); err != nil {
					return err
				}
			}
			if err := counterApplyKwargs(d, kwargs, 1); err != nil {
				return err
			}
			return &object.Null{}
		}),
		"__getitem__": counterMethod("c[key] - count of key, 0 when missing", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			_, d := counterSelf(args)
			return counterCount(d, args[1])
		}),
		"__setitem__": counterMethod("c[key] = count", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 3); err != nil {
				return err
			}
			if !object.IsHashable(args[1]) {
				return &object.Exception{Message: "unhashable type: '" + object.PythonTypeName(args[1]) + "'", ExceptionType: object.ExceptionTypeTypeError, Raised: true}
			}
			_, d := counterSelf(args)
			d.Store(object.DictKey(args[1]), args[1], args[2])
			return &object.Null{}
		}),
		"__delitem__": counterMethod("del c[key]", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			_, d := counterSelf(args)
			d.Delete(object.DictKey(args[1]))
			return &object.Null{}
		}),
		"__len__": counterMethod("len(c)", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			_, d := counterSelf(args)
			return object.NewInteger(int64(len(d.Pairs)))
		}),
		"__bool__": counterMethod("bool(c)", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			_, d := counterSelf(args)
			return object.NewBoolean(len(d.Pairs) > 0)
		}),
		"__contains__": counterMethod("key in c", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			_, d := counterSelf(args)
			_, ok := d.Pairs[object.DictKey(args[1])]
			return object.NewBoolean(ok)
		}),
		"__iter__": counterMethod("iter(c) - the keys", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			_, d := counterSelf(args)
			return d.CreateIterator()
		}),
		"__eq__": counterMethod("c == other - same counts", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			_, d := counterSelf(args)
			var other *object.Dict
			switch o := args[1].(type) {
			case *object.Instance:
				if o.Class != counterClassRef {
					return object.NewBoolean(false)
				}
				other = counterData(o)
			case *object.Dict:
				other = o
			default:
				return object.NewBoolean(false)
			}

			same := func(a, b *object.Dict) bool {
				for k, p := range a.Pairs {
					var q object.Object = counterZero
					if pair, ok := b.Pairs[k]; ok {
						q = pair.Value
					}
					x, xOK := countValue(p.Value)
					y, yOK := countValue(q)
					if xOK && yOK {
						if x != y {
							return false
						}
					} else if p.Value.Inspect() != q.Inspect() {
						return false
					}
				}
				return true
			}
			return object.NewBoolean(same(d, other) && same(other, d))
		}),
		"__repr__": counterMethod("repr(c) - Counter({'a': 2, 'b': 1})", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			_, d := counterSelf(args)
			entries := counterSorted(d)
			if len(entries) == 0 {
				return object.NewString("Counter()")
			}
			parts := make([]string, len(entries))
			for i, e := range entries {
				parts[i] = counterKeyRepr(e.key) + ": " + counterKeyRepr(e.count)
			}
			return object.NewString("Counter({" + strings.Join(parts, ", ") + "})")
		}),
		"__add__": counterOperator('+'),
		"__sub__": counterOperator('-'),
		"__or__":  counterOperator('|'),
		"__and__": counterOperator('&'),
		"keys": counterMethod("keys() - view of the elements", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			_, d := counterSelf(args)
			return &object.DictKeys{Dict: d}
		}),
		"values": counterMethod("values() - view of the counts", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			_, d := counterSelf(args)
			return &object.DictValues{Dict: d}
		}),
		"items": counterMethod("items() - view of (element, count) pairs", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			_, d := counterSelf(args)
			return &object.DictItems{Dict: d}
		}),
		"get": counterMethod("get(key, default=None)", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.RangeArgs(args, 2, 3); err != nil {
				return err
			}
			_, d := counterSelf(args)
			if pair, ok := d.Pairs[object.DictKey(args[1])]; ok {
				return pair.Value
			}
			if len(args) == 3 {
				return args[2]
			}
			return &object.Null{}
		}),
		"pop": counterMethod("pop(key[, default])", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.RangeArgs(args, 2, 3); err != nil {
				return err
			}
			_, d := counterSelf(args)
			k := object.DictKey(args[1])
			if pair, ok := d.Pairs[k]; ok {
				d.Delete(k)
				return pair.Value
			}
			if len(args) == 3 {
				return args[2]
			}
			return &object.Exception{Message: counterKeyRepr(args[1]), ExceptionType: object.ExceptionTypeKeyError, Raised: true}
		}),
		"update": counterMethod("update([iterable_or_mapping], **kwargs) - add counts", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}
			_, d := counterSelf(args)
			if len(args) == 2 {
				if err := counterApply(ctx, d, args[1], 1); err != nil {
					return err
				}
			}
			if err := counterApplyKwargs(d, kwargs, 1); err != nil {
				return err
			}
			return &object.Null{}
		}),
		"subtract": counterMethod("subtract([iterable_or_mapping], **kwargs) - subtract counts (may go negative)", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}
			_, d := counterSelf(args)
			if len(args) == 2 {
				if err := counterApply(ctx, d, args[1], -1); err != nil {
					return err
				}
			}
			if err := counterApplyKwargs(d, kwargs, -1); err != nil {
				return err
			}
			return &object.Null{}
		}),
		"total": counterMethod("total() - sum of all counts", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			_, d := counterSelf(args)
			var sum object.Object = counterZero
			for _, p := range d.OrderedPairs() {
				var err object.Object
				if sum, err = addCounts(sum, p.Value, 1); err != nil {
					return err
				}
			}
			return sum
		}),
		"most_common": counterMethod("most_common([n]) - (element, count) pairs, most common first", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}
			inst, _ := counterSelf(args)
			n, all, errObj := mostCommonArg(args, 1)
			if errObj != nil {
				return errObj
			}
			return counterMostCommon(inst, n, all)
		}),
		"elements": counterMethod("elements() - each element repeated by its count", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			_, d := counterSelf(args)
			var out []object.Object
			for _, e := range counterSorted(d) {
				n, isInt := e.count.(*object.Integer)
				if !isInt {
					if _, isFloat := e.count.(*object.Float); isFloat {
						return &object.Exception{Message: "'float' object cannot be interpreted as an integer", ExceptionType: object.ExceptionTypeTypeError, Raised: true}
					}
					continue
				}
				for i := int64(0); i < n.IntValue(); i++ {
					out = append(out, e.key)
				}
			}
			object.TrackSlice(out)
			return &object.List{Elements: out}
		}),
		"copy": counterMethod("copy() - a shallow copy", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			_, d := counterSelf(args)
			out := newCounter()
			od := counterData(out)
			od.StoreFrom(d)
			return out
		}),
		"clear": counterMethod("clear() - remove all elements", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			_, d := counterSelf(args)
			d.Clear()
			return &object.Null{}
		}),
	},
}
View Source
var DataclassesLibrary = object.NewLibrary(DataclassesLibraryName, map[string]*object.Builtin{
	"dataclass": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if len(args) > 0 {
				if cls, ok := args[0].(*object.Class); ok {
					return buildDataclass(cls, kwargs)
				}
			}
			opts := kwargs
			return &object.Builtin{
				Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
					if len(args) != 1 {
						return errors.NewError("dataclass decorator requires the class")
					}
					cls, ok := args[0].(*object.Class)
					if !ok {
						return errors.NewTypeError("class", args[0].Type().String())
					}
					merged := map[string]object.Object{}
					for k, v := range opts.Kwargs {
						merged[k] = v
					}
					for k, v := range kwargs.Kwargs {
						merged[k] = v
					}
					return buildDataclass(cls, object.NewKwargs(merged))
				},
				HelpText: "dataclass(options) - decorator factory",
			}
		},
		HelpText: `dataclass(cls) or dataclass(init=, repr=, eq=) - Generate __init__, __repr__ and __eq__ from annotated class fields

Fields are the class body's annotated names in source order; values are
defaults, and field(default=..., default_factory=...) customises them.`,
	},
	"field": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 0); err != nil {
				return err
			}
			inst := object.NewInstance(fieldSpecClass)
			def, hasDefault := kwargs.Kwargs["default"]
			if !hasDefault {
				def = dataclassesMissing
			}
			inst.SetField("default", def)
			factory, hasFactory := kwargs.Kwargs["default_factory"]
			if !hasFactory {
				factory = &object.Null{}
			}
			inst.SetField("default_factory", factory)
			return inst
		},
		HelpText: `field(default=..., default_factory=...) - Describe a dataclass field

default is the field's default value; default_factory is a zero-argument
callable producing one (for mutable defaults like lists).`,
	},
	"asdict": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			return dataclassesAsDict(args[0])
		},
		HelpText: "asdict(obj) - Recursively convert a dataclass instance to a dict",
	},
	"astuple": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			inst, ok := args[0].(*object.Instance)
			if !ok || inst.Class.FieldNames == nil {
				return errors.NewTypeError("dataclass instance", getTypeNameFor(args[0]))
			}
			elems := []object.Object{}
			for _, name := range inst.Class.FieldNames {
				if v, has := inst.GetField(name); has {
					elems = append(elems, v)
				} else {
					elems = append(elems, &object.Null{})
				}
			}
			object.TrackSlice(elems)
			return &object.Tuple{Elements: elems}
		},
		HelpText: "astuple(obj) - Convert a dataclass instance to a tuple of field values",
	},
}, map[string]object.Object{
	"MISSING": dataclassesMissing,
}, "Dataclass support (@dataclass, field, MISSING, asdict, astuple)")
View Source
var DatetimeLibrary = object.NewLibrary(DatetimeLibraryName,
	map[string]*object.Builtin{
		"timedelta": timedeltaBuiltinNew,
		"datetime":  datetimeConstructorBuiltin,
		"date":      dateConstructorBuiltin,
	},
	nil,
	"Date and time manipulation library.",
)

DatetimeLibrary is the main datetime module

View Source
var DequeClass = &object.Class{
	Name: "deque",
	Methods: map[string]object.Object{
		"__init__": &object.Builtin{Fn: dequeInit, HelpText: `deque([iterable[, maxlen]]) - Double-ended queue`},
		"maxlen": &object.Property{Getter: dequeMethod(0, func(ctx context.Context, d *dequeBuf, _ *object.Instance, _ []object.Object) object.Object {
			if d.maxlen < 0 {
				return &object.Null{}
			}
			return object.NewInteger(d.maxlen)
		})},
		"append": dequeMethod(1, func(ctx context.Context, d *dequeBuf, _ *object.Instance, args []object.Object) object.Object {
			if errObj := d.pushRight(args[0]); errObj != nil {
				return errObj
			}
			return &object.Null{}
		}),
		"appendleft": dequeMethod(1, func(ctx context.Context, d *dequeBuf, _ *object.Instance, args []object.Object) object.Object {
			if errObj := d.pushLeft(args[0]); errObj != nil {
				return errObj
			}
			return &object.Null{}
		}),
		"pop": dequeMethod(0, func(ctx context.Context, d *dequeBuf, _ *object.Instance, _ []object.Object) object.Object {
			if d.n == 0 {
				return emptyDequeError("pop")
			}
			return d.popRight()
		}),
		"popleft": dequeMethod(0, func(ctx context.Context, d *dequeBuf, _ *object.Instance, _ []object.Object) object.Object {
			if d.n == 0 {
				return emptyDequeError("pop")
			}
			return d.popLeft()
		}),
		"extend": dequeMethod(1, func(ctx context.Context, d *dequeBuf, _ *object.Instance, args []object.Object) object.Object {
			add, errObj := dequeValues(ctx, args[0])
			if errObj != nil {
				return errObj
			}
			if errObj := d.extendRight(add); errObj != nil {
				return errObj
			}
			return &object.Null{}
		}),
		"extendleft": dequeMethod(1, func(ctx context.Context, d *dequeBuf, _ *object.Instance, args []object.Object) object.Object {
			add, errObj := dequeValues(ctx, args[0])
			if errObj != nil {
				return errObj
			}
			if errObj := d.extendLeft(add); errObj != nil {
				return errObj
			}
			return &object.Null{}
		}),
		"insert": dequeMethod(2, func(ctx context.Context, d *dequeBuf, _ *object.Instance, args []object.Object) object.Object {
			i, err := args[0].AsInt()
			if err != nil {
				return errors.ParameterError("index", err)
			}
			if d.full() {
				return &object.Exception{Message: "deque already at its maximum size", ExceptionType: object.ExceptionTypeIndexError, Raised: true}
			}
			n := int64(d.n)
			if i < 0 {
				i = max(i+n, 0)
			}
			i = min(i, n)
			if errObj := d.insertAt(int(i), args[1]); errObj != nil {
				return errObj
			}
			return &object.Null{}
		}),
		"remove": dequeMethod(1, func(ctx context.Context, d *dequeBuf, _ *object.Instance, args []object.Object) object.Object {
			i, errObj := dequeFind(ctx, d, args[0], 0, d.n, "remove")
			if errObj != nil {
				return errObj
			}
			if i < 0 {
				return errors.NewValueError("deque.remove(x): x not in deque")
			}
			d.removeAt(i)
			return &object.Null{}
		}),
		"index": dequeMethod(-1, func(ctx context.Context, d *dequeBuf, _ *object.Instance, args []object.Object) object.Object {
			if len(args) < 1 || len(args) > 3 {
				return errors.NewTypeErrorTagged("index expected at least 1 argument, got %d", len(args))
			}
			n := int64(d.n)
			bound := func(o object.Object, def int64) (int64, object.Object) {
				v, err := o.AsInt()
				if err != nil {
					return 0, errors.ParameterError("index", err)
				}
				if v < 0 {
					v = max(v+n, 0)
				}
				return min(v, n), nil
			}
			start, stop := int64(0), n
			var errObj object.Object
			if len(args) >= 2 {
				if start, errObj = bound(args[1], 0); errObj != nil {
					return errObj
				}
			}
			if len(args) == 3 {
				if stop, errObj = bound(args[2], n); errObj != nil {
					return errObj
				}
			}
			i, errObj := dequeFind(ctx, d, args[0], int(start), int(stop), "iteration")
			if errObj != nil {
				return errObj
			}
			if i < 0 {
				return errors.NewValueError("deque.index(x): x not in deque")
			}
			return object.NewInteger(int64(i))
		}),
		"count": dequeMethod(1, func(ctx context.Context, d *dequeBuf, _ *object.Instance, args []object.Object) object.Object {
			state := d.state
			count := int64(0)
			for i := 0; i < d.n; i++ {
				eq, errObj := dequeMatch(ctx, d.at(i), args[0])
				if errObj != nil {
					return errObj
				}
				if d.state != state {
					return &object.Exception{Message: "deque mutated during iteration", ExceptionType: object.ExceptionTypeRuntimeError, Raised: true}
				}
				if eq {
					count++
				}
			}
			return object.NewInteger(count)
		}),
		"reverse": dequeMethod(0, func(ctx context.Context, d *dequeBuf, _ *object.Instance, _ []object.Object) object.Object {
			d.reverse()
			return &object.Null{}
		}),
		"rotate": dequeMethod(-1, func(ctx context.Context, d *dequeBuf, _ *object.Instance, args []object.Object) object.Object {
			if len(args) > 1 {
				return errors.NewTypeErrorTagged("rotate expected at most 1 argument, got %d", len(args))
			}
			k := int64(1)
			if len(args) == 1 {
				v, err := args[0].AsInt()
				if err != nil {
					return errors.ParameterError("n", err)
				}
				k = v
			}
			d.rotate(k)
			return &object.Null{}
		}),
		"clear": dequeMethod(0, func(ctx context.Context, d *dequeBuf, _ *object.Instance, _ []object.Object) object.Object {
			d.clear()
			return &object.Null{}
		}),
		"copy": dequeMethod(0, func(ctx context.Context, _ *dequeBuf, self *object.Instance, _ []object.Object) object.Object {
			return copyDequeFn(self)
		}),
		"__len__": dequeMethod(0, func(ctx context.Context, d *dequeBuf, _ *object.Instance, _ []object.Object) object.Object {
			return object.NewInteger(int64(d.n))
		}),
		"__bool__": dequeMethod(0, func(ctx context.Context, d *dequeBuf, _ *object.Instance, _ []object.Object) object.Object {
			return object.NewBoolean(d.n > 0)
		}),
		"__iter__": dequeMethod(0, func(ctx context.Context, d *dequeBuf, _ *object.Instance, _ []object.Object) object.Object {
			return dequeIterator(d, false)
		}),
		"__reversed__": dequeMethod(0, func(ctx context.Context, d *dequeBuf, _ *object.Instance, _ []object.Object) object.Object {
			return dequeIterator(d, true)
		}),
		"__contains__": dequeMethod(1, func(ctx context.Context, d *dequeBuf, _ *object.Instance, args []object.Object) object.Object {
			i, errObj := dequeFind(ctx, d, args[0], 0, d.n, "iteration")
			if errObj != nil {
				return errObj
			}
			return object.NewBoolean(i >= 0)
		}),
		"__getitem__": dequeMethod(1, func(ctx context.Context, d *dequeBuf, _ *object.Instance, args []object.Object) object.Object {
			i, errObj := dequeIndex(d, args[0])
			if errObj != nil {
				return errObj
			}
			return d.at(i)
		}),
		"__setitem__": dequeMethod(2, func(ctx context.Context, d *dequeBuf, _ *object.Instance, args []object.Object) object.Object {
			i, errObj := dequeIndex(d, args[0])
			if errObj != nil {
				return errObj
			}
			d.buf[d.slot(i)] = args[1]
			return &object.Null{}
		}),
		"__delitem__": dequeMethod(1, func(ctx context.Context, d *dequeBuf, _ *object.Instance, args []object.Object) object.Object {
			i, errObj := dequeIndex(d, args[0])
			if errObj != nil {
				return errObj
			}
			d.removeAt(i)
			return &object.Null{}
		}),
		"__eq__": dequeMethod(1, func(ctx context.Context, d *dequeBuf, _ *object.Instance, args []object.Object) object.Object {
			o := dequeOf(args[0])
			if o == nil || o.n != d.n {
				return object.NewBoolean(false)
			}
			if o == d {
				return object.NewBoolean(true)
			}
			eq, errObj := object.Equal(ctx, &object.List{Elements: d.elements()}, &object.List{Elements: o.elements()})
			if errObj != nil {
				return errObj
			}
			return object.NewBoolean(eq)
		}),
		"__lt__": dequeOrder("<", func(c int) bool { return c < 0 }),
		"__le__": dequeOrder("<=", func(c int) bool { return c <= 0 }),
		"__gt__": dequeOrder(">", func(c int) bool { return c > 0 }),
		"__ge__": dequeOrder(">=", func(c int) bool { return c >= 0 }),
		"__add__": dequeMethod(1, func(ctx context.Context, d *dequeBuf, self *object.Instance, args []object.Object) object.Object {
			o := dequeOf(args[0])
			if o == nil {
				return errors.NewTypeErrorTagged("can only concatenate deque (not \"%s\") to deque", pyTypeName(args[0]))
			}
			c, errObj := d.clone()
			if errObj != nil {
				return errObj
			}
			if errObj := c.extendRight(o.elements()); errObj != nil {
				return errObj
			}
			return newDequeOf(self.Class, c)
		}),
		"__iadd__": dequeMethod(1, func(ctx context.Context, d *dequeBuf, self *object.Instance, args []object.Object) object.Object {
			add, errObj := dequeValues(ctx, args[0])
			if errObj != nil {
				return errObj
			}
			if errObj := d.extendRight(add); errObj != nil {
				return errObj
			}
			return self
		}),
		"__mul__":  dequeMultiply(false),
		"__rmul__": dequeMultiply(false),
		"__imul__": dequeMultiply(true),
		"__str__": dequeMethod(0, func(ctx context.Context, d *dequeBuf, self *object.Instance, _ []object.Object) object.Object {
			return object.NewString(dequeRepr(self, d))
		}),
		"__repr__": dequeMethod(0, func(ctx context.Context, d *dequeBuf, self *object.Instance, _ []object.Object) object.Object {
			return object.NewString(dequeRepr(self, d))
		}),
	},
}

DequeClass is collections.deque: a double-ended queue.

View Source
var DifflibLibrary = object.NewLibrary(DifflibLibraryName, map[string]*object.Builtin{
	"unified_diff": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 2); err != nil {
				return err
			}
			aStr, e := args[0].AsString()
			if e != nil {
				return errors.NewTypeError("STRING", args[0].Type().String())
			}
			bStr, e := args[1].AsString()
			if e != nil {
				return errors.NewTypeError("STRING", args[1].Type().String())
			}
			fromFile := kwargs.MustGetString("fromfile", "")
			toFile := kwargs.MustGetString("tofile", "")
			n := int(kwargs.MustGetInt("n", 3))
			return object.NewString(unifiedDiff(splitLines(aStr), splitLines(bStr), fromFile, toFile, n))
		},
		HelpText: `unified_diff(a, b, fromfile="", tofile="", n=3) - Return a unified format diff string`,
	},
	"ratio": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			aStr, e := args[0].AsString()
			if e != nil {
				return errors.NewTypeError("STRING", args[0].Type().String())
			}
			bStr, e := args[1].AsString()
			if e != nil {
				return errors.NewTypeError("STRING", args[1].Type().String())
			}
			r := sequenceRatio(charSplit(aStr), charSplit(bStr))
			return object.NewFloat(math.Round(r*100) / 100)
		},
		HelpText: `ratio(a, b) - Return a similarity ratio between 0.0 and 1.0`,
	},
	"opcodes": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			aStr, e := args[0].AsString()
			if e != nil {
				return errors.NewTypeError("STRING", args[0].Type().String())
			}
			bStr, e := args[1].AsString()
			if e != nil {
				return errors.NewTypeError("STRING", args[1].Type().String())
			}
			ops := computeOpcodes(splitLines(aStr), splitLines(bStr))
			result := make([]object.Object, len(ops))
			for i, op := range ops {
				result[i] = &object.Tuple{Elements: []object.Object{
					object.NewString(string(op.tag)),
					object.NewInteger(int64(op.i1)),
					object.NewInteger(int64(op.i2)),
					object.NewInteger(int64(op.j1)),
					object.NewInteger(int64(op.j2)),
				}}
			}
			return &object.List{Elements: result}
		},
		HelpText: `opcodes(a, b) - Return list of (tag, i1, i2, j1, j2) tuples describing how to turn a into b`,
	},
	"get_close_matches": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 2); err != nil {
				return err
			}
			word, e := args[0].AsString()
			if e != nil {
				return errors.NewTypeError("STRING", args[0].Type().String())
			}
			possElems, e2 := args[1].AsList()
			if e2 != nil {
				return errors.NewTypeError("LIST", args[1].Type().String())
			}
			n := int(kwargs.MustGetInt("n", 3))
			cutoff := kwargs.MustGetFloat("cutoff", 0.6)
			if len(args) >= 3 {
				if v, err := args[2].AsInt(); err == nil {
					n = int(v)
				}
			}
			if len(args) >= 4 {
				if v, err := args[3].AsFloat(); err == nil {
					cutoff = v
				}
			}
			poss := make([]string, 0, len(possElems))
			for _, p := range possElems {
				if s, err := p.AsString(); err == nil {
					poss = append(poss, s)
				}
			}
			matches := getCloseMatches(word, poss, n, cutoff)
			elems := make([]object.Object, len(matches))
			for i, m := range matches {
				elems[i] = object.NewString(m)
			}
			return &object.List{Elements: elems}
		},
		HelpText: `get_close_matches(word, possibilities, n=3, cutoff=0.6) - Return list of best matches from possibilities`,
	},
}, nil, "Helpers for computing deltas between sequences")
View Source
var EnumLibrary = object.NewLibrary(EnumLibraryName, nil, map[string]object.Object{
	"Enum":    enumBaseClass,
	"IntEnum": intEnumBaseClass,
	"auto": &object.Builtin{
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			return AutoMarker
		},
		HelpText: `auto() - Placeholder for an automatically numbered enum member value

Valid only inside an enum class body: RED = auto(), BLUE = auto().`,
	},
}, "Enumeration support (enum.Enum, enum.IntEnum, enum.auto)")
View Source
var FunctoolsLibrary = object.NewLibrary(FunctoolsLibraryName, map[string]*object.Builtin{
	"lru_cache": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if len(args) > 0 {
				switch args[0].(type) {
				case *object.Function, *object.LambdaFunction, *object.Builtin, *object.BoundMethod:
					return newLRUCacheWrapper(args[0], 128)
				}
				if len(args) > 1 {
					return errors.NewError("lru_cache() takes at most 1 argument")
				}
			}
			maxsize := int64(128)
			if v, ok := kwargs.Kwargs["maxsize"]; ok {
				switch mv := v.(type) {
				case *object.Integer:
					maxsize = mv.IntValue()
				case *object.Null:
					maxsize = -1
				default:
					return errors.NewError("maxsize should be an integer or None")
				}
			} else if len(args) == 1 {
				if iv, ok := args[0].(*object.Integer); ok {
					maxsize = iv.IntValue()
				} else if _, isNull := args[0].(*object.Null); isNull {
					maxsize = -1
				} else {
					return errors.NewError("maxsize should be an integer or None")
				}
			}

			return &object.Builtin{
				Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
					if len(args) != 1 {
						return errors.NewError("lru_cache decorator requires 1 argument")
					}
					switch args[0].(type) {
					case *object.Function, *object.LambdaFunction, *object.Builtin, *object.BoundMethod:
						return newLRUCacheWrapper(args[0], maxsize)
					}
					return errors.NewTypeError("callable", args[0].Type().String())
				},
				HelpText: "lru_cache(maxsize) - decorator factory",
			}
		},
		HelpText: `lru_cache(user_function) or lru_cache(maxsize=128) - Memoizing decorator

Calls are cached by argument values; maxsize bounds the cache with
least-recently-used eviction (maxsize=None is unbounded). Unhashable
arguments bypass the cache, as in Python.`,
	},
	"wraps": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if len(args) != 1 {
				return errors.NewError("wraps() requires 1 argument")
			}
			wrapped := args[0]
			name := "<unknown>"
			switch w := wrapped.(type) {
			case *object.Function:
				name = w.Name
			case *object.LambdaFunction:
				name = "<lambda>"
			case *object.Builtin:
				if w.Repr != "" {
					name = w.Repr
				}
			}
			return &object.Builtin{
				Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
					if len(args) != 1 {
						return errors.NewError("wraps decorator requires the wrapper")
					}
					switch wr := args[0].(type) {
					case *object.Builtin:
						if wr.Attributes == nil {
							wr.Attributes = map[string]object.Object{}
						}
						wr.Attributes["__name__"] = object.NewString(name)
					case *object.Instance:
						wr.SetField("__name__", object.NewString(name))
					case *object.Function:

						_ = wr
					}
					return args[0]
				},
				HelpText: "wraps decorator - copy identity onto the wrapper",
			}
		},
		HelpText: `wraps(wrapped) - Decorator factory preserving the wrapped function's name

The standard decorator pattern:

  def deco(fn):
      @functools.wraps(fn)
      def wrapper(*a, **kw):
          return fn(*a, **kw)
      return wrapper`,
	},
	"cache": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) != 1 {
				return errors.NewError("cache() requires 1 argument")
			}
			switch args[0].(type) {
			case *object.Function, *object.LambdaFunction, *object.Builtin, *object.BoundMethod:
				return newLRUCacheWrapper(args[0], -1)
			}
			return errors.NewTypeError("callable", args[0].Type().String())
		},
		HelpText: `cache(user_function) - Unbounded memoizing decorator (lru_cache(maxsize=None))`,
	},
	"reduce": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) < 2 || len(args) > 3 {
				return errors.NewError("reduce() requires 2 or 3 arguments")
			}

			callable := args[0]
			switch callable.(type) {
			case *object.Function, *object.LambdaFunction, *object.Builtin, *object.BoundMethod, *object.Class:

			default:
				return errors.NewTypeError("callable", callable.Type().String())
			}

			list, ok := args[1].(*object.List)
			if !ok {
				return errors.NewTypeError("LIST", args[1].Type().String())
			}

			if len(list.Elements) == 0 {
				if len(args) == 3 {
					return args[2]
				}
				return errors.NewError("reduce() of empty sequence with no initial value")
			}

			var accumulator object.Object
			startIdx := 0
			if len(args) == 3 {
				accumulator = args[2]
			} else {
				accumulator = list.Elements[0]
				startIdx = 1
			}

			eval := evaliface.FromContext(ctx)
			if eval == nil {
				return errors.NewError("evaluator not available in context")
			}

			for i := startIdx; i < len(list.Elements); i++ {
				result := eval.CallObjectFunction(ctx, callable, []object.Object{accumulator, list.Elements[i]}, nil, nil)
				if result == nil {
					return errors.NewError("reduce function returned nil")
				}
				if object.IsError(result) || result.Type() == object.EXCEPTION_OBJ {
					return result
				}
				accumulator = result
			}

			return accumulator
		},
		HelpText: `reduce(function, iterable[, initializer]) - Apply function cumulatively to items

Parameters:
  function    - Function taking 2 arguments (accumulator, item)
  iterable    - List of items to reduce
  initializer - Optional starting value

Returns: Reduced value

Example:
  import functools

  def add(x, y):
      return x + y

  functools.reduce(add, [1, 2, 3, 4])  # 10
  functools.reduce(add, [1, 2, 3], 10)  # 16`,
	},
	"partial": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) < 1 {
				return errors.NewError("partial() requires at least 1 argument")
			}

			var fn *object.Function
			var lambda *object.LambdaFunction
			var builtin *object.Builtin
			if f, ok := args[0].(*object.Function); ok {
				fn = f
			} else if l, ok := args[0].(*object.LambdaFunction); ok {
				lambda = l
			} else if b, ok := args[0].(*object.Builtin); ok {
				builtin = b
			} else {
				return errors.NewTypeError("FUNCTION", args[0].Type().String())
			}

			partialArgs := make([]object.Object, len(args)-1)
			copy(partialArgs, args[1:])
			partialKwargs := make(map[string]object.Object)
			for k, v := range kwargs.Kwargs {
				partialKwargs[k] = v
			}

			partial := &object.Builtin{
				Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
					allArgs := make([]object.Object, len(partialArgs)+len(args))
					copy(allArgs, partialArgs)
					copy(allArgs[len(partialArgs):], args)
					allKwargs := make(map[string]object.Object)
					for k, v := range partialKwargs {
						allKwargs[k] = v
					}
					for k, v := range kwargs.Kwargs {
						allKwargs[k] = v
					}

					if fn != nil {
						eval := evaliface.FromContext(ctx)
						if eval == nil {
							return errors.NewError("evaluator not available in context")
						}
						return eval.CallFunction(ctx, fn, allArgs, allKwargs)
					}
					if lambda != nil {
						eval := evaliface.FromContext(ctx)
						if eval == nil {
							return errors.NewError("evaluator not available in context")
						}
						return eval.CallObjectFunction(ctx, lambda, allArgs, allKwargs, nil)
					}
					return builtin.Fn(ctx, object.NewKwargs(allKwargs), allArgs...)
				},
				HelpText: "Partial function application",
			}
			object.TrackAlloc(partial, partialObjectSize+int64(len(partialArgs)+len(partialKwargs))*16)
			return partial
		},
		HelpText: `partial(func, *args, **kwargs) - Create a partial function application

Parameters:
  func - Function to partially apply
  *args - Arguments to pre-fill
  **kwargs - Keyword arguments to pre-fill

Returns: New function with pre-filled arguments

Example:
  import functools

  def add(x, y):
      return x + y

  add_five = functools.partial(add, 5)
  add_five(3)  # 8`,
	},
}, nil, "Higher-order functions and operations on callable objects")
View Source
var HMACClass = &object.Class{
	Name: "HMAC",
	Methods: map[string]object.Object{
		"update": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 2); err != nil {
					return err
				}
				inst, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewTypeError("HMAC", args[0].Type().String())
				}
				state, ok := inst.NativeData.(*hmacState)
				if !ok {
					return errors.NewError("invalid hmac object")
				}
				b, errObj := toByteString(args[1])
				if errObj != nil {
					return errObj
				}
				state.data = append(state.data, b...)
				return &object.Null{}
			},
			HelpText: `update(data) - Feed data into the HMAC

Appends data (a string treated as bytes, or a list of byte values) to the
message being authenticated. Returns None.`,
		},
		"digest": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				inst, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewTypeError("HMAC", args[0].Type().String())
				}
				state, ok := inst.NativeData.(*hmacState)
				if !ok {
					return errors.NewError("invalid hmac object")
				}
				return object.NewBytes(computeHmacDigest(state))
			},
			HelpText: `digest() - Return the raw HMAC as a Bytes value

Returns the MAC of the data passed to the hmac so far, as a Bytes object.
Use hexdigest() for a lowercase-hex string representation.`,
		},
		"hexdigest": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				inst, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewTypeError("HMAC", args[0].Type().String())
				}
				state, ok := inst.NativeData.(*hmacState)
				if !ok {
					return errors.NewError("invalid hmac object")
				}
				return object.NewString(hex.EncodeToString(computeHmacDigest(state)))
			},
			HelpText: `hexdigest() - Return the HMAC as a hexadecimal string

Example:
  import hmac
  sig = hmac.new("secret", "payload", "sha256").hexdigest()`,
		},
		"copy": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				inst, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewTypeError("HMAC", args[0].Type().String())
				}
				state, ok := inst.NativeData.(*hmacState)
				if !ok {
					return errors.NewError("invalid hmac object")
				}
				newKey := make([]byte, len(state.key))
				copy(newKey, state.key)
				newData := make([]byte, len(state.data))
				copy(newData, state.data)
				return object.NewInstanceWithData(inst.Class, map[string]object.Object{
					"name":        object.NewString("hmac-" + state.alg),
					"digest_size": object.NewInteger(int64(hashDigestSize(state.alg))),
					"block_size":  object.NewInteger(int64(hashBlockSize(state.alg))),
				}, &hmacState{key: newKey, data: newData, alg: state.alg})
			},
			HelpText: `copy() - Return a copy of the HMAC object`,
		},
	},
}

HMACClass is the class exposed for objects returned by hmac.new().

View Source
var HTMLLibrary = object.NewLibrary(HTMLLibraryName, map[string]*object.Builtin{
	"escape": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}
			str, ok := args[0].(*object.String)
			if !ok {
				return errors.NewTypeError("STRING", args[0].Type().String())
			}
			for _, k := range kwargs.Keys() {
				if k != "quote" {
					return newArgTypeError("escape() got an unexpected keyword argument '%s'", k)
				}
			}
			quote := true
			if len(args) == 2 {
				if kwargs.Has("quote") {
					return newArgTypeError("escape() got multiple values for argument 'quote'")
				}
				quote = isTruthy(args[1])
			} else if q := kwargs.Get("quote"); q != nil {
				quote = isTruthy(q)
			}

			if quote {
				return object.NewString(htmlEscapeQuote.Replace(str.StringValue()))
			}
			return object.NewString(htmlEscapeNoQuote.Replace(str.StringValue()))
		},
		HelpText: `escape(s, quote=True) - Escape HTML special characters

Converts &, < and > to HTML-safe sequences. If quote is true (the default),
also converts " to &quot; and ' to &#x27;.

Parameters:
  s     - String to escape
  quote - Also escape quote characters (default True)

Returns: Escaped string

Example:
  import html
  safe = html.escape("<script>alert('xss')</script>")
  print(safe)  # "&lt;script&gt;alert(&#x27;xss&#x27;)&lt;/script&gt;"`,
	},
	"unescape": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			str, ok := args[0].(*object.String)
			if !ok {
				return errors.NewTypeError("STRING", args[0].Type().String())
			}

			result := html.UnescapeString(str.StringValue())

			result = unescapeNumericEntities(result)
			return object.NewString(result)
		},
		HelpText: `unescape(s) - Unescape HTML entities

Converts HTML entities back to their corresponding characters.
Handles named entities (&lt;, &gt;, &amp;, &quot;, &#39;) and
numeric entities (&#60;, &#x3c;).

Parameters:
  s - String with HTML entities to unescape

Returns: Unescaped string

Example:
  import html
  text = html.unescape("&lt;script&gt;")
  print(text)  # "<script>"`,
	},
}, nil, "HTML escaping and unescaping library")
View Source
var HashClass = &object.Class{
	Name: "Hash",
	Methods: map[string]object.Object{
		"update": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 2); err != nil {
					return err
				}
				inst, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewTypeError("Hash", args[0].Type().String())
				}
				state, ok := inst.NativeData.(*hashState)
				if !ok {
					return errors.NewError("invalid hash object")
				}
				b, errObj := toByteString(args[1])
				if errObj != nil {
					return errObj
				}
				state.data = append(state.data, b...)
				return &object.Null{}
			},
			HelpText: `update(data) - Feed data into the hash

Appends the given data (a string treated as bytes, or a list of byte values)
to the data already fed into the hash. Returns None.

Example:
  import hashlib
  h = hashlib.sha256()
  h.update("foo")
  h.update("bar")
  print(h.hexdigest())  # hash of "foobar"`,
		},
		"digest": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				inst, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewTypeError("Hash", args[0].Type().String())
				}
				state, ok := inst.NativeData.(*hashState)
				if !ok {
					return errors.NewError("invalid hash object")
				}
				h := hashConstructor(state.alg)()
				h.Write(state.data)
				return object.NewBytes(h.Sum(nil))
			},
			HelpText: `digest() - Return the raw hash as a Bytes value

Returns the digest of the data passed to the hash so far, as a Bytes object.
Use hexdigest() for a lowercase-hex string representation.`,
		},
		"hexdigest": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				inst, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewTypeError("Hash", args[0].Type().String())
				}
				state, ok := inst.NativeData.(*hashState)
				if !ok {
					return errors.NewError("invalid hash object")
				}
				h := hashConstructor(state.alg)()
				h.Write(state.data)
				return object.NewString(hex.EncodeToString(h.Sum(nil)))
			},
			HelpText: `hexdigest() - Return the hash as a hexadecimal string

Returns the digest of the data passed to the hash so far, encoded as lowercase
hexadecimal.

Example:
  import hashlib
  print(hashlib.sha256("hello").hexdigest())
  # "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"`,
		},
		"copy": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				inst, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewTypeError("Hash", args[0].Type().String())
				}
				state, ok := inst.NativeData.(*hashState)
				if !ok {
					return errors.NewError("invalid hash object")
				}
				newData := make([]byte, len(state.data))
				copy(newData, state.data)

				return object.NewInstanceWithData(inst.Class, map[string]object.Object{
					"name":        object.NewString(state.alg),
					"digest_size": object.NewInteger(int64(hashDigestSize(state.alg))),
					"block_size":  object.NewInteger(int64(hashBlockSize(state.alg))),
				}, &hashState{data: newData, alg: state.alg})
			},
			HelpText: `copy() - Return a copy of the hash object

Returns a new hash object with the same algorithm and accumulated data.
Updates to one object do not affect the other.`,
		},
	},
}

HashClass is the class exposed for objects returned by hashlib constructors.

View Source
var HashlibLibrary = object.NewLibrary(HashlibLibraryName, map[string]*object.Builtin{
	"sha256": HashlibSHA256Builtin,
	"sha512": makeHashBuiltin("sha512", `sha512([data]) - Create a SHA-512 hash object

With data, hashes it immediately; without, call .update() then .hexdigest().`),
	"sha384": makeHashBuiltin("sha384", `sha384([data]) - Create a SHA-384 hash object

With data, hashes it immediately; without, call .update() then .hexdigest().`),
	"sha224": makeHashBuiltin("sha224", `sha224([data]) - Create a SHA-224 hash object

With data, hashes it immediately; without, call .update() then .hexdigest().`),
	"sha1": HashlibSHA1Builtin,
	"md5":  HashlibMD5Builtin,
}, nil, "Cryptographic hash functions library")
View Source
var HmacLibrary = object.NewLibrary(HmacLibraryName, map[string]*object.Builtin{
	"new": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.RangeArgs(args, 1, 3); err != nil {
				return err
			}
			key, errObj := toByteString(args[0])
			if errObj != nil {
				return errObj
			}
			var data []byte
			if len(args) >= 2 {
				if _, isNull := args[1].(*object.Null); !isNull {
					b, e := toByteString(args[1])
					if e != nil {
						return e
					}
					data = b
				}
			}
			var digestmod object.Object
			if len(args) >= 3 {
				digestmod = args[2]
			}
			alg, e := resolveHmacAlg(digestmod)
			if e != nil {
				return e
			}
			return newHmacInstance(alg, key, data)
		},
		HelpText: `new(key, msg=None, digestmod=None) - Create an HMAC object

key and msg are byte buffers (a string treated as bytes, or a list of byte
values such as returned by str.encode()). digestmod may be a string name
("sha256", "sha1", "md5"), a hashlib constructor (e.g. hashlib.sha256), or
omitted (defaults to sha256). Call .hexdigest() or .digest() for the result.

Example:
  import hmac, hashlib
  sig = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
  ok = hmac.compare_digest(sig, signature)`,
	},
	"digest": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 3); err != nil {
				return err
			}
			key, errObj := toByteString(args[0])
			if errObj != nil {
				return errObj
			}
			msg, errObj := toByteString(args[1])
			if errObj != nil {
				return errObj
			}
			alg, e := resolveHmacAlg(args[2])
			if e != nil {
				return e
			}
			return object.NewBytes(computeHmacDigest(&hmacState{key: key, data: msg, alg: alg}))
		},
		HelpText: `digest(key, msg, digestmod) - One-shot HMAC as a raw byte string`,
	},
	"compare_digest": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			a, okA := args[0].(*object.String)
			b, okB := args[1].(*object.String)
			if !okA || !okB {
				return errors.NewError("compare_digest() requires two string arguments")
			}
			return object.NewBoolean(CompareDigest(a.StringValue(), b.StringValue()))
		},
		HelpText: `compare_digest(a, b) - Constant-time string comparison

Compares two strings in constant time to help prevent timing attacks. Returns
True if equal, False otherwise. Use this to compare signature values.

Example:
  import hmac
  ok = hmac.compare_digest(expected_signature, received_signature)`,
	},
}, nil, "HMAC (Keyed-Hashing for Message Authentication) library")

HmacLibrary provides HMAC (Keyed-Hashing for Message Authentication).

View Source
var IOLibrary = object.NewLibrary(IOLibraryName, map[string]*object.Builtin{
	"StringIO": {
		Fn: stringIONew,
		HelpText: `StringIO([initial_value=""]) - In-memory string buffer

Creates a StringIO object that behaves like a file opened for text I/O.
Optional initial_value pre-fills the buffer.

Methods: write(s), getvalue(), read([n]), readline(), seek(pos), tell(), truncate([pos]), close()
Supports the with statement: with io.StringIO() as buf: ...`,
	},
}, nil, "In-memory I/O streams")
View Source
var ItertoolsLibrary = object.NewLibrary(ItertoolsLibraryName, map[string]*object.Builtin{
	"chain": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			lazy := false
			srcs := make([]func() (object.Object, bool), 0, len(args))
			for _, arg := range args {
				switch a := arg.(type) {
				case *object.List:
					next, _ := object.IterSource(a)
					srcs = append(srcs, next)
				case *object.Tuple:
					next, _ := object.IterSource(a)
					srcs = append(srcs, next)
				case *object.String:
					next, _ := object.IterSource(a)
					srcs = append(srcs, next)
				case *object.Iterator:
					lazy = true
					srcs = append(srcs, a.Next)
				default:
					it, errObj := instanceAsIterator(ctx, arg)
					if errObj != nil {
						return errObj
					}
					if it == nil {
						elems, cerr := collectIterable(ctx, arg)
						if cerr != nil {
							return cerr
						}
						l := &object.List{Elements: elems}
						next, _ := object.IterSource(l)
						srcs = append(srcs, next)
						continue
					}
					lazy = true
					srcs = append(srcs, it.Next)
				}
			}
			if !lazy {
				result := []object.Object{}
				for _, next := range srcs {
					for {
						v, more := next()
						if !more {
							break
						}
						result = append(result, v)
					}
				}
				object.TrackSlice(result)
				return &object.List{Elements: result}
			}
			cur := 0
			mkIter := object.NewIterator
			for _, arg := range args {
				if isEndless(arg) {
					mkIter = object.NewInfiniteIterator
					break
				}
			}
			return mkIter(func() (object.Object, bool) {
				for cur < len(srcs) {
					v, more := srcs[cur]()
					if !more {
						cur++
						continue
					}
					return v, true
				}
				return nil, false
			})
		},
		HelpText: `chain(*iterables) - Chain multiple iterables together

Returns a list with elements from all iterables concatenated.

Example:
  itertools.chain([1, 2], [3, 4]) -> [1, 2, 3, 4]
  itertools.chain("ab", "cd") -> ["a", "b", "c", "d"]`,
	},
	"repeat": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}
			if err := checkKwargs("repeat", kwargs, "times"); err != nil {
				return err
			}
			elem := args[0]
			timesObj, terr := optionalArg("repeat", args, kwargs, 1, "times")
			if terr != nil {
				return terr
			}
			if timesObj == nil {
				return object.NewInfiniteIterator(func() (object.Object, bool) {
					return elem, true
				})
			}
			n, ok := timesObj.(*object.Integer)
			if !ok {
				return errors.NewTypeError("INTEGER", timesObj.Type().String())
			}
			left := n.IntValue()
			return object.NewIterator(func() (object.Object, bool) {
				if left <= 0 {
					return nil, false
				}
				left--
				return elem, true
			})
		},
		HelpText: `repeat(elem[, times]) - Repeat element times times

Returns an iterator giving elem times times, or forever when times is
omitted, like Python's itertools.repeat. Use list() for a list, or consume an
endless one with next(), zip() or itertools.islice.

Example:
  list(itertools.repeat("x", 3)) -> ["x", "x", "x"]
  list(zip(range(3), itertools.repeat(0))) -> [(0, 0), (1, 0), (2, 0)]`,
	},
	"cycle": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}

			cycleArg := args[0]
			if _, isIter := cycleArg.(*object.Iterator); !isIter && len(args) == 1 {
				if it, errObj := instanceAsIterator(ctx, cycleArg); errObj != nil {
					return errObj
				} else if it != nil {
					cycleArg = it
				}
			}
			if src, isIter := cycleArg.(*object.Iterator); isIter && len(args) == 1 {

				first, any := src.Next()
				if !any {
					return object.NewIterator(func() (object.Object, bool) { return nil, false })
				}
				pending := true
				var saved []object.Object
				exhausted := false
				i := 0
				return object.NewInfiniteIterator(func() (object.Object, bool) {
					if pending {
						pending = false
						if !object.IsPropagating(first) {
							saved = append(saved, first)
						}
						return first, true
					}
					if !exhausted {
						if v, ok := src.Next(); ok {
							if !object.IsError(v) && v.Type() != object.EXCEPTION_OBJ {
								saved = append(saved, v)
							}
							return v, true
						}
						exhausted = true
					}
					if len(saved) == 0 {
						return nil, false
					}
					elem := saved[i%len(saved)]
					i++
					return elem, true
				})
			}
			var elements []object.Object
			switch a := args[0].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			case *object.String:
				for _, ch := range a.StringValue() {
					elements = append(elements, object.NewString(string(ch)))
				}
			default:
				elems, errObj := collectIterable(ctx, args[0])
				if errObj != nil {
					return errObj
				}
				elements = elems
			}
			if len(args) == 2 {
				n, ok := args[1].(*object.Integer)
				if !ok {
					return errors.NewTypeError("INTEGER", args[1].Type().String())
				}
				if len(elements) == 0 || n.IntValue() <= 0 {
					return &object.List{Elements: []object.Object{}}
				}
				result := make([]object.Object, 0, len(elements)*int(n.IntValue()))
				for i := int64(0); i < n.IntValue(); i++ {
					result = append(result, elements...)
				}
				object.TrackSlice(result)
				return &object.List{Elements: result}
			}
			if len(elements) == 0 {

				return object.NewIterator(func() (object.Object, bool) { return nil, false })
			}
			i := 0
			return object.NewInfiniteIterator(func() (object.Object, bool) {
				elem := elements[i%len(elements)]
				i++
				return elem, true
			})
		},
		HelpText: `cycle(iterable) - Cycle through iterable endlessly

Returns an infinite iterator, like Python's itertools.cycle; consume it with
next() or itertools.islice. The legacy form cycle(iterable, n) returns the
materialized list of n repetitions.`,
	},
	"count": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			{
				if len(args) > 2 {
					return &object.Exception{
						Message:       fmt.Sprintf("count() takes at most 2 arguments (%d given)", len(args)),
						ExceptionType: object.ExceptionTypeTypeError,
						Raised:        true,
					}
				}
				if err := checkKwargs("count", kwargs, "start", "step"); err != nil {
					return err
				}
				startObj, serr := optionalArg("count", args, kwargs, 0, "start")
				if serr != nil {
					return serr
				}
				stepObj, terr := optionalArg("count", args, kwargs, 1, "step")
				if terr != nil {
					return terr
				}
				if _, isNull := stepObj.(*object.Null); isNull {
					stepObj = nil
				}
				if startObj == nil {
					startObj = object.NewInteger(0)
				}
				if stepObj == nil {
					stepObj = object.NewInteger(1)
				}
				for _, o := range []object.Object{startObj, stepObj} {
					switch o.(type) {
					case *object.Integer, *object.Float:
					default:
						return errors.NewTypeError("a number is required", o.Type().String())
					}
				}
				si, sInt := startObj.(*object.Integer)
				ti, tInt := stepObj.(*object.Integer)
				if sInt && tInt {
					cur, step := si.IntValue(), ti.IntValue()
					return object.NewInfiniteIterator(func() (object.Object, bool) {
						v := object.NewInteger(cur)
						cur += step
						return v, true
					})
				}
				curF, _ := startObj.AsFloat()
				stepF, _ := stepObj.AsFloat()
				return object.NewInfiniteIterator(func() (object.Object, bool) {
					v := object.NewFloat(curF)
					curF += stepF
					return v, true
				})
			}
		},
		HelpText: `count(start=0, step=1) - Count up from start by step, forever

Returns an infinite iterator like Python's itertools.count; consume it with
next(), zip(), enumerate() or itertools.islice.`,
	},
	"islice": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if err := errors.RangeArgs(args, 2, 4); err != nil {
				return err
			}
			start, stop, step, errObj := parseIsliceBounds(args)
			if errObj != nil {
				return errObj
			}

			iterTarget := args[0]
			if _, isIter := iterTarget.(*object.Iterator); !isIter {
				if it, errObj := instanceAsIterator(ctx, iterTarget); errObj != nil {
					return errObj
				} else if it != nil {
					iterTarget = it
				}
			}
			if iter, isIter := iterTarget.(*object.Iterator); isIter {
				pos := int64(0)
				done := false
				next := func() (object.Object, bool) {
					for !done {
						if stop >= 0 && pos >= stop {
							break
						}
						val, ok := iter.Next()
						if !ok {
							break
						}
						if object.IsPropagating(val) {
							done = true
							return val, true
						}
						p := pos
						pos++
						if p >= start && (p-start)%step == 0 {
							return val, true
						}
					}
					done = true
					return nil, false
				}
				if stop < 0 && iter.Infinite() {
					return object.NewInfiniteIterator(next)
				}
				return object.NewIterator(next)
			}

			elements, errObj := collectIterable(ctx, args[0])
			if errObj != nil {
				return errObj
			}
			if stop < 0 || stop > int64(len(elements)) {
				stop = int64(len(elements))
			}
			result := []object.Object{}
			for i := start; i < stop; i += step {
				result = append(result, elements[i])
			}
			object.TrackSlice(result)
			return &object.List{Elements: result}
		},
		HelpText: `islice(iterable, stop) or islice(iterable, start, stop[, step]) - Slice an iterable

Returns a list with elements from the iterable sliced by indices.

Example:
  itertools.islice([0, 1, 2, 3, 4], 3) -> [0, 1, 2]
  itertools.islice([0, 1, 2, 3, 4], 1, 4) -> [1, 2, 3]
  itertools.islice([0, 1, 2, 3, 4], 0, 5, 2) -> [0, 2, 4]`,
	},
	"takewhile": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			pred := args[0]
			if !isCallable(pred) {
				return errors.NewTypeError("callable", pred.Type().String())
			}

			srcArg := args[1]
			if _, isIter := srcArg.(*object.Iterator); !isIter {
				if it, errObj := instanceAsIterator(ctx, srcArg); errObj != nil {
					return errObj
				} else if it != nil {
					srcArg = it
				}
			}
			next, ok := object.IterSource(srcArg)
			if !ok {
				return notIterableError(args[1])
			}
			result := []object.Object{}
			for {
				if err := ctx.Err(); err != nil {
					return errors.NewError("%s", err.Error())
				}
				elem, more := next()
				if !more {
					break
				}
				if object.IsPropagating(elem) {
					return elem
				}
				res := callCallable(ctx, pred, elem)
				if object.IsError(res) || res.Type() == object.EXCEPTION_OBJ {
					return res
				}
				if !isTruthy(res) {
					break
				}
				result = append(result, elem)
			}
			object.TrackSlice(result)
			return &object.List{Elements: result}
		},
		HelpText: `takewhile(predicate, iterable) - Take elements while predicate is true

Returns a list with elements from the start of iterable as long as predicate is true.

Example:
  itertools.takewhile(lambda x: x < 5, [1, 3, 5, 2, 4]) -> [1, 3]`,
	},
	"dropwhile": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			pred := args[0]
			if !isCallable(pred) {
				return errors.NewTypeError("callable", pred.Type().String())
			}

			srcArg := args[1]
			lazy := false
			if _, isIter := srcArg.(*object.Iterator); isIter {
				lazy = true
			} else if it, errObj := instanceAsIterator(ctx, srcArg); errObj != nil {
				return errObj
			} else if it != nil {
				srcArg = it
				lazy = true
			}
			next, ok := object.IterSource(srcArg)
			if !ok {
				return notIterableError(args[1])
			}
			runTail := func(emit func(object.Object) object.Object) object.Object {
				dropping := true
				for {
					if err := ctx.Err(); err != nil {
						return errors.NewError("%s", err.Error())
					}
					elem, more := next()
					if !more {
						return nil
					}
					if object.IsPropagating(elem) {
						return elem
					}
					if dropping {
						res := callCallable(ctx, pred, elem)
						if object.IsError(res) || res.Type() == object.EXCEPTION_OBJ {
							return res
						}
						if isTruthy(res) {
							continue
						}
						dropping = false
					}
					if errObj := emit(elem); errObj != nil {
						return errObj
					}
				}
			}
			if lazy {

				dropping := true
				mkIter := object.NewIterator
				if isEndless(srcArg) {
					mkIter = object.NewInfiniteIterator
				}
				return mkIter(func() (object.Object, bool) {
					for {
						if err := ctx.Err(); err != nil {
							return errors.NewError("%s", err.Error()), true
						}
						elem, more := next()
						if !more {
							return nil, false
						}
						if object.IsPropagating(elem) {
							return elem, true
						}
						if dropping {
							res := callCallable(ctx, pred, elem)
							if object.IsError(res) || res.Type() == object.EXCEPTION_OBJ {
								return res, true
							}
							if isTruthy(res) {
								continue
							}
							dropping = false
						}
						return elem, true
					}
				})
			}
			result := []object.Object{}
			if errObj := runTail(func(v object.Object) object.Object {
				result = append(result, v)
				return nil
			}); errObj != nil {
				return errObj
			}
			object.TrackSlice(result)
			return &object.List{Elements: result}
		},
		HelpText: `dropwhile(predicate, iterable) - Drop elements while predicate is true

Returns a list with elements after the predicate becomes false.

Example:
  itertools.dropwhile(lambda x: x < 5, [1, 3, 5, 2, 4]) -> [5, 2, 4]`,
	},
	"zip_longest": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if len(args) < 1 {
				return &object.List{Elements: []object.Object{}}
			}

			fillvalue := object.Object(&object.Null{})
			if kwargs.Len() > 0 {
				if kwargs.Has("fillvalue") {
					fillvalue = kwargs.Get("fillvalue")
				}
			}

			lazy := false
			srcs := make([]func() (object.Object, bool), 0, len(args))
			for _, arg := range args {
				switch a := arg.(type) {
				case *object.List:
					next, _ := object.IterSource(a)
					srcs = append(srcs, next)
				case *object.Tuple:
					next, _ := object.IterSource(a)
					srcs = append(srcs, next)
				case *object.String:
					next, _ := object.IterSource(a)
					srcs = append(srcs, next)
				case *object.Iterator:
					lazy = true
					srcs = append(srcs, a.Next)
				default:
					it, errObj := instanceAsIterator(ctx, arg)
					if errObj != nil {
						return errObj
					}
					if it == nil {
						elems, cerr := collectIterable(ctx, arg)
						if cerr != nil {
							return cerr
						}
						l := &object.List{Elements: elems}
						next, _ := object.IterSource(l)
						srcs = append(srcs, next)
						continue
					}
					lazy = true
					srcs = append(srcs, it.Next)
				}
			}
			if !lazy {
				result := []object.Object{}
				for {
					if err := ctx.Err(); err != nil {
						return errors.NewError("%s", err.Error())
					}
					tuple := make([]object.Object, len(srcs))
					any := false
					for i, next := range srcs {
						v, more := next()
						if more {
							if object.IsPropagating(v) {
								return v
							}
							tuple[i] = v
							any = true
						} else {
							tuple[i] = fillvalue
						}
					}
					if !any {
						break
					}
					result = append(result, newTrackedTuple(tuple))
				}
				object.TrackSlice(result)
				return &object.List{Elements: result}
			}
			mkIter := object.NewIterator
			for _, arg := range args {
				if isEndless(arg) {
					mkIter = object.NewInfiniteIterator
					break
				}
			}
			return mkIter(func() (object.Object, bool) {
				tuple := make([]object.Object, len(srcs))
				any := false
				for i, next := range srcs {
					v, more := next()
					if more {
						if object.IsPropagating(v) {
							return v, true
						}
						tuple[i] = v
						any = true
					} else {
						tuple[i] = fillvalue
					}
				}
				if !any {
					return nil, false
				}
				object.TrackSlice(tuple)
				return &object.Tuple{Elements: tuple}, true
			})
		},
		HelpText: `zip_longest(*iterables, fillvalue=None) - Zip iterables, filling shorter ones

Zips iterables together, using fillvalue for missing values in shorter iterables.

Example:
  itertools.zip_longest([1, 2, 3], ["a", "b"]) -> [(1, "a"), (2, "b"), (3, None)]
  itertools.zip_longest([1, 2], ["a"], fillvalue="-") -> [(1, "a"), (2, "-")]`,
	},
	"product": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if len(args) < 1 {
				return &object.List{Elements: []object.Object{&object.Tuple{Elements: []object.Object{}}}}
			}

			repeatN := int64(1)
			if v := kwargs.Get("repeat"); v != nil {
				if n, err := v.AsInt(); err == nil {
					repeatN = n
				} else {
					return errors.ParameterError("repeat", err)
				}
			}

			collected := make([][]object.Object, 0, len(args))
			for _, arg := range args {
				switch a := arg.(type) {
				case *object.List:
					collected = append(collected, a.Elements)
				case *object.Tuple:
					collected = append(collected, a.Elements)
				case *object.String:
					chars := []object.Object{}
					for _, ch := range a.StringValue() {
						chars = append(chars, object.NewString(string(ch)))
					}
					collected = append(collected, chars)
				default:
					elems, errObj := collectIterable(ctx, arg)
					if errObj != nil {
						return errObj
					}
					collected = append(collected, elems)
				}
			}
			iterables := make([][]object.Object, 0, int(repeatN)*len(collected))
			for rep := int64(0); rep < repeatN; rep++ {
				iterables = append(iterables, collected...)
			}

			for _, iter := range iterables {
				if len(iter) == 0 {
					return &object.List{Elements: []object.Object{}}
				}
			}

			result := []object.Object{}
			indices := make([]int, len(iterables))

			for {

				tuple := make([]object.Object, len(iterables))
				for i, idx := range indices {
					tuple[i] = iterables[i][idx]
				}
				result = append(result, newTrackedTuple(tuple))

				carry := true
				for i := len(indices) - 1; i >= 0 && carry; i-- {
					indices[i]++
					if indices[i] >= len(iterables[i]) {
						indices[i] = 0
					} else {
						carry = false
					}
				}
				if carry {
					break
				}
			}
			object.TrackSlice(result)
			return &object.List{Elements: result}
		},
		HelpText: `product(*iterables) - Cartesian product of iterables

Returns all possible combinations (tuples) of elements from input iterables.

Example:
  itertools.product([1, 2], ["a", "b"]) -> [(1, "a"), (1, "b"), (2, "a"), (2, "b")]
  itertools.product([1, 2], [3, 4]) -> [(1, 3), (1, 4), (2, 3), (2, 4)]`,
	},
	"permutations": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			args, kerr := kwargsToPositional("permutations", args, kwargs, "iterable", "r")
			if kerr != nil {
				return kerr
			}

			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}
			var elements []object.Object
			switch a := args[0].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			case *object.String:
				for _, ch := range a.StringValue() {
					elements = append(elements, object.NewString(string(ch)))
				}
			default:
				elems, errObj := collectIterable(ctx, args[0])
				if errObj != nil {
					return errObj
				}
				elements = elems
			}

			r := len(elements)
			if len(args) == 2 && args[1].Type() != object.NULL_OBJ {
				if rArg, ok := args[1].(*object.Integer); ok {
					r = int(rArg.IntValue())
				} else {
					return errors.NewTypeError("INTEGER", args[1].Type().String())
				}
			}

			if r < 0 {
				return errors.NewValueError("r must be non-negative")
			}
			if r > len(elements) {
				return &object.List{Elements: []object.Object{}}
			}

			result := []object.Object{}
			generatePermutations(elements, r, []object.Object{}, make([]bool, len(elements)), &result)
			object.TrackSlice(result)
			return &object.List{Elements: result}
		},
		HelpText: `permutations(iterable[, r]) - Generate permutations

Returns all r-length permutations of elements from iterable.
If r is not specified, defaults to length of iterable (full permutations).

Example:
  itertools.permutations([1, 2, 3], 2) -> [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
  itertools.permutations("ab") -> [("a", "b"), ("b", "a")]`,
	},
	"combinations": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			args, kerr := kwargsToPositional("combinations", args, kwargs, "iterable", "r")
			if kerr != nil {
				return kerr
			}

			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			var elements []object.Object
			switch a := args[0].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			case *object.String:
				for _, ch := range a.StringValue() {
					elements = append(elements, object.NewString(string(ch)))
				}
			default:
				elems, errObj := collectIterable(ctx, args[0])
				if errObj != nil {
					return errObj
				}
				elements = elems
			}

			rArg, ok := args[1].(*object.Integer)
			if !ok {
				return errors.NewTypeError("INTEGER", args[1].Type().String())
			}
			r := int(rArg.IntValue())

			if r < 0 || r > len(elements) {
				return &object.List{Elements: []object.Object{}}
			}

			result := []object.Object{}
			generateCombinations(elements, r, 0, []object.Object{}, &result)
			object.TrackSlice(result)
			return &object.List{Elements: result}
		},
		HelpText: `combinations(iterable, r) - Generate combinations

Returns all r-length combinations of elements from iterable (without repetition).

Example:
  itertools.combinations([1, 2, 3], 2) -> [(1, 2), (1, 3), (2, 3)]
  itertools.combinations("abc", 2) -> [("a", "b"), ("a", "c"), ("b", "c")]`,
	},
	"combinations_with_replacement": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			args, kerr := kwargsToPositional("combinations_with_replacement", args, kwargs, "iterable", "r")
			if kerr != nil {
				return kerr
			}

			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			var elements []object.Object
			switch a := args[0].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			case *object.String:
				for _, ch := range a.StringValue() {
					elements = append(elements, object.NewString(string(ch)))
				}
			default:
				elems, errObj := collectIterable(ctx, args[0])
				if errObj != nil {
					return errObj
				}
				elements = elems
			}

			rArg, ok := args[1].(*object.Integer)
			if !ok {
				return errors.NewTypeError("INTEGER", args[1].Type().String())
			}
			r := int(rArg.IntValue())

			if r < 0 {
				return &object.List{Elements: []object.Object{}}
			}
			if len(elements) == 0 && r > 0 {
				return &object.List{Elements: []object.Object{}}
			}

			result := []object.Object{}
			generateCombinationsWithReplacement(elements, r, 0, []object.Object{}, &result)
			object.TrackSlice(result)
			return &object.List{Elements: result}
		},
		HelpText: `combinations_with_replacement(iterable, r) - Generate combinations with replacement

Returns all r-length combinations of elements from iterable (with repetition allowed).

Example:
  itertools.combinations_with_replacement([1, 2], 2) -> [(1, 1), (1, 2), (2, 2)]
  itertools.combinations_with_replacement("ab", 2) -> [("a", "a"), ("a", "b"), ("b", "b")]`,
	},
	"groupby": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}
			var elements []object.Object
			switch a := args[0].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			default:
				elems, errObj := collectIterable(ctx, args[0])
				if errObj != nil {
					return errObj
				}
				elements = elems
			}

			if err := checkKwargs("groupby", kwargs, "key"); err != nil {
				return err
			}
			keyFunc, kerr := optionalArg("groupby", args, kwargs, 1, "key")
			if kerr != nil {
				return kerr
			}
			if keyFunc != nil && !isCallable(keyFunc) {
				return errors.NewTypeError("callable", keyFunc.Type().String())
			}

			if len(elements) == 0 {
				return &object.List{Elements: []object.Object{}}
			}

			result := []object.Object{}
			var currentKey object.Object
			var currentGroup []object.Object

			for i, elem := range elements {
				var key object.Object
				if keyFunc != nil {
					key = callCallable(ctx, keyFunc, elem)
					if object.IsError(key) || key.Type() == object.EXCEPTION_OBJ {
						return key
					}
				} else {
					key = elem
				}

				if i == 0 {
					currentKey = key
					currentGroup = []object.Object{elem}
				} else if objectsEqual(key, currentKey) {
					currentGroup = append(currentGroup, elem)
				} else {

					result = append(result, &object.Tuple{Elements: []object.Object{
						currentKey,
						&object.List{Elements: currentGroup},
					}})
					currentKey = key
					currentGroup = []object.Object{elem}
				}
			}

			if len(currentGroup) > 0 {
				result = append(result, &object.Tuple{Elements: []object.Object{
					currentKey,
					&object.List{Elements: currentGroup},
				}})
			}

			object.TrackSlice(result)
			return &object.List{Elements: result}
		},
		HelpText: `groupby(iterable, key=None) - Group consecutive elements

Groups consecutive elements that have the same key value.
Returns list of (key, group) tuples where group is a list.

Example:
  itertools.groupby([1, 1, 2, 2, 3]) -> [(1, [1, 1]), (2, [2, 2]), (3, [3])]
  itertools.groupby(["aa", "ab", "ba"], lambda x: x[0]) -> [("a", ["aa", "ab"]), ("b", ["ba"])]`,
	},
	"accumulate": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}

			srcArg := args[0]
			var elements []object.Object
			switch a := args[0].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			default:
				if it, errObj := instanceAsIterator(ctx, srcArg); errObj != nil {
					return errObj
				} else if it != nil {
					srcArg = it
				}
				if it, isIter := srcArg.(*object.Iterator); isIter {
					elems := []object.Object{}
					for {
						if err := ctx.Err(); err != nil {
							return errors.NewError("%s", err.Error())
						}
						v, more := it.Next()
						if !more {
							break
						}
						if object.IsPropagating(v) {
							return v
						}
						elems = append(elems, v)
					}
					elements = elems
				} else {
					elems, errObj := collectIterable(ctx, args[0])
					if errObj != nil {
						return errObj
					}
					elements = elems
				}
			}

			if err := checkKwargs("accumulate", kwargs, "func", "initial"); err != nil {
				return err
			}
			accumFunc, ferr := optionalArg("accumulate", args, kwargs, 1, "func")
			if ferr != nil {
				return ferr
			}
			if accumFunc != nil && !isCallable(accumFunc) {
				return errors.NewTypeError("callable", accumFunc.Type().String())
			}

			if initial, _ := optionalArg("accumulate", nil, kwargs, 0, "initial"); initial != nil {
				elements = append([]object.Object{initial}, elements...)
			}

			if len(elements) == 0 {
				return &object.List{Elements: []object.Object{}}
			}

			result := []object.Object{elements[0]}
			accumulator := elements[0]

			for i := 1; i < len(elements); i++ {
				if accumFunc != nil {
					accumulator = callCallable(ctx, accumFunc, accumulator, elements[i])
					if object.IsError(accumulator) || accumulator.Type() == object.EXCEPTION_OBJ {
						return accumulator
					}
				} else {

					accumulator = addObjects(accumulator, elements[i])
					if object.IsError(accumulator) {
						return accumulator
					}
				}
				result = append(result, accumulator)
			}
			object.TrackSlice(result)
			return &object.List{Elements: result}
		},
		HelpText: `accumulate(iterable[, func, *, initial=None]) - Running totals/accumulation

Returns list of accumulated values. Default is sum, but can provide custom function.
If initial is given, it is emitted first and seeds the accumulation.

Example:
  itertools.accumulate([1, 2, 3, 4]) -> [1, 3, 6, 10]
  itertools.accumulate([1, 2, 3], operator.mul) -> [1, 2, 6]`,
	},
	"filterfalse": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			pred := args[0]
			if !isCallable(pred) {
				return errors.NewTypeError("callable", pred.Type().String())
			}
			var elements []object.Object
			switch a := args[1].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			default:
				elems, errObj := collectIterable(ctx, args[1])
				if errObj != nil {
					return errObj
				}
				elements = elems
			}
			result := []object.Object{}
			for _, elem := range elements {
				res := callCallable(ctx, pred, elem)
				if object.IsError(res) || res.Type() == object.EXCEPTION_OBJ {
					return res
				}
				if !isTruthy(res) {
					result = append(result, elem)
				}
			}
			object.TrackSlice(result)
			return &object.List{Elements: result}
		},
		HelpText: `filterfalse(predicate, iterable) - Filter elements where predicate is false

Returns elements for which the predicate returns false.

Example:
  itertools.filterfalse(lambda x: x % 2, [1, 2, 3, 4]) -> [2, 4]`,
	},
	"starmap": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			fn := args[0]
			if !isCallable(fn) {
				return errors.NewTypeError("callable", fn.Type().String())
			}
			var elements []object.Object
			switch a := args[1].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			default:
				elems, errObj := collectIterable(ctx, args[1])
				if errObj != nil {
					return errObj
				}
				elements = elems
			}
			result := []object.Object{}
			for _, elem := range elements {
				var fnArgs []object.Object
				switch e := elem.(type) {
				case *object.List:
					fnArgs = e.Elements
				case *object.Tuple:
					fnArgs = e.Elements
				default:
					return errors.NewError("starmap() iterable must contain sequences")
				}
				res := callCallable(ctx, fn, fnArgs...)
				if object.IsError(res) || res.Type() == object.EXCEPTION_OBJ {
					return res
				}
				result = append(result, res)
			}
			object.TrackSlice(result)
			return &object.List{Elements: result}
		},
		HelpText: `starmap(func, iterable) - Apply function to argument tuples

Applies function using elements of each tuple as arguments.

Example:
  itertools.starmap(pow, [(2, 3), (3, 2)]) -> [8, 9]`,
	},
	"compress": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}

			dataArg, selArg := args[0], args[1]
			for i, a := range []object.Object{dataArg, selArg} {
				if _, isIter := a.(*object.Iterator); isIter {
					continue
				}
				if it, errObj := instanceAsIterator(ctx, a); errObj != nil {
					return errObj
				} else if it != nil {
					if i == 0 {
						dataArg = it
					} else {
						selArg = it
					}
				}
			}
			data, dataOK := object.IterSource(dataArg)
			if !dataOK {
				return notIterableError(args[0])
			}
			selectors, selOK := object.IterSource(selArg)
			if !selOK {
				return notIterableError(args[1])
			}
			if isEndless(args[0]) && isEndless(args[1]) {
				return errors.NewError("%s", object.InfiniteIteratorMessage)
			}

			result := []object.Object{}
			for {
				if err := ctx.Err(); err != nil {
					return errors.NewError("%s", err.Error())
				}
				d, ok := data()
				if !ok {
					break
				}
				if object.IsPropagating(d) {
					return d
				}
				sel, ok := selectors()
				if !ok {
					break
				}
				if object.IsPropagating(sel) {
					return sel
				}
				if isTruthy(sel) {
					result = append(result, d)
				}
			}
			object.TrackSlice(result)
			return &object.List{Elements: result}
		},
		HelpText: `compress(data, selectors) - Filter data based on selectors

Returns elements from data where corresponding selector is truthy.

Example:
  itertools.compress([1, 2, 3, 4], [True, False, True, False]) -> [1, 3]
  itertools.compress("abcd", [1, 0, 1, 0]) -> ["a", "c"]`,
	},
	"pairwise": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			var elements []object.Object
			switch a := args[0].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			case *object.String:
				for _, ch := range a.StringValue() {
					elements = append(elements, object.NewString(string(ch)))
				}
			default:
				elems, errObj := collectIterable(ctx, args[0])
				if errObj != nil {
					return errObj
				}
				elements = elems
			}

			if len(elements) < 2 {
				return &object.List{Elements: []object.Object{}}
			}

			result := make([]object.Object, len(elements)-1)
			for i := 0; i < len(elements)-1; i++ {
				result[i] = &object.Tuple{Elements: []object.Object{elements[i], elements[i+1]}}
			}
			object.TrackSlice(result)
			return &object.List{Elements: result}
		},
		HelpText: `pairwise(iterable) - Return successive overlapping pairs

Returns consecutive pairs from the iterable.

Example:
  itertools.pairwise([1, 2, 3, 4]) -> [(1, 2), (2, 3), (3, 4)]
  itertools.pairwise("abc") -> [("a", "b"), ("b", "c")]`,
	},
	"batched": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			args, kerr := kwargsToPositional("batched", args, kwargs, "iterable", "n")
			if kerr != nil {
				return kerr
			}

			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			var elements []object.Object
			switch a := args[0].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			case *object.String:
				for _, ch := range a.StringValue() {
					elements = append(elements, object.NewString(string(ch)))
				}
			default:
				elems, errObj := collectIterable(ctx, args[0])
				if errObj != nil {
					return errObj
				}
				elements = elems
			}

			n, ok := args[1].(*object.Integer)
			if !ok {
				return errors.NewTypeError("INTEGER", args[1].Type().String())
			}
			if n.IntValue() <= 0 {
				return errors.NewValueError("n must be at least one")
			}

			batchSize := int(n.IntValue())
			result := []object.Object{}
			for i := 0; i < len(elements); i += batchSize {
				end := i + batchSize
				if end > len(elements) {
					end = len(elements)
				}
				batch := make([]object.Object, end-i)
				copy(batch, elements[i:end])
				result = append(result, newTrackedTuple(batch))
			}
			object.TrackSlice(result)
			return &object.List{Elements: result}
		},
		HelpText: `batched(iterable, n) - Batch elements into tuples of size n

Groups elements into batches of n elements each.

Example:
  itertools.batched([1, 2, 3, 4, 5], 2) -> [(1, 2), (3, 4), (5,)]
  itertools.batched("abcdef", 3) -> [("a", "b", "c"), ("d", "e", "f")]`,
	},
}, nil, "Python-compatible itertools library for iteration utilities")

ItertoolsLibrary provides Python-like itertools functions

View Source
var JSONLibrary = object.NewLibrary(JSONLibraryName, map[string]*object.Builtin{
	"loads": {
		Fn: jsonLoads,
		HelpText: `loads(json_string) - Parse JSON string

Parses a JSON string and returns the corresponding Scriptling object.`,
	},
	"dumps": {
		Fn: jsonDumps,
		HelpText: `dumps(obj, indent="", separators=None, sort_keys=False, ensure_ascii=True) - Serialize object to JSON string

Converts a Scriptling object to its JSON string representation. Dict keys are
emitted in the dict's insertion order, as in Python; sort_keys=True sorts them.
Default separators are (", ", ": "); pass separators=(",", ":") for compact
output. Non-ASCII characters are escaped as \uXXXX unless ensure_ascii=False.
Optional indent parameter for pretty-printing: a string used verbatim,
or a number of spaces (indent=2 is the common idiom; indent=0
newline-separates with no spaces).`,
	},
	"parse": {
		Fn: jsonLoads,
		HelpText: `parse(json_string) - Parse JSON string (alias for loads)

Parses a JSON string and returns the corresponding Scriptling object.`,
	},
	"stringify": {
		Fn: jsonDumps,
		HelpText: `stringify(obj, indent="") - Serialize object to JSON string (alias for dumps)

Converts a Scriptling object to its JSON string representation.
Optional indent parameter for pretty-printing.`,
	},
}, map[string]object.Object{

	"JSONDecodeError": &object.Builtin{
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			message := ""
			if len(args) > 0 {
				if str, err := args[0].AsString(); err == nil {
					message = str
				} else {
					message = args[0].Inspect()
				}
			}
			return &object.Exception{Message: message, ExceptionType: "JSONDecodeError"}
		},
		HelpText: `JSONDecodeError([message]) - Create a JSON decode error`,
	},
}, "JSON encoding and decoding library")
View Source
var MatchClass = &object.Class{
	Name: "Match",
	Methods: map[string]object.Object{
		"group": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if len(args) > 2 {
					return errors.NewError("group() takes at most 1 argument (%d given)", len(args))
				}
				match := args[0].(*object.Instance)
				groups := match.Field("_groups").(*object.List).Elements
				names := match.Field("_group_names").(*object.List).Elements
				groupNum := 0
				if len(args) == 2 {
					if args[1].Type() == object.STRING_OBJ {

						name, _ := args[1].AsString()
						for i, n := range names {
							if ns, ok := n.(*object.String); ok && ns.StringValue() == name {
								return groups[i]
							}
						}
						return &object.Exception{Message: "no such group", ExceptionType: object.ExceptionTypeIndexError, Raised: true}
					}
					if args[1].Type() != object.INTEGER_OBJ {
						return errors.NewTypeError("INTEGER or STRING", args[1].Type().String())
					}
					val, _ := args[1].AsInt()
					groupNum = int(val)
				}
				if groupNum < 0 || groupNum >= len(groups) {
					return &object.Exception{Message: "no such group", ExceptionType: object.ExceptionTypeIndexError, Raised: true}
				}
				return groups[groupNum]
			},
			HelpText: `group([n]) - Return the nth matched group (0 = full match), or the group captured under a (?P<name>...) name`,
		},
		"groupdict": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				match := args[0].(*object.Instance)
				groups := match.Field("_groups").(*object.List).Elements
				names := match.Field("_group_names").(*object.List).Elements
				result := &object.Dict{Pairs: make(map[string]object.DictPair)}
				for i, n := range names {
					ns, ok := n.(*object.String)
					if !ok || ns.StringValue() == "" || i >= len(groups) {
						continue
					}
					result.SetByString(ns.StringValue(), groups[i])
				}
				return result
			},
			HelpText: `groupdict() - Return a dict of named groups to their matched text`,
		},
		"groups": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				match := args[0].(*object.Instance)
				groups := match.Field("_groups").(*object.List).Elements
				if len(groups) <= 1 {
					return &object.Tuple{Elements: []object.Object{}}
				}
				elements := make([]object.Object, len(groups)-1)
				object.TrackSlice(elements)
				for i := 1; i < len(groups); i++ {
					elements[i-1] = groups[i]
				}
				return &object.Tuple{Elements: elements}
			},
			HelpText: `groups() - Return tuple of all matched groups (excluding group 0)`,
		},
		"start": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if len(args) > 2 {
					return errors.NewError("start() takes at most 1 argument (%d given)", len(args))
				}
				match := args[0].(*object.Instance)
				groupNum := 0
				if len(args) == 2 {
					if args[1].Type() != object.INTEGER_OBJ {
						return errors.NewTypeError("INTEGER", args[1].Type().String())
					}
					val, _ := args[1].AsInt()
					groupNum = int(val)
				}
				if groupNum != 0 {
					return errors.NewError("start() only supports group 0")
				}
				return match.Field("_start")
			},
			HelpText: `start(n=0) - Return start position of nth group`,
		},
		"end": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if len(args) > 2 {
					return errors.NewError("end() takes at most 1 argument (%d given)", len(args))
				}
				match := args[0].(*object.Instance)
				groupNum := 0
				if len(args) == 2 {
					if args[1].Type() != object.INTEGER_OBJ {
						return errors.NewTypeError("INTEGER", args[1].Type().String())
					}
					val, _ := args[1].AsInt()
					groupNum = int(val)
				}
				if groupNum != 0 {
					return errors.NewError("end() only supports group 0")
				}
				return match.Field("_end")
			},
			HelpText: `end(n=0) - Return end position of nth group`,
		},
		"span": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if len(args) > 2 {
					return errors.NewError("span() takes at most 1 argument (%d given)", len(args))
				}
				match := args[0].(*object.Instance)
				groupNum := 0
				if len(args) == 2 {
					if args[1].Type() != object.INTEGER_OBJ {
						return errors.NewTypeError("INTEGER", args[1].Type().String())
					}
					val, _ := args[1].AsInt()
					groupNum = int(val)
				}
				if groupNum != 0 {
					return errors.NewError("span() only supports group 0")
				}
				return &object.Tuple{Elements: []object.Object{
					match.Field("_start"),
					match.Field("_end"),
				}}
			},
			HelpText: `span(n=0) - Return (start, end) tuple for nth group`,
		},
	},
}

Match class definition

View Source
var MathLibrary = object.NewLibrary(MathLibraryName, map[string]*object.Builtin{
	"fsum": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			var elements []object.Object
			switch it := args[0].(type) {
			case *object.List:
				elements = it.Elements
			case *object.Tuple:
				elements = it.Elements
			default:
				return errors.NewTypeError("iterable of numbers", args[0].Type().String())
			}
			acc := new(big.Float).SetPrec(128)
			for _, e := range elements {
				switch v := e.(type) {
				case *object.Float:
					acc.Add(acc, big.NewFloat(v.FloatValue()))
				case *object.Integer:
					acc.Add(acc, big.NewFloat(float64(v.IntValue())))
				default:
					return errors.NewTypeError("iterable of numbers", e.Type().String())
				}
			}
			out, _ := acc.Float64()
			return object.NewFloat(out)
		},
		HelpText: `fsum(iterable) - Exact floating-point summation`,
	},
	"sqrt": {
		Fn: oneFloatFunc(math.Sqrt),
		HelpText: `sqrt(x) - Return the square root of x

x must be a non-negative number (integer or float).
Returns a float.`,
	},
	"pow": {
		Fn: twoFloatFunc(math.Pow),
		HelpText: `pow(base, exp) - Return base raised to the power exp

base and exp can be integers or floats.
Returns a float.`,
	},
	"fabs": {
		Fn: oneFloatFunc(math.Abs),
		HelpText: `fabs(x) - Return the absolute value of x as a float

x can be an integer or float.
Always returns a float.`,
	},
	"floor": {
		Fn: oneIntOrFloatFunc(
			func(i *object.Integer) object.Object { return i },
			func(f *object.Float) object.Object { return object.NewInteger(int64(math.Floor(f.FloatValue()))) },
		),
		HelpText: `floor(x) - Return the floor of x

x can be an integer or float.
Returns the largest integer less than or equal to x.`,
	},
	"ceil": {
		Fn: oneIntOrFloatFunc(
			func(i *object.Integer) object.Object { return i },
			func(f *object.Float) object.Object { return object.NewInteger(int64(math.Ceil(f.FloatValue()))) },
		),
		HelpText: `ceil(x) - Return the ceiling of x

x can be an integer or float.
Returns the smallest integer greater than or equal to x.`,
	},

	"sin": {
		Fn: oneFloatFunc(math.Sin),
		HelpText: `sin(x) - Return the sine of x (radians)

x can be an integer or float in radians.
Returns a float.`,
	},
	"cos": {
		Fn: oneFloatFunc(math.Cos),
		HelpText: `cos(x) - Return the cosine of x (radians)

x can be an integer or float in radians.
Returns a float.`,
	},
	"tan": {
		Fn: oneFloatFunc(math.Tan),
		HelpText: `tan(x) - Return the tangent of x (radians)

x can be an integer or float in radians.
Returns a float.`,
	},
	"log": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}
			x, err := args[0].AsFloat()
			if err != nil {
				return errors.NewTypeError("INTEGER or FLOAT", args[0].Type().String())
			}
			if x <= 0 {
				return errors.NewError("log: domain error")
			}
			if len(args) == 2 {
				base, err := args[1].AsFloat()
				if err != nil {
					return errors.NewTypeError("INTEGER or FLOAT", args[1].Type().String())
				}
				if base <= 0 || base == 1 {
					return errors.NewError("log: base must be positive and not 1")
				}
				return object.NewFloat(math.Log(x) / math.Log(base))
			}
			return object.NewFloat(math.Log(x))
		},
		HelpText: `log(x[, base]) - Return the logarithm of x to the given base

With one argument, the natural logarithm. With two, log(x) / log(base).
x must be positive; base must be positive and not 1.`,
	},
	"sinh": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			x, err := args[0].AsFloat()
			if err != nil {
				return errors.NewTypeError("INTEGER or FLOAT", args[0].Type().String())
			}
			return object.NewFloat(math.Sinh(x))
		},
		HelpText: "sinh(x) - Hyperbolic sine",
	},
	"cosh": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			x, err := args[0].AsFloat()
			if err != nil {
				return errors.NewTypeError("INTEGER or FLOAT", args[0].Type().String())
			}
			return object.NewFloat(math.Cosh(x))
		},
		HelpText: "cosh(x) - Hyperbolic cosine",
	},
	"exp2": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			x, err := args[0].AsFloat()
			if err != nil {
				return errors.NewTypeError("INTEGER or FLOAT", args[0].Type().String())
			}
			return object.NewFloat(math.Exp2(x))
		},
		HelpText: "exp2(x) - 2 raised to the power x",
	},
	"ldexp": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			x, err := args[0].AsFloat()
			if err != nil {
				return errors.NewTypeError("INTEGER or FLOAT", args[0].Type().String())
			}
			e, err := args[1].AsInt()
			if err != nil {
				return errors.NewTypeError("INTEGER", args[1].Type().String())
			}
			return object.NewFloat(math.Ldexp(x, int(e)))
		},
		HelpText: "ldexp(x, i) - x * 2**i",
	},
	"frexp": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			x, err := args[0].AsFloat()
			if err != nil {
				return errors.NewTypeError("INTEGER or FLOAT", args[0].Type().String())
			}
			mantissa, exp := math.Frexp(x)
			return &object.List{Elements: []object.Object{object.NewFloat(mantissa), object.NewInteger(int64(exp))}}
		},
		HelpText: "frexp(x) - [mantissa, exponent] with x == mantissa * 2**exponent",
	},
	"exp": {
		Fn: oneFloatFunc(math.Exp),
		HelpText: `exp(x) - Return e raised to the power x

x can be an integer or float.
Returns a float.`,
	},
	"degrees": {
		Fn: oneFloatFunc(func(x float64) float64 { return x * 180.0 / math.Pi }),
		HelpText: `degrees(x) - Convert radians to degrees

x can be an integer or float in radians.
Returns a float in degrees.`,
	},
	"radians": {
		Fn: oneFloatFunc(func(x float64) float64 { return x * math.Pi / 180.0 }),
		HelpText: `radians(x) - Convert degrees to radians

x can be an integer or float in degrees.
Returns a float in radians.`,
	},
	"fmod": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			x, err := args[0].AsFloat()
			if err != nil {
				return errors.NewTypeError("INTEGER or FLOAT", args[0].Type().String())
			}
			y, err := args[1].AsFloat()
			if err != nil {
				return errors.NewTypeError("INTEGER or FLOAT", args[1].Type().String())
			}
			if y == 0 {

				return errors.NewValueError("fmod: division by zero")
			}
			return object.NewFloat(math.Mod(x, y))
		},
		HelpText: `fmod(x, y) - Return the floating-point remainder of x/y

x and y can be integers or floats.
y must not be zero. Returns a float.`,
	},
	"isclose": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			a, err := args[0].AsFloat()
			if err != nil {
				return errors.NewTypeError("NUMBER", args[0].Type().String())
			}
			b, err := args[1].AsFloat()
			if err != nil {
				return errors.NewTypeError("NUMBER", args[1].Type().String())
			}

			relTol := 1e-09
			if v := kwargs.Get("rel_tol"); v != nil {
				relTol, err = v.AsFloat()
				if err != nil {
					return errors.ParameterError("rel_tol", err)
				}
			}
			absTol := 0.0
			if v := kwargs.Get("abs_tol"); v != nil {
				absTol, err = v.AsFloat()
				if err != nil {
					return errors.ParameterError("abs_tol", err)
				}
			}
			if relTol < 0 || absTol < 0 {
				return errors.NewError("tolerances must be non-negative")
			}
			if math.IsNaN(a) || math.IsNaN(b) {
				return object.NewBoolean(false)
			}
			if a == b {
				return object.NewBoolean(true)
			}
			if math.IsInf(a, 0) || math.IsInf(b, 0) {
				return object.NewBoolean(false)
			}
			diff := math.Abs(a - b)
			close := diff <= math.Max(relTol*math.Max(math.Abs(a), math.Abs(b)), absTol)
			if close {
				return object.NewBoolean(true)
			}
			return object.NewBoolean(false)
		},
		HelpText: `isclose(a, b, rel_tol=1e-09, abs_tol=0.0) - Compare floats for near-equality

Returns True if a and b are approximately equal within the relative and
absolute tolerances, matching Python's math.isclose.`,
	},
	"gcd": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			a, err := args[0].AsInt()
			if err != nil {
				return errors.NewTypeError("INTEGER", args[0].Type().String())
			}
			b, err := args[1].AsInt()
			if err != nil {
				return errors.NewTypeError("INTEGER", args[1].Type().String())
			}
			return object.NewInteger(gcd(a, b))
		},
		HelpText: `gcd(a, b) - Return the greatest common divisor of a and b

a and b must be integers.
Returns an integer.`,
	},
	"factorial": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			n, err := args[0].AsInt()
			if err != nil {
				return errors.NewTypeError("INTEGER", args[0].Type().String())
			}
			if n < 0 {
				return errors.NewError("factorial: negative number")
			}
			if n > 20 {
				return errors.NewError("factorial: result too large")
			}
			result := int64(1)
			for i := int64(2); i <= n; i++ {
				result *= i
			}
			return object.NewInteger(result)
		},
		HelpText: `factorial(n) - Return n!

n must be a non-negative integer <= 20.
Returns an integer.`,
	},
	"isnan": {
		Fn: oneIntOrFloatFunc(
			func(i *object.Integer) object.Object { return object.NewBoolean(false) },
			func(f *object.Float) object.Object { return object.NewBoolean(math.IsNaN(f.FloatValue())) },
		),
		HelpText: `isnan(x) - Check if x is NaN (Not a Number)

Returns True if x is NaN, False otherwise.`,
	},
	"isinf": {
		Fn: oneIntOrFloatFunc(
			func(i *object.Integer) object.Object { return object.NewBoolean(false) },
			func(f *object.Float) object.Object { return object.NewBoolean(math.IsInf(f.FloatValue(), 0)) },
		),
		HelpText: `isinf(x) - Check if x is infinite

Returns True if x is positive or negative infinity.`,
	},
	"isfinite": {
		Fn: oneIntOrFloatFunc(
			func(i *object.Integer) object.Object { return object.NewBoolean(true) },
			func(f *object.Float) object.Object {
				return object.NewBoolean(!math.IsNaN(f.FloatValue()) && !math.IsInf(f.FloatValue(), 0))
			},
		),
		HelpText: `isfinite(x) - Check if x is finite

Returns True if x is neither NaN nor infinite.`,
	},
	"copysign": {
		Fn: twoFloatFunc(math.Copysign),
		HelpText: `copysign(x, y) - Return x with the sign of y

Returns a float with magnitude of x and sign of y.`,
	},
	"trunc": {
		Fn: oneIntOrFloatFunc(
			func(i *object.Integer) object.Object { return i },
			func(f *object.Float) object.Object { return object.NewInteger(int64(math.Trunc(f.FloatValue()))) },
		),
		HelpText: `trunc(x) - Truncate x to the nearest integer toward zero

Returns an integer.`,
	},
	"log10": {
		Fn: oneFloatFunc(math.Log10),
		HelpText: `log10(x) - Return the base-10 logarithm of x


x must be positive. Returns a float.`,
	},
	"log2": {
		Fn: oneFloatFunc(math.Log2),
		HelpText: `log2(x) - Return the base-2 logarithm of x

x must be positive. Returns a float.`,
	},
	"hypot": {
		Fn: twoFloatFunc(math.Hypot),
		HelpText: `hypot(x, y) - Return the Euclidean distance sqrt(x*x + y*y)

Returns a float.`,
	},
	"asin": {
		Fn: oneFloatFunc(math.Asin),
		HelpText: `asin(x) - Return the arc sine of x in radians

x must be in the range [-1, 1]. Returns a float.`,
	},
	"acos": {
		Fn: oneFloatFunc(math.Acos),
		HelpText: `acos(x) - Return the arc cosine of x in radians

x must be in the range [-1, 1]. Returns a float.`,
	},
	"atan": {
		Fn: oneFloatFunc(math.Atan),
		HelpText: `atan(x) - Return the arc tangent of x in radians

Returns a float in the range [-pi/2, pi/2].`,
	},
	"atan2": {
		Fn: twoFloatFunc(math.Atan2),
		HelpText: `atan2(y, x) - Return the arc tangent of y/x in radians

Correctly handles the quadrant of the result.
Returns a float in the range [-pi, pi].`,
	},
	"tanh": {
		Fn: oneFloatFunc(math.Tanh),
		HelpText: `tanh(x) - Return the hyperbolic tangent of x

Returns a float in the range [-1, 1].`,
	},
	"softmax": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			var vals []float64
			inputIsFA := false
			switch a := args[0].(type) {
			case *object.FloatArray:
				if a.Is2D() {
					return errors.NewError("softmax: expected 1D array, got 2D")
				}
				vals = a.Data
				inputIsFA = true
			case *object.List:
				n := len(a.Elements)
				if n == 0 {
					return errors.NewError("softmax: input list cannot be empty")
				}
				vals = make([]float64, n)
				for i, el := range a.Elements {
					f, err := el.AsFloat()
					if err != nil {
						return errors.NewTypeError("INTEGER or FLOAT", el.Type().String())
					}
					vals[i] = f
				}
			default:
				return errors.NewTypeError("LIST or FLOAT_ARRAY", args[0].Type().String())
			}
			if len(vals) == 0 {
				return errors.NewError("softmax: input cannot be empty")
			}
			maxVal := math.Inf(-1)
			for _, v := range vals {
				if v > maxVal {
					maxVal = v
				}
			}
			var sum float64
			exps := make([]float64, len(vals))
			for i, v := range vals {
				exps[i] = math.Exp(v - maxVal)
				sum += exps[i]
			}
			result := make([]float64, len(vals))
			for i, e := range exps {
				result[i] = e / sum
			}
			if inputIsFA {
				return object.NewFloatArray1D(result)
			}
			out := make([]object.Object, len(result))
			for i, v := range result {
				out[i] = object.NewFloat(v)
			}
			return &object.List{Elements: out}
		},
		HelpText: `softmax(x) - Return numerically stable softmax of a vector

Returns a FloatArray probability distribution summing to 1.0.`,
	},
	"dot": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			var aData, bData []float64
			switch a := args[0].(type) {
			case *object.FloatArray:
				if a.Is2D() {
					return errors.NewError("dot: expected 1D array, got 2D")
				}
				aData = a.Data
			case *object.List:
				aData = make([]float64, len(a.Elements))
				for i, el := range a.Elements {
					f, err := el.AsFloat()
					if err != nil {
						return errors.NewTypeError("INTEGER or FLOAT", el.Type().String())
					}
					aData[i] = f
				}
			default:
				return errors.NewTypeError("LIST or FLOAT_ARRAY", args[0].Type().String())
			}
			switch b := args[1].(type) {
			case *object.FloatArray:
				if b.Is2D() {
					return errors.NewError("dot: expected 1D array, got 2D")
				}
				bData = b.Data
			case *object.List:
				bData = make([]float64, len(b.Elements))
				for i, el := range b.Elements {
					f, err := el.AsFloat()
					if err != nil {
						return errors.NewTypeError("INTEGER or FLOAT", el.Type().String())
					}
					bData[i] = f
				}
			default:
				return errors.NewTypeError("LIST or FLOAT_ARRAY", args[1].Type().String())
			}
			if len(aData) != len(bData) {
				return errors.NewError("dot: vectors must have the same length")
			}
			var sum float64
			for i := range aData {
				sum += aData[i] * bData[i]
			}
			return object.NewFloat(sum)
		},
		HelpText: `dot(a, b) - Return the dot product of two vectors

a and b must be lists or 1D FloatArrays of numbers with the same length.`,
	},
	"matmul": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			aRows, aFA, errObj := toFloatMatrix(args[0], "matmul", "a")
			if errObj != nil {
				return errObj
			}
			bRows, bFA, errObj := toFloatMatrix(args[1], "matmul", "b")
			if errObj != nil {
				return errObj
			}
			if len(aRows) == 0 || len(bRows) == 0 {
				return errors.NewError("matmul: matrices cannot be empty")
			}
			k := len(aRows[0])
			if len(bRows) != k {
				return errors.NewError("matmul: inner dimensions mismatch (%d vs %d)", k, len(bRows))
			}
			n := len(bRows[0])
			result := make([][]float64, len(aRows))
			for i := range result {
				result[i] = make([]float64, n)
			}
			for i, aRow := range aRows {
				for j := 0; j < n; j++ {
					var sum float64
					for l := 0; l < k; l++ {
						sum += aRow[l] * bRows[l][j]
					}
					result[i][j] = sum
				}
			}
			return toMatrixOutput(result, aFA || bFA, n)
		},
		HelpText: `matmul(a, b) - Matrix-matrix multiply

a is (M x K), b is (K x N). Returns (M x N) matrix as list of lists.`,
	},
	"transpose": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			rows, useFA, errObj := toFloatMatrix(args[0], "transpose", "matrix")
			if errObj != nil {
				return errObj
			}
			if len(rows) == 0 {
				if useFA {
					return object.NewFloatArray2D(nil, 0, 0)
				}
				return &object.List{Elements: []object.Object{}}
			}
			m := len(rows)
			n := len(rows[0])
			result := make([][]float64, n)
			for j := range result {
				result[j] = make([]float64, m)
				for i := 0; i < m; i++ {
					result[j][i] = rows[i][j]
				}
			}
			return toMatrixOutput(result, useFA, m)
		},
		HelpText: `transpose(m) - Transpose a 2D matrix

Rows become columns. Returns a new matrix.`,
	},
	"mat_add": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			aRows, aFA, errObj := toFloatMatrix(args[0], "mat_add", "a")
			if errObj != nil {
				return errObj
			}
			bRows, bFA, errObj := toFloatMatrix(args[1], "mat_add", "b")
			if errObj != nil {
				return errObj
			}
			if len(aRows) != len(bRows) {
				return errors.NewError("mat_add: matrices must have the same shape")
			}
			if len(aRows) == 0 {
				if aFA || bFA {
					return object.NewFloatArray2D(nil, 0, 0)
				}
				return &object.List{Elements: []object.Object{}}
			}
			if len(aRows[0]) != len(bRows[0]) {
				return errors.NewError("mat_add: matrices must have the same shape")
			}
			result := make([][]float64, len(aRows))
			for i := range aRows {
				result[i] = make([]float64, len(aRows[0]))
				for j := range aRows[0] {
					result[i][j] = aRows[i][j] + bRows[i][j]
				}
			}
			return toMatrixOutput(result, aFA || bFA, len(aRows[0]))
		},
		HelpText: `mat_add(a, b) - Element-wise addition of two matrices

a and b must have the same shape. Returns a new matrix.`,
	},
	"erf": {
		Fn: oneFloatFunc(math.Erf),
		HelpText: `erf(x) - Return the error function of x

Returns a float in the range [-1, 1].`,
	},
	"erfc": {
		Fn: oneFloatFunc(math.Erfc),
		HelpText: `erfc(x) - Return the complementary error function of x

Returns a float in the range [0, 2].`,
	},
	"gamma": {
		Fn: oneFloatFunc(math.Gamma),
		HelpText: `gamma(x) - Return the gamma function of x

Returns a float.`,
	},
	"lgamma": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			x, err := args[0].AsFloat()
			if err != nil {
				return errors.NewTypeError("INTEGER or FLOAT", args[0].Type().String())
			}
			val, sign := math.Lgamma(x)
			return &object.List{Elements: []object.Object{
				object.NewFloat(val),
				object.NewInteger(int64(sign)),
			}}
		},
		HelpText: `lgamma(x) - Return the natural log of the absolute value of the gamma function

Returns a list [log_abs_gamma, sign].`,
	},
	"nextafter": {
		Fn: twoFloatFunc(math.Nextafter),
		HelpText: `nextafter(x, y) - Return the next floating-point value after x towards y

Returns a float.`,
	},
	"cbrt": {
		Fn: oneFloatFunc(math.Cbrt),
		HelpText: `cbrt(x) - Return the cube root of x

Returns a float.`,
	},
	"remainder": {
		Fn: twoFloatFunc(math.Remainder),
		HelpText: `remainder(x, y) - Return the IEEE 754-style remainder of x/y

Returns a float.`,
	},
	"log1p": {
		Fn: oneFloatFunc(math.Log1p),
		HelpText: `log1p(x) - Return log(1+x) accurately for small x

Returns a float.`,
	},
	"expm1": {
		Fn: oneFloatFunc(math.Expm1),
		HelpText: `expm1(x) - Return exp(x)-1 accurately for small x

Returns a float.`,
	},
	"comb": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			n, err := args[0].AsInt()
			if err != nil {
				return err
			}
			k, err := args[1].AsInt()
			if err != nil {
				return err
			}
			if n < 0 || k < 0 {
				return errors.NewError("comb: arguments must be non-negative")
			}
			if k > n {
				return object.NewInteger(0)
			}
			if k == 0 || k == n {
				return object.NewInteger(1)
			}
			if k > n-k {
				k = n - k
			}
			var result big.Int
			result.Binomial(n, k)
			if !result.IsInt64() {
				return errors.NewError("comb: result too large")
			}
			return object.NewInteger(result.Int64())
		},
		HelpText: `comb(n, k) - Return the number of ways to choose k items from n

Also known as the binomial coefficient. n and k must be non-negative integers.`,
	},
	"perm": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}
			n, err := args[0].AsInt()
			if err != nil {
				return err
			}
			if n < 0 {
				return errors.NewError("perm: argument must be non-negative")
			}
			k := n
			if len(args) == 2 {
				k, err = args[1].AsInt()
				if err != nil {
					return err
				}
			}
			if k < 0 || k > n {
				return object.NewInteger(0)
			}
			var result int64 = 1
			for i := int64(0); i < k; i++ {
				if result > math.MaxInt64/(n-i) {
					return errors.NewError("perm: result too large")
				}
				result *= n - i
			}
			return object.NewInteger(result)
		},
		HelpText: `perm(n[, k]) - Return the number of ways to choose k items from n with order

n and k must be non-negative integers. If k is omitted, returns n!.`,
	},
	"prod": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			list, ok := args[0].(*object.List)
			if !ok {
				return errors.NewTypeError("LIST", args[0].Type().String())
			}
			if len(list.Elements) == 0 {
				return object.NewInteger(1)
			}
			if kwargs.Has("start") && kwargs.Get("start") != nil {
				result, err := kwargs.Get("start").AsFloat()
				if err != nil {
					return err
				}
				for _, el := range list.Elements {
					v, err := el.AsFloat()
					if err != nil {
						return errors.NewTypeError("INTEGER or FLOAT", el.Type().String())
					}
					result *= v
				}
				return object.NewFloat(result)
			}
			var intResult int64 = 1
			allInt := true
			overflow := false
			for _, el := range list.Elements {
				vInt, ok := el.(*object.Integer)
				if !ok {
					allInt = false
					break
				}
				if overflow || intResult == 0 {
					continue
				}
				if vInt.IntValue() != 0 && intResult > math.MaxInt64/vInt.IntValue() {
					return errors.NewError("integer overflow in product")
				}
				intResult *= vInt.IntValue()
			}
			if allInt && !overflow {
				return object.NewInteger(intResult)
			}
			var result float64 = 1
			for _, el := range list.Elements {
				v, err := el.AsFloat()
				if err != nil {
					return errors.NewTypeError("INTEGER or FLOAT", el.Type().String())
				}
				result *= v
			}
			return object.NewFloat(result)
		},
		HelpText: `prod(iterable, start=1) - Return the product of all elements

With start keyword, begins multiplication from that value.
Returns an integer for all-integer inputs, float otherwise.`,
	},
	"dist": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			pList, pOK := numericVector(args[0])
			if !pOK {
				return errors.NewTypeError("LIST or TUPLE", args[0].Type().String())
			}
			qList, qOK := numericVector(args[1])
			if !qOK {
				return errors.NewTypeError("LIST or TUPLE", args[1].Type().String())
			}
			if len(pList) != len(qList) {
				return errors.NewError("dist: points must have the same dimension")
			}
			var sum float64
			for i := range pList {
				d := pList[i] - qList[i]
				sum += d * d
			}
			return object.NewFloat(math.Sqrt(sum))
		},
		HelpText: `dist(p, q) - Return the Euclidean distance between two points

p and q must be lists of numbers with the same length.`,
	},
	"array": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			switch a := args[0].(type) {
			case *object.FloatArray:
				return a
			case *object.List:
				if len(a.Elements) == 0 {
					return object.NewFloatArray1D([]float64{})
				}
				if inner, ok := a.Elements[0].(*object.List); ok {
					rows := len(a.Elements)
					cols := len(inner.Elements)
					data := make([]float64, 0, rows*cols)
					for _, rowObj := range a.Elements {
						row, ok := rowObj.(*object.List)
						if !ok {
							return errors.NewError("array: cannot mix list rows with non-list rows")
						}
						if len(row.Elements) != cols {
							return errors.NewError("array: all rows must have the same length")
						}
						for _, el := range row.Elements {
							f, err := el.AsFloat()
							if err != nil {
								return errors.NewTypeError("INTEGER or FLOAT", el.Type().String())
							}
							data = append(data, f)
						}
					}
					return object.NewFloatArray2D(data, rows, cols)
				}
				if innerFA, ok := a.Elements[0].(*object.FloatArray); ok && !innerFA.Is2D() {
					cols := len(innerFA.Data)
					rows := len(a.Elements)
					data := make([]float64, 0, rows*cols)
					for _, rowObj := range a.Elements {
						fa, ok := rowObj.(*object.FloatArray)
						if !ok || fa.Is2D() {
							return errors.NewError("array: cannot mix FloatArray rows with non-FloatArray rows")
						}
						if len(fa.Data) != cols {
							return errors.NewError("array: all rows must have the same length")
						}
						data = append(data, fa.Data...)
					}
					return object.NewFloatArray2D(data, rows, cols)
				}
				data := make([]float64, len(a.Elements))
				for i, el := range a.Elements {
					f, err := el.AsFloat()
					if err != nil {
						return errors.NewTypeError("INTEGER or FLOAT", el.Type().String())
					}
					data[i] = f
				}
				return object.NewFloatArray1D(data)
			default:
				return errors.NewTypeError("LIST or FLOAT_ARRAY", args[0].Type().String())
			}
		},
		HelpText: `array(data) - Create an efficient FloatArray from a list

Accepts a 1D list of numbers or a 2D list of lists.
Returns a FloatArray that avoids per-element boxing overhead.`,
	},
	"shape": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			fa, ok := args[0].(*object.FloatArray)
			if !ok {
				return errors.NewTypeError("FLOAT_ARRAY", args[0].Type().String())
			}
			elems := make([]object.Object, len(fa.Shape))
			for i, s := range fa.Shape {
				elems[i] = object.NewInteger(int64(s))
			}
			return &object.List{Elements: elems}
		},
		HelpText: `shape(a) - Return the shape of a FloatArray as a list of ints`,
	},
}, map[string]object.Object{
	"pi":  object.NewFloat(math.Pi),
	"e":   object.NewFloat(math.E),
	"inf": object.NewFloat(math.Inf(1)),
	"nan": object.NewFloat(math.NaN()),
	"tau": object.NewFloat(2 * math.Pi),
}, "Mathematical functions library")
View Source
var MsgpackLibrary = NewMsgpackLibrary(shamaton.New())

MsgpackLibrary is the default library instance, backed by the same codec as gossip's DefaultConfig (shamaton), so a fresh Scriptling instance and a fresh gossip cluster agree on the wire format without any explicit wiring.

View Source
var ParseResultClass = &object.Class{
	Name: "ParseResult",
	Methods: map[string]object.Object{
		"geturl": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				result := args[0].(*object.Instance)

				scheme, _ := result.Field("scheme").(*object.String)
				netloc, _ := result.Field("netloc").(*object.String)
				path, _ := result.Field("path").(*object.String)
				query, _ := result.Field("query").(*object.String)
				fragment, _ := result.Field("fragment").(*object.String)

				url := scheme.StringValue() + "://"
				if netloc.StringValue() != "" {
					url += netloc.StringValue()
				}
				url += path.StringValue()
				if query.StringValue() != "" {
					url += "?" + query.StringValue()
				}
				if fragment.StringValue() != "" {
					url += "#" + fragment.StringValue()
				}

				return object.NewString(url)
			},
			HelpText: `geturl() - Reconstruct URL from parsed components`,
		},
	},
}

ParseResult class for URL parsing results

View Source
var PlatformLibrary = object.NewLibrary(PlatformLibraryName, map[string]*object.Builtin{
	"python_version": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 0); err != nil {
				return err
			}

			return object.NewString(build.Version)
		},
		HelpText: `python_version() - Returns the Python version

Returns the Python version (Scriptling version for compatibility).`,
	},
	"system": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 0); err != nil {
				return err
			}

			switch runtime.GOOS {
			case "darwin":
				return object.NewString("Darwin")
			case "linux":
				return object.NewString("Linux")
			case "windows":
				return object.NewString("Windows")
			case "freebsd":
				return object.NewString("FreeBSD")
			default:
				return object.NewString(runtime.GOOS)
			}
		},
		HelpText: `system() - Returns the system/OS name

Returns 'Darwin', 'Linux', 'Windows', etc.`,
	},
	"architecture": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 0); err != nil {
				return err
			}

			arch := "64bit"
			if runtime.GOARCH == "386" || runtime.GOARCH == "arm" {
				arch = "32bit"
			}

			return &object.List{Elements: []object.Object{object.NewString(arch), object.NewString("")}}
		},
		HelpText: `architecture() - Returns the architecture

Returns a list like ['64bit', ''] indicating bits and linkage.`,
	},
	"machine": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 0); err != nil {
				return err
			}
			return object.NewString(runtime.GOARCH)
		},
		HelpText: `machine() - Returns the machine type (architecture)

Returns 'amd64', 'arm64', 'arm', etc.`,
	},
	"platform": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 0); err != nil {
				return err
			}

			return object.NewString(runtime.GOOS + "-" + runtime.GOARCH)
		},
		HelpText: `platform() - Returns a string identifying the platform

Returns a string like 'darwin-amd64', 'linux-amd64', etc.`,
	},
	"scriptling_version": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 0); err != nil {
				return err
			}
			return object.NewString(build.Version)
		},
		HelpText: `scriptling_version() - Returns Scriptling version string

Returns the current version of Scriptling.`,
	},
	"processor": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 0); err != nil {
				return err
			}
			return object.NewString(runtime.GOARCH)
		},
		HelpText: `processor() - Returns the processor name

Returns the processor name, often same as machine.`,
	},
	"node": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 0); err != nil {
				return err
			}
			if hostname, err := os.Hostname(); err == nil {
				return object.NewString(hostname)
			}
			return object.NewString("")
		},
		HelpText: `node() - Returns the network name (hostname)

Returns the computer's network name.`,
	},
	"release": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 0); err != nil {
				return err
			}

			return object.NewString(build.Version)
		},
		HelpText: `release() - Returns the system release

Returns the system release (Scriptling version for compatibility).`,
	},
	"version": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 0); err != nil {
				return err
			}

			return object.NewString(build.Version)
		},
		HelpText: `version() - Returns the system version

Returns the system version (Scriptling version for compatibility).`,
	},
	"uname": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 0); err != nil {
				return err
			}

			// Get system name (capitalized, matching system() function)
			var systemName string
			switch runtime.GOOS {
			case "darwin":
				systemName = "Darwin"
			case "linux":
				systemName = "Linux"
			case "windows":
				systemName = "Windows"
			case "freebsd":
				systemName = "FreeBSD"
			default:
				systemName = runtime.GOOS
			}

			result := &object.Dict{Pairs: make(map[string]object.DictPair)}
			result.SetByString("system", object.NewString(systemName))
			result.SetByString("machine", object.NewString(runtime.GOARCH))
			result.SetByString("processor", object.NewString(runtime.GOARCH))

			node := ""
			if hostname, err := os.Hostname(); err == nil {
				node = hostname
			}
			result.SetByString("node", object.NewString(node))

			result.SetByString("release", object.NewString(build.Version))
			result.SetByString("version", object.NewString(build.Version))

			return result
		},
		HelpText: `uname() - Returns system information

Returns a dict with keys: system, node, release, version, machine, processor`,
	},
}, nil, "Access to underlying platform's identifying data")

PlatformLibrary provides system/platform information (Python's platform module)

View Source
var RandomLibrary = object.NewLibrary(RandomLibraryName, map[string]*object.Builtin{
	"seed": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MaxArgs(args, 1); err != nil {
				return err
			}
			var seedVal int64
			if len(args) == 0 {
				seedVal = time.Now().UnixNano()
			} else {
				switch arg := args[0].(type) {
				case *object.Integer:
					seedVal = arg.IntValue()
				case *object.Float:
					seedVal = int64(arg.FloatValue())
				default:
					return errors.NewTypeError("INTEGER or FLOAT", args[0].Type().String())
				}
			}
			rng = rand.New(rand.NewSource(seedVal))
			return &object.Null{}
		},
		HelpText: `seed([a]) - Initialize the random number generator

If a is omitted, current time is used. Otherwise, a is used as the seed.`,
	},
	"randint": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			args, kerr := kwargsToPositional("randint", args, kwargs, "a", "b")
			if kerr != nil {
				return kerr
			}
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			var min, max int64
			switch arg := args[0].(type) {
			case *object.Integer:
				min = arg.IntValue()
			default:
				return errors.NewTypeError("INTEGER", args[0].Type().String())
			}
			switch arg := args[1].(type) {
			case *object.Integer:
				max = arg.IntValue()
			default:
				return errors.NewTypeError("INTEGER", args[1].Type().String())
			}
			if min > max {
				return errors.NewError("randint() min must be <= max")
			}
			val := min + rng.Int63n(max-min+1)
			return object.NewInteger(val)
		},
		HelpText: `randint(min, max) - Return random integer

Returns a random integer N such that min <= N <= max.`,
	},
	"random": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 0); err != nil {
				return err
			}
			return object.NewFloat(rng.Float64())
		},
		HelpText: `random() - Return random float

Returns a random float in the range [0.0, 1.0).`,
	},
	"choice": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			if str, ok := args[0].(*object.String); ok {
				if len(str.StringValue()) == 0 {
					return &object.Exception{Message: "choice() string cannot be empty", ExceptionType: object.ExceptionTypeIndexError, Raised: true}
				}
				idx := rng.Intn(len(str.StringValue()))
				return object.NewString(string(str.StringValue()[idx]))
			}
			if list, ok := args[0].(*object.List); ok {
				if len(list.Elements) == 0 {
					return &object.Exception{Message: "choice() list cannot be empty", ExceptionType: object.ExceptionTypeIndexError, Raised: true}
				}
				idx := rng.Intn(len(list.Elements))
				return list.Elements[idx]
			}
			return errors.NewTypeError("LIST or STRING", args[0].Type().String())
		},
		HelpText: `choice(seq) - Return random element from sequence

Returns a randomly selected element from the given list or string.`,
	},
	"shuffle": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			if list, ok := args[0].(*object.List); ok {
				n := len(list.Elements)
				for i := n - 1; i > 0; i-- {
					j := rng.Intn(i + 1)
					list.Elements[i], list.Elements[j] = list.Elements[j], list.Elements[i]
				}
				return &object.Null{}
			}
			return errors.NewTypeError("LIST", args[0].Type().String())
		},
		HelpText: `shuffle(list) - Shuffle list in place

Randomly shuffles the elements of the list in place using the Fisher-Yates algorithm. Returns None.`,
	},
	"uniform": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			args, kerr := kwargsToPositional("uniform", args, kwargs, "a", "b")
			if kerr != nil {
				return kerr
			}
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			var a, b float64
			switch arg := args[0].(type) {
			case *object.Integer:
				a = float64(arg.IntValue())
			case *object.Float:
				a = arg.FloatValue()
			default:
				return errors.NewTypeError("INTEGER or FLOAT", args[0].Type().String())
			}
			switch arg := args[1].(type) {
			case *object.Integer:
				b = float64(arg.IntValue())
			case *object.Float:
				b = arg.FloatValue()
			default:
				return errors.NewTypeError("INTEGER or FLOAT", args[1].Type().String())
			}

			val := a + rng.Float64()*(b-a)
			return object.NewFloat(val)
		},
		HelpText: `uniform(a, b) - Return random float N such that a <= N <= b

Returns a random floating-point number N such that a <= N <= b.`,
	},
	"sample": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			args, kerr := kwargsToPositional("sample", args, kwargs, "population", "k")
			if kerr != nil {
				return kerr
			}
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			k, ok := args[1].(*object.Integer)
			if !ok {
				return errors.NewTypeError("INTEGER", args[1].Type().String())
			}
			// Python accepts any sequence (list, tuple, str, range, ...). A
			// range is indexed rather than expanded, so sampling from
			// range(10**9) costs only k picks.
			var n int64
			var at func(i int64) object.Object
			if it, isIter := args[0].(*object.Iterator); isIter {
				if start, step, count, isRange := it.RangeRemaining(); isRange {
					n = count
					at = func(i int64) object.Object { return object.NewInteger(start + i*step) }
				}
			}
			if at == nil {
				list, errObj := collectIterable(ctx, args[0])
				if errObj != nil {
					return errObj
				}
				n = int64(len(list))
				at = func(i int64) object.Object { return list[i] }
			}
			if k.IntValue() < 0 || k.IntValue() > n {
				return errors.NewValueError("Sample larger than population or is negative")
			}

			swapped := map[int64]int64{}
			index := func(i int64) int64 {
				if v, ok := swapped[i]; ok {
					return v
				}
				return i
			}
			result := make([]object.Object, k.IntValue())
			for i := int64(0); i < k.IntValue(); i++ {
				j := i + rng.Int63n(n-i)
				vi, vj := index(i), index(j)
				swapped[j] = vi
				result[i] = at(vj)
			}
			object.TrackSlice(result)
			return &object.List{Elements: result}
		},
		HelpText: `sample(population, k) - Return k unique random elements from population

Returns a k length list of unique elements chosen from the population sequence.
Used for random sampling without replacement.`,
	},
	"randrange": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) < 1 || len(args) > 3 {
				return errors.NewError("randrange() takes 1 to 3 arguments (%d given)", len(args))
			}
			var start, stop, step int64
			step = 1
			switch len(args) {
			case 1:

				start = 0
				if i, ok := args[0].(*object.Integer); ok {
					stop = i.IntValue()
				} else {
					return errors.NewTypeError("INTEGER", args[0].Type().String())
				}
			case 2:

				if i, ok := args[0].(*object.Integer); ok {
					start = i.IntValue()
				} else {
					return errors.NewTypeError("INTEGER", args[0].Type().String())
				}
				if i, ok := args[1].(*object.Integer); ok {
					stop = i.IntValue()
				} else {
					return errors.NewTypeError("INTEGER", args[1].Type().String())
				}
			case 3:

				if i, ok := args[0].(*object.Integer); ok {
					start = i.IntValue()
				} else {
					return errors.NewTypeError("INTEGER", args[0].Type().String())
				}
				if i, ok := args[1].(*object.Integer); ok {
					stop = i.IntValue()
				} else {
					return errors.NewTypeError("INTEGER", args[1].Type().String())
				}
				if i, ok := args[2].(*object.Integer); ok {
					step = i.IntValue()
				} else {
					return errors.NewTypeError("INTEGER", args[2].Type().String())
				}
			}
			if step == 0 {
				return errors.NewError("randrange() step argument must not be zero")
			}
			var n int64
			if step > 0 {
				n = (stop - start + step - 1) / step
			} else {
				n = (start - stop - step - 1) / (-step)
			}
			if n <= 0 {
				return errors.NewValueError("randrange() empty range")
			}
			return object.NewInteger(start + step*rng.Int63n(n))
		},
		HelpText: `randrange(stop) or randrange(start, stop[, step]) - Return random integer from range

Returns a randomly selected element from range(start, stop, step).
Like randint, but doesn't include the endpoint.`,
	},
	"gauss": {
		Fn: gaussianRandom,
		HelpText: `gauss(mu, sigma) - Return random number from Gaussian distribution

mu is the mean, sigma is the standard deviation.`,
	},
	"normalvariate": {
		Fn: gaussianRandom,
		HelpText: `normalvariate(mu, sigma) - Return random number from normal distribution

mu is the mean, sigma is the standard deviation.
Same as gauss() but provided for compatibility.`,
	},
	"expovariate": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			var lambd float64
			switch arg := args[0].(type) {
			case *object.Integer:
				lambd = float64(arg.IntValue())
			case *object.Float:
				lambd = arg.FloatValue()
			default:
				return errors.NewTypeError("INTEGER or FLOAT", args[0].Type().String())
			}
			if lambd == 0 {
				return errors.NewError("expovariate() lambda must not be zero")
			}

			val := -math.Log(1.0-rng.Float64()) / lambd
			return object.NewFloat(val)
		},
		HelpText: `expovariate(lambd) - Return random number from exponential distribution

lambd is 1.0 divided by the desired mean.`,
	},
	"choices": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.RangeArgs(args, 1, 3); err != nil {
				return err
			}
			for _, key := range kwargs.Keys() {
				if key != "weights" && key != "cum_weights" && key != "k" {
					return newArgTypeError("choices() got an unexpected keyword argument '%s'", key)
				}
			}
			population, errObj := collectIterable(ctx, args[0])
			if errObj != nil {
				return errObj
			}
			n := len(population)

			weightList := func(name string, obj object.Object) ([]float64, object.Object) {
				if obj == nil {
					return nil, nil
				}
				if _, isNull := obj.(*object.Null); isNull {
					return nil, nil
				}
				var elems []object.Object
				switch w := obj.(type) {
				case *object.List:
					elems = w.Elements
				case *object.Tuple:
					elems = w.Elements
				default:
					return nil, errors.NewTypeError("LIST", obj.Type().String())
				}
				if len(elems) != n {
					return nil, errors.NewValueError("The number of %s does not match the population", name)
				}
				out := make([]float64, n)
				for i, w := range elems {
					f, err := w.AsFloat()
					if err != nil {
						return nil, errors.NewTypeError("INTEGER or FLOAT", w.Type().String())
					}
					if math.IsNaN(f) || math.IsInf(f, 0) {
						return nil, errors.NewError("choices: %s must be finite", name)
					}
					out[i] = f
				}
				return out, nil
			}

			wObj := kwargs.Get("weights")
			if len(args) >= 2 {
				if kwargs.Has("weights") {
					return errors.NewError("choices: weights specified both positionally and by keyword")
				}
				wObj = args[1]
			}
			weights, werr := weightList("weights", wObj)
			if werr != nil {
				return werr
			}
			cumWeights, cerr := weightList("cum_weights", kwargs.Get("cum_weights"))
			if cerr != nil {
				return cerr
			}
			if weights != nil && cumWeights != nil {
				return newArgTypeError("Cannot specify both weights and cumulative weights")
			}
			k := 1
			kObj := kwargs.Get("k")
			if len(args) >= 3 {
				if kwargs.Has("k") {
					return errors.NewError("choices: k specified both positionally and by keyword")
				}
				kObj = args[2]
			}
			if kObj != nil {
				kVal, err := kObj.AsInt()
				if err != nil {
					return err
				}
				if kVal < 0 {
					return errors.NewError("choices: k must be non-negative")
				}
				k = int(kVal)
			}
			if k == 0 {
				return &object.List{Elements: []object.Object{}}
			}
			if n == 0 {
				return &object.Exception{Message: "Cannot choose from an empty sequence", ExceptionType: object.ExceptionTypeIndexError, Raised: true}
			}

			if cumWeights == nil {
				if weights == nil {
					weights = make([]float64, n)
					for i := range weights {
						weights[i] = 1.0
					}
				}
				for _, f := range weights {
					if f < 0 {
						return errors.NewError("choices: weights must be finite and non-negative")
					}
				}
				cumWeights = make([]float64, n)
				cumWeights[0] = weights[0]
				for i := 1; i < n; i++ {
					cumWeights[i] = cumWeights[i-1] + weights[i]
				}
			}
			total := cumWeights[n-1]
			if math.IsInf(total, 0) || math.IsNaN(total) {
				return errors.NewValueError("Total of weights must be finite")
			}
			if total <= 0 {
				return errors.NewValueError("Total of weights must be greater than zero")
			}

			result := make([]object.Object, k)
			for i := 0; i < k; i++ {
				r := rng.Float64() * total
				idx := 0
				for idx < n-1 && cumWeights[idx] <= r {
					idx++
				}
				result[i] = population[idx]
			}
			object.TrackSlice(result)
			return &object.List{Elements: result}
		},
		HelpText: `choices(population, weights=None, *, cum_weights=None, k=1) - Weighted random sampling with replacement

Select k items from population with the given relative weights, or with
cumulative weights (cum_weights). Specifying both is an error.
Returns a list of k selected items.`,
	},
	"betavariate": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			alpha, err := args[0].AsFloat()
			if err != nil {
				return errors.NewTypeError("INTEGER or FLOAT", args[0].Type().String())
			}
			beta, err := args[1].AsFloat()
			if err != nil {
				return errors.NewTypeError("INTEGER or FLOAT", args[1].Type().String())
			}
			if alpha <= 0 || beta <= 0 {
				return errors.NewError("betavariate: alpha and beta must be positive")
			}

			x := sampleGamma(alpha)
			y := sampleGamma(beta)
			if x+y == 0 {
				return object.NewFloat(0)
			}
			return object.NewFloat(x / (x + y))
		},
		HelpText: `betavariate(alpha, beta) - Return random number from beta distribution

alpha and beta must be positive. Returns a float in [0, 1].`,
	},
	"gammavariate": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			alpha, err := args[0].AsFloat()
			if err != nil {
				return errors.NewTypeError("INTEGER or FLOAT", args[0].Type().String())
			}
			beta, err := args[1].AsFloat()
			if err != nil {
				return errors.NewTypeError("INTEGER or FLOAT", args[1].Type().String())
			}
			if alpha <= 0 || beta <= 0 {
				return errors.NewError("gammavariate: alpha and beta must be positive")
			}
			return object.NewFloat(sampleGamma(alpha) * beta)
		},
		HelpText: `gammavariate(alpha, beta) - Return random number from gamma distribution

alpha (shape) and beta (scale) must be positive.`,
	},
	"triangular": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MaxArgs(args, 3); err != nil {
				return err
			}
			if err := checkKwargs("triangular", kwargs, "low", "high", "mode"); err != nil {
				return err
			}

			vals := [3]float64{0, 1, 0}
			hasMode := false
			for i, name := range []string{"low", "high", "mode"} {
				v, aerr := optionalArg("triangular", args, kwargs, i, name)
				if aerr != nil {
					return aerr
				}
				if v == nil {

					if name != "mode" && (len(args) > i || kwargs.Has(name)) {
						return errors.NewTypeError("INTEGER or FLOAT", "NULL")
					}
					continue
				}
				f, err := v.AsFloat()
				if err != nil {
					return errors.NewTypeError("INTEGER or FLOAT", v.Type().String())
				}
				vals[i] = f
				hasMode = hasMode || i == 2
			}
			low, high, mode := vals[0], vals[1], vals[2]
			if !hasMode {
				mode = (low + high) / 2.0
			}
			if low == high {
				return object.NewFloat(low)
			}
			u := rng.Float64()
			c := (mode - low) / (high - low)
			var val float64
			if u <= c {
				val = low + math.Sqrt(u*(high-low)*(mode-low))
			} else {
				val = high - math.Sqrt((1-u)*(high-low)*(high-mode))
			}
			return object.NewFloat(val)
		},
		HelpText: `triangular(low, high[, mode]) - Return random number from triangular distribution

low is the minimum, high is the maximum, mode is the peak (defaults to midpoint).`,
	},
	"paretovariate": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			alpha, err := args[0].AsFloat()
			if err != nil {
				return errors.NewTypeError("INTEGER or FLOAT", args[0].Type().String())
			}
			if alpha <= 0 {
				return errors.NewError("paretovariate: alpha must be positive")
			}

			u := rng.Float64()
			if u == 0 {
				u = math.SmallestNonzeroFloat64
			}
			return object.NewFloat(1.0 / math.Pow(u, 1.0/alpha))
		},
		HelpText: `paretovariate(alpha) - Return random number from Pareto distribution

alpha is the shape parameter, must be positive.`,
	},
	"weibullvariate": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			alpha, err := args[0].AsFloat()
			if err != nil {
				return errors.NewTypeError("INTEGER or FLOAT", args[0].Type().String())
			}
			beta, err := args[1].AsFloat()
			if err != nil {
				return errors.NewTypeError("INTEGER or FLOAT", args[1].Type().String())
			}
			if alpha <= 0 || beta <= 0 {
				return errors.NewError("weibullvariate: alpha and beta must be positive")
			}

			u := rng.Float64()
			if u == 0 {
				u = math.SmallestNonzeroFloat64
			}
			return object.NewFloat(alpha * math.Pow(-math.Log(u), 1.0/beta))
		},
		HelpText: `weibullvariate(alpha, beta) - Return random number from Weibull distribution

alpha is the scale parameter, beta is the shape parameter. Both must be positive.`,
	},
}, nil, "Random number generation library")
View Source
var ReLibrary = object.NewLibrary(ReLibraryName, map[string]*object.Builtin{
	"match": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) < 2 || len(args) > 3 {
				return errors.NewError("match() takes 2 or 3 arguments (%d given)", len(args))
			}
			if args[0].Type() != object.STRING_OBJ || args[1].Type() != object.STRING_OBJ {
				return errors.NewTypeError("STRING", "mixed types")
			}
			pattern, _ := args[0].AsString()
			text, _ := args[1].AsString()

			flags, err := getFlags(args, kwargs, 2)
			if err != nil {
				return errors.NewError("%s", err.Error())
			}
			pattern = applyFlags(pattern, flags)

			re, err := GetCompiledRegex(pattern)
			if err != nil {
				return errors.NewError("regex compile error: %s", err.Error())
			}

			match := re.FindStringSubmatchIndex(text)
			if match == nil || match[0] != 0 {
				return &object.Null{}
			}

			groups := make([]string, 0)
			for i := 0; i < len(match); i += 2 {
				if match[i] >= 0 && match[i+1] >= 0 {
					groups = append(groups, text[match[i]:match[i+1]])
				} else {
					groups = append(groups, "")
				}
			}

			return createMatchInstance(text, groups, match[0], match[1], re.SubexpNames())
		},
		HelpText: `match(pattern, string, flags=0) - Match pattern at start of string

Returns a Match object if the pattern matches at the beginning, or None if no match.
Use match.group(0) for the full match, match.group(1) for the first group, etc.

Methods on Match object:
  group(n=0)  - Return the nth matched group (0 = full match)
  groups()    - Return tuple of all matched groups (excluding group 0)
  start(n=0)  - Return start position of nth group
  end(n=0)    - Return end position of nth group
  span(n=0)   - Return (start, end) tuple for nth group

Flags:
  re.IGNORECASE or re.I - Case-insensitive matching
  re.MULTILINE or re.M  - ^ and $ match at line boundaries
  re.DOTALL or re.S     - . matches newlines`,
	},
	"search": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) < 2 || len(args) > 3 {
				return errors.NewError("search() takes 2 or 3 arguments (%d given)", len(args))
			}
			if args[0].Type() != object.STRING_OBJ || args[1].Type() != object.STRING_OBJ {
				return errors.NewTypeError("STRING", "mixed types")
			}
			pattern, _ := args[0].AsString()
			text, _ := args[1].AsString()

			flags, err := getFlags(args, kwargs, 2)
			if err != nil {
				return errors.NewError("%s", err.Error())
			}
			pattern = applyFlags(pattern, flags)

			re, err := GetCompiledRegex(pattern)
			if err != nil {
				return errors.NewError("regex compile error: %s", err.Error())
			}

			match := re.FindStringSubmatchIndex(text)
			if match == nil {
				return &object.Null{}
			}

			groups := make([]string, 0)
			for i := 0; i < len(match); i += 2 {
				if match[i] >= 0 && match[i+1] >= 0 {
					groups = append(groups, text[match[i]:match[i+1]])
				} else {
					groups = append(groups, "")
				}
			}

			return createMatchInstance(text, groups, match[0], match[1], re.SubexpNames())
		},
		HelpText: `search(pattern, string, flags=0) - Search for pattern anywhere in string

Returns a Match object for the first match, or None if no match found.
Use match.group(0) for the full match, match.group(1) for the first group, etc.

Methods on Match object:
  group(n=0)  - Return the nth matched group (0 = full match)
  groups()    - Return tuple of all matched groups (excluding group 0)
  start(n=0)  - Return start position of nth group
  end(n=0)    - Return end position of nth group
  span(n=0)   - Return (start, end) tuple for nth group

Flags:
  re.IGNORECASE or re.I - Case-insensitive matching
  re.MULTILINE or re.M  - ^ and $ match at line boundaries
  re.DOTALL or re.S     - . matches newlines`,
	},
	"findall": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) < 2 || len(args) > 3 {
				return errors.NewError("findall() takes 2 or 3 arguments (%d given)", len(args))
			}
			if args[0].Type() != object.STRING_OBJ || args[1].Type() != object.STRING_OBJ {
				return errors.NewTypeError("STRING", "mixed types")
			}
			pattern, _ := args[0].AsString()
			text, _ := args[1].AsString()

			flags, err := getFlags(args, kwargs, 2)
			if err != nil {
				return errors.NewError("%s", err.Error())
			}
			pattern = applyFlags(pattern, flags)

			re, err := GetCompiledRegex(pattern)
			if err != nil {
				return errors.NewError("regex compile error: %s", err.Error())
			}

			matches := re.FindAllStringSubmatch(text, -1)
			elements := make([]object.Object, len(matches))
			object.TrackSlice(elements)
			numGroups := re.NumSubexp()
			for i, match := range matches {
				if numGroups == 0 {
					elements[i] = object.NewSubstring(match[0], text)
				} else if numGroups == 1 {
					elements[i] = object.NewSubstring(match[1], text)
				} else {
					groupElements := make([]object.Object, numGroups)
					object.TrackSlice(groupElements)
					for j := 1; j <= numGroups; j++ {
						groupElements[j-1] = object.NewSubstring(match[j], text)
					}
					elements[i] = &object.Tuple{Elements: groupElements}
				}
			}
			return &object.List{Elements: elements}
		},
		HelpText: `findall(pattern, string, flags=0) - Find all matches

Returns a list of all substrings that match the regex pattern.
If the pattern contains capturing groups, returns a list of tuples containing the groups.
If there is one capturing group, returns a list of strings for that group.
If there are no capturing groups, returns a list of strings for the full matches.

Flags:
  re.IGNORECASE or re.I - Case-insensitive matching
  re.MULTILINE or re.M  - ^ and $ match at line boundaries
  re.DOTALL or re.S     - . matches newlines`,
	},
	"finditer": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) < 2 || len(args) > 3 {
				return errors.NewError("finditer() takes 2 or 3 arguments (%d given)", len(args))
			}
			if args[0].Type() != object.STRING_OBJ || args[1].Type() != object.STRING_OBJ {
				return errors.NewTypeError("STRING", "mixed types")
			}
			pattern, _ := args[0].AsString()
			text, _ := args[1].AsString()

			flags, err := getFlags(args, kwargs, 2)
			if err != nil {
				return errors.NewError("%s", err.Error())
			}
			pattern = applyFlags(pattern, flags)

			re, err := GetCompiledRegex(pattern)
			if err != nil {
				return errors.NewError("regex compile error: %s", err.Error())
			}

			allMatches := re.FindAllStringSubmatchIndex(text, -1)
			elements := make([]object.Object, len(allMatches))
			object.TrackSlice(elements)
			for i, match := range allMatches {
				elements[i] = createMatchInstance(text, groupsFromIndices(text, match), match[0], match[1], re.SubexpNames())
			}
			return &object.List{Elements: elements}
		},
		HelpText: `finditer(pattern, string, flags=0) - Find all matches as Match objects

Returns a list of Match objects for all matches of the regex pattern in the string.

Flags:
  re.IGNORECASE or re.I - Case-insensitive matching
  re.MULTILINE or re.M  - ^ and $ match at line boundaries
  re.DOTALL or re.S     - . matches newlines`,
	},
	"sub": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) < 3 || len(args) > 5 {
				return errors.NewError("sub() takes 3 to 5 arguments (%d given)", len(args))
			}
			if args[0].Type() != object.STRING_OBJ {
				return errors.NewTypeError("STRING", args[0].Type().String())
			}
			if args[2].Type() != object.STRING_OBJ {
				return errors.NewTypeError("STRING", args[2].Type().String())
			}
			pattern, _ := args[0].AsString()
			text, _ := args[2].AsString()

			isFunc := false
			var replacementStr string
			var replacementFunc object.Object

			switch args[1].Type() {
			case object.STRING_OBJ:
				replacementStr, _ = args[1].AsString()
			case object.FUNCTION_OBJ, object.BUILTIN_OBJ, object.LAMBDA_OBJ:
				isFunc = true
				replacementFunc = args[1]
			default:
				return errors.NewTypeError("STRING or FUNCTION", args[1].Type().String())
			}

			count := -1
			countObj := object.Object(nil)
			if len(args) > 3 {
				countObj = args[3]
			} else if v := kwargs.Get("count"); v != nil {
				countObj = v
			}
			if countObj != nil {
				if countObj.Type() != object.INTEGER_OBJ {
					return errors.NewError("count must be an integer")
				}
				val, _ := countObj.AsInt()
				count = int(val)
				if count == 0 {
					count = -1
				}
			}

			flags, err := getFlags(args, kwargs, 4)
			if err != nil {
				return errors.NewError("%s", err.Error())
			}
			pattern = applyFlags(pattern, flags)

			re, err := GetCompiledRegex(pattern)
			if err != nil {
				return errors.NewError("regex compile error: %s", err.Error())
			}

			var result string
			if isFunc {

				replaced := 0
				allMatches := re.FindAllStringSubmatchIndex(text, -1)

				if len(allMatches) == 0 {
					return object.NewString(text)
				}

				// Build result by replacing matches
				var resultBuilder []byte
				lastEnd := 0

				for _, match := range allMatches {
					if count > 0 && replaced >= count {
						break
					}

					resultBuilder = append(resultBuilder, text[lastEnd:match[0]]...)

					matchObj := createMatchInstance(text, groupsFromIndices(text, match), match[0], match[1], re.SubexpNames())

					// Call the replacement function
					var resultObj object.Object
					if replacementFunc.Type() == object.BUILTIN_OBJ {
						builtin := replacementFunc.(*object.Builtin)
						resultObj = builtin.Fn(ctx, object.Kwargs{}, matchObj)
					} else {
						eval := evaliface.FromContext(ctx)
						if eval == nil {
							return errors.NewError("evaluator not available in context")
						}
						env := object.NewEnvironment()
						if ctxEnv, ok := ctx.Value("scriptling-env").(*object.Environment); ok {
							env = ctxEnv
						}
						resultObj = eval.CallObjectFunction(ctx, replacementFunc, []object.Object{matchObj}, nil, env)
					}

					if resultObj.Type() == object.ERROR_OBJ || resultObj.Type() == object.EXCEPTION_OBJ {
						return resultObj
					}

					if resultStr, err := resultObj.AsString(); err == nil {
						resultBuilder = append(resultBuilder, resultStr...)
					} else {

						resultBuilder = append(resultBuilder, text[match[0]:match[1]]...)
					}

					lastEnd = match[1]
					replaced++
				}

				resultBuilder = append(resultBuilder, text[lastEnd:]...)
				result = string(resultBuilder)
			} else if count < 0 {
				result = re.ReplaceAllString(text, pythonReplToGo(replacementStr))
			} else {

				replaced := 0
				result = re.ReplaceAllStringFunc(text, func(match string) string {
					if replaced < count {
						replaced++
						return re.ReplaceAllString(match, pythonReplToGo(replacementStr))
					}
					return match
				})
			}
			return object.NewString(result)
		},
		HelpText: `sub(pattern, repl, string, count=0, flags=0) - Replace matches

Replaces occurrences of the regex pattern in the string with the replacement.
The replacement can be either a string or a function that takes a Match object and returns a string.
If count is 0 (default), all occurrences are replaced.
If count > 0, only the first count occurrences are replaced.

Flags:
  re.IGNORECASE or re.I - Case-insensitive matching
  re.MULTILINE or re.M  - ^ and $ match at line boundaries
  re.DOTALL or re.S     - . matches newlines`,
	},
	"subn": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			result := reSubBuiltin.Fn(ctx, kwargs, args...)
			if object.IsError(result) || result.Type() == object.EXCEPTION_OBJ {
				return result
			}
			newText, _ := result.(*object.String)

			pattern, _ := args[0].AsString()
			text, _ := args[2].AsString()
			count := int64(-1)
			countObj := object.Object(nil)
			if len(args) > 3 {
				countObj = args[3]
			} else if v := kwargs.Get("count"); v != nil {
				countObj = v
			}
			if countObj != nil {
				if v, err := countObj.AsInt(); err == nil {
					count = v
				}
			}
			if flags, err := getFlags(args, kwargs, 4); err == nil {
				pattern = applyFlags(pattern, flags)
			}
			re, err := GetCompiledRegex(pattern)
			if err != nil {
				return errors.NewError("regex compile error: %s", err.Error())
			}
			all := re.FindAllStringIndex(text, int(count))
			return &object.Tuple{Elements: []object.Object{newText, object.NewInteger(int64(len(all)))}}
		},
		HelpText: `subn(pattern, repl, string, count=0) - Replace matches, returning (new_string, n)

Like sub(), but returns a tuple of the modified string and the number of
substitutions made.`,
	},
	"split": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) < 2 || len(args) > 4 {
				return errors.NewError("split() takes 2 to 4 arguments (%d given)", len(args))
			}
			if args[0].Type() != object.STRING_OBJ || args[1].Type() != object.STRING_OBJ {
				return errors.NewTypeError("STRING", "mixed types")
			}
			pattern, _ := args[0].AsString()
			text, _ := args[1].AsString()

			maxsplit := -1
			maxObj := object.Object(nil)
			if len(args) > 2 {
				maxObj = args[2]
			} else if v := kwargs.Get("maxsplit"); v != nil {
				maxObj = v
			}
			if maxObj != nil {
				if maxObj.Type() != object.INTEGER_OBJ {
					return errors.NewError("maxsplit must be an integer")
				}
				val, _ := maxObj.AsInt()
				maxsplit = int(val)
				if maxsplit == 0 {
					maxsplit = -1
				}
			}

			flags, err := getFlags(args, kwargs, 3)
			if err != nil {
				return errors.NewError("%s", err.Error())
			}
			pattern = applyFlags(pattern, flags)

			re, err := GetCompiledRegex(pattern)
			if err != nil {
				return errors.NewError("regex compile error: %s", err.Error())
			}

			splitN := maxsplit
			if maxsplit > 0 {
				splitN = maxsplit + 1
			}
			parts := re.Split(text, splitN)
			elements := make([]object.Object, len(parts))
			object.TrackSlice(elements)
			for i, part := range parts {
				elements[i] = object.NewSubstring(part, text)
			}
			return &object.List{Elements: elements}
		},
		HelpText: `split(pattern, string, maxsplit=0, flags=0) - Split string by pattern

Splits the string by occurrences of the regex pattern and returns a list of substrings.
If maxsplit is 0 (default), all occurrences are split.
If maxsplit > 0, at most maxsplit splits are done.

Flags:
  re.IGNORECASE or re.I - Case-insensitive matching
  re.MULTILINE or re.M  - ^ and $ match at line boundaries
  re.DOTALL or re.S     - . matches newlines`,
	},
	"compile": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) < 1 || len(args) > 2 {
				return errors.NewError("compile() takes 1 or 2 arguments (%d given)", len(args))
			}
			if args[0].Type() != object.STRING_OBJ {
				return errors.NewTypeError("STRING", args[0].Type().String())
			}
			pattern, _ := args[0].AsString()

			flags, err := getFlags(args, kwargs, 1)
			if err != nil {
				return errors.NewError("%s", err.Error())
			}
			pattern = applyFlags(pattern, flags)

			_, err = GetCompiledRegex(pattern)
			if err != nil {
				return errors.NewError("regex compile error: %s", err.Error())
			}

			return createRegexInstance(pattern, flags)
		},
		HelpText: `compile(pattern, flags=0) - Compile regex pattern

Validates and caches a regex pattern for later use. Returns the pattern if valid.

Flags:
  re.IGNORECASE or re.I - Case-insensitive matching
  re.MULTILINE or re.M  - ^ and $ match at line boundaries
  re.DOTALL or re.S     - . matches newlines`,
	},
	"escape": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			if args[0].Type() != object.STRING_OBJ {
				return errors.NewTypeError("STRING", args[0].Type().String())
			}
			text, _ := args[0].AsString()

			escaped := regexp.QuoteMeta(text)
			return object.NewString(escaped)
		},
		HelpText: `escape(pattern) - Escape special regex characters

Returns a string with all special regex characters escaped.`,
	},
	"fullmatch": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) < 2 || len(args) > 3 {
				return errors.NewError("fullmatch() takes 2 or 3 arguments (%d given)", len(args))
			}
			if args[0].Type() != object.STRING_OBJ || args[1].Type() != object.STRING_OBJ {
				return errors.NewTypeError("STRING", "mixed types")
			}
			pattern, _ := args[0].AsString()
			text, _ := args[1].AsString()

			flags, err := getFlags(args, kwargs, 2)
			if err != nil {
				return errors.NewError("%s", err.Error())
			}
			pattern = applyFlags(pattern, flags)

			re, err := GetCompiledRegex(pattern)
			if err != nil {
				return errors.NewError("regex compile error: %s", err.Error())
			}

			match := re.FindStringSubmatchIndex(text)
			if match == nil || match[0] != 0 || match[1] != len(text) {
				return &object.Null{}
			}

			groups := make([]string, 0)
			for i := 0; i < len(match); i += 2 {
				if match[i] >= 0 && match[i+1] >= 0 {
					groups = append(groups, text[match[i]:match[i+1]])
				} else {
					groups = append(groups, "")
				}
			}

			return createMatchInstance(text, groups, match[0], match[1], re.SubexpNames())
		},
		HelpText: `fullmatch(pattern, string, flags=0) - Match entire string

Returns a Match object if the regex pattern matches the entire string, or None if no match.

Flags:
  re.IGNORECASE or re.I - Case-insensitive matching
  re.MULTILINE or re.M  - ^ and $ match at line boundaries
  re.DOTALL or re.S     - . matches newlines`,
	},
}, map[string]object.Object{

	"IGNORECASE": object.NewInteger(RE_IGNORECASE),
	"I":          object.NewInteger(RE_IGNORECASE),
	"MULTILINE":  object.NewInteger(RE_MULTILINE),
	"M":          object.NewInteger(RE_MULTILINE),
	"DOTALL":     object.NewInteger(RE_DOTALL),
	"S":          object.NewInteger(RE_DOTALL),
}, "Regular expression library")
View Source
var RegexClass = &object.Class{
	Name: "Regex",
	Methods: map[string]object.Object{
		"match": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 2); err != nil {
					return err
				}
				if args[1].Type() != object.STRING_OBJ {
					return errors.NewTypeError("STRING", args[1].Type().String())
				}
				regex := args[0].(*object.Instance)
				pattern := regex.Field("pattern").(*object.String).StringValue()
				text, _ := args[1].AsString()

				re, err := GetCompiledRegex(pattern)
				if err != nil {
					return errors.NewError("regex compile error: %s", err.Error())
				}

				match := re.FindStringSubmatchIndex(text)
				if match == nil || match[0] != 0 {
					return &object.Null{}
				}

				return createMatchInstance(text, groupsFromIndices(text, match), match[0], match[1], re.SubexpNames())
			},
			HelpText: `match(string) - Match pattern at start of string

Returns a Match object if the pattern matches at the beginning, or None if no match.`,
		},
		"search": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 2); err != nil {
					return err
				}
				if args[1].Type() != object.STRING_OBJ {
					return errors.NewTypeError("STRING", args[1].Type().String())
				}
				regex := args[0].(*object.Instance)
				pattern := regex.Field("pattern").(*object.String).StringValue()
				text, _ := args[1].AsString()

				re, err := GetCompiledRegex(pattern)
				if err != nil {
					return errors.NewError("regex compile error: %s", err.Error())
				}

				match := re.FindStringSubmatchIndex(text)
				if match == nil {
					return &object.Null{}
				}

				return createMatchInstance(text, groupsFromIndices(text, match), match[0], match[1], re.SubexpNames())
			},
			HelpText: `search(string) - Search for pattern anywhere in string

Returns a Match object for the first match, or None if no match found.`,
		},
		"findall": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 2); err != nil {
					return err
				}
				if args[1].Type() != object.STRING_OBJ {
					return errors.NewTypeError("STRING", args[1].Type().String())
				}
				regex := args[0].(*object.Instance)
				pattern := regex.Field("pattern").(*object.String).StringValue()
				text, _ := args[1].AsString()

				re, err := GetCompiledRegex(pattern)
				if err != nil {
					return errors.NewError("regex compile error: %s", err.Error())
				}

				numGroups := re.NumSubexp()

				if numGroups == 0 {
					matches := re.FindAllString(text, -1)
					elements := make([]object.Object, len(matches))
					object.TrackSlice(elements)
					for i, match := range matches {
						elements[i] = object.NewSubstring(match, text)
					}
					return &object.List{Elements: elements}
				}

				matches := re.FindAllStringSubmatchIndex(text, -1)
				elements := make([]object.Object, len(matches))
				object.TrackSlice(elements)
				for i, match := range matches {
					if numGroups == 1 {
						start, end := match[2], match[3]
						if start >= 0 && end >= 0 {
							elements[i] = object.NewSubstring(text[start:end], text)
						} else {
							elements[i] = object.NewString("")
						}
						continue
					}

					groupElements := make([]object.Object, numGroups)
					object.TrackSlice(groupElements)
					for j := 0; j < numGroups; j++ {
						start, end := match[(j+1)*2], match[(j+1)*2+1]
						if start >= 0 && end >= 0 {
							groupElements[j] = object.NewSubstring(text[start:end], text)
						} else {
							groupElements[j] = object.NewString("")
						}
					}
					elements[i] = &object.Tuple{Elements: groupElements}
				}
				return &object.List{Elements: elements}
			},
			HelpText: `findall(string) - Find all matches

Returns a list of all substrings that match the regex pattern.
If the pattern contains capturing groups, returns a list of tuples containing the groups.
If there is one capturing group, returns a list of strings for that group.
If there are no capturing groups, returns a list of strings for the full matches.`,
		},
		"finditer": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 2); err != nil {
					return err
				}
				if args[1].Type() != object.STRING_OBJ {
					return errors.NewTypeError("STRING", args[1].Type().String())
				}
				regex := args[0].(*object.Instance)
				pattern := regex.Field("pattern").(*object.String).StringValue()
				text, _ := args[1].AsString()

				re, err := GetCompiledRegex(pattern)
				if err != nil {
					return errors.NewError("regex compile error: %s", err.Error())
				}

				allMatches := re.FindAllStringSubmatchIndex(text, -1)
				elements := make([]object.Object, len(allMatches))
				object.TrackSlice(elements)
				for i, match := range allMatches {

					groups := make([]string, 0)
					for j := 0; j < len(match); j += 2 {
						if match[j] >= 0 && match[j+1] >= 0 {
							groups = append(groups, text[match[j]:match[j+1]])
						} else {
							groups = append(groups, "")
						}
					}
					elements[i] = createMatchInstance(text, groups, match[0], match[1], re.SubexpNames())
				}
				return &object.List{Elements: elements}
			},
			HelpText: `finditer(string) - Find all matches as Match objects

Returns a list of Match objects for all matches of the regex pattern in the string.`,
		},
	},
}

Regex class definition

View Source
var StatisticsLibrary = object.NewLibrary(StatisticsLibraryName, map[string]*object.Builtin{
	"mean": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			values, err := extractNumbers(args[0])
			if err != nil {
				return err
			}
			if len(values) == 0 {
				return errors.NewError("mean requires at least one data point")
			}
			sum := 0.0
			for _, v := range values {
				sum += v
			}
			return object.NewFloat(sum / float64(len(values)))
		},
		HelpText: `mean(data) - Return the arithmetic mean of data

Parameters:
  data - List of numbers

Returns: Float

Example:
  import statistics
  statistics.mean([1, 2, 3, 4, 5])  # 3.0`,
	},
	"fmean": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			values, err := extractNumbers(args[0])
			if err != nil {
				return err
			}
			if len(values) == 0 {
				return errors.NewError("fmean requires at least one data point")
			}
			sum := 0.0
			for _, v := range values {
				sum += v
			}
			return object.NewFloat(sum / float64(len(values)))
		},
		HelpText: `fmean(data) - Return the arithmetic mean of data (faster float version)

Parameters:
  data - List of numbers

Returns: Float

Example:
  import statistics
  statistics.fmean([1.0, 2.0, 3.0])  # 2.0`,
	},
	"geometric_mean": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			values, err := extractNumbers(args[0])
			if err != nil {
				return err
			}
			if len(values) == 0 {
				return errors.NewError("geometric_mean requires at least one data point")
			}

			logSum := 0.0
			for _, v := range values {
				if v <= 0 {
					return errors.NewError("geometric_mean requires positive numbers")
				}
				logSum += math.Log(v)
			}
			return object.NewFloat(math.Exp(logSum / float64(len(values))))
		},
		HelpText: `geometric_mean(data) - Return the geometric mean of data

Parameters:
  data - List of positive numbers

Returns: Float

Example:
  import statistics
  statistics.geometric_mean([1, 2, 4])  # 2.0`,
	},
	"harmonic_mean": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			values, err := extractNumbers(args[0])
			if err != nil {
				return err
			}
			if len(values) == 0 {
				return errors.NewError("harmonic_mean requires at least one data point")
			}
			reciprocalSum := 0.0
			for _, v := range values {
				if v <= 0 {
					return errors.NewError("harmonic_mean requires positive numbers")
				}
				reciprocalSum += 1.0 / v
			}
			return object.NewFloat(float64(len(values)) / reciprocalSum)
		},
		HelpText: `harmonic_mean(data) - Return the harmonic mean of data

Parameters:
  data - List of positive numbers

Returns: Float

Example:
  import statistics
  statistics.harmonic_mean([1, 2, 4])  # ~1.714`,
	},
	"median": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			values, err := extractNumbers(args[0])
			if err != nil {
				return err
			}
			if len(values) == 0 {
				return errors.NewError("median requires at least one data point")
			}
			sorted := make([]float64, len(values))
			copy(sorted, values)
			sort.Float64s(sorted)
			n := len(sorted)
			if n%2 == 0 {
				return object.NewFloat((sorted[n/2-1] + sorted[n/2]) / 2)
			}

			if list, ok := args[0].(*object.List); ok {
				ordered := make([]object.Object, len(list.Elements))
				copy(ordered, list.Elements)
				sort.SliceStable(ordered, func(i, j int) bool {
					return numericOf(ordered[i]) < numericOf(ordered[j])
				})
				if mid, ok := ordered[n/2].(*object.Integer); ok {
					return mid
				}
			}
			return object.NewFloat(sorted[n/2])
		},
		HelpText: `median(data) - Return the median (middle value) of data

Parameters:
  data - List of numbers

Returns: Float

Example:
  import statistics
  statistics.median([1, 3, 5])  # 3.0
  statistics.median([1, 3, 5, 7])  # 4.0`,
	},
	"mode": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			list, ok := args[0].(*object.List)
			if !ok {
				return errors.NewTypeError("LIST", args[0].Type().String())
			}
			if len(list.Elements) == 0 {
				return errors.NewError("mode requires at least one data point")
			}

			counts := make(map[string]int)
			elements := make(map[string]object.Object)
			for _, elem := range list.Elements {
				key := elem.Inspect()
				counts[key]++
				elements[key] = elem
			}

			maxCount := 0
			var modeKey string
			for key, count := range counts {
				if count > maxCount {
					maxCount = count
					modeKey = key
				}
			}
			return elements[modeKey]
		},
		HelpText: `mode(data) - Return the most common value in data

Parameters:
  data - List of values

Returns: Most frequent value (same type as input elements)

Example:
  import statistics
  statistics.mode([1, 1, 2, 3])  # 1
  statistics.mode(["a", "b", "a"])  # "a"`,
	},
	"stdev": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			values, err := extractNumbers(args[0])
			if err != nil {
				return err
			}
			if len(values) < 2 {
				return errors.NewError("stdev requires at least two data points")
			}
			variance := sampleVariance(values)
			return object.NewFloat(math.Sqrt(variance))
		},
		HelpText: `stdev(data) - Return the sample standard deviation of data

Parameters:
  data - List of numbers (at least 2)

Returns: Float

Example:
  import statistics
  statistics.stdev([1, 2, 3, 4, 5])  # ~1.58`,
	},
	"pstdev": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			values, err := extractNumbers(args[0])
			if err != nil {
				return err
			}
			if len(values) < 1 {
				return errors.NewError("pstdev requires at least one data point")
			}
			variance := populationVariance(values)
			return object.NewFloat(math.Sqrt(variance))
		},
		HelpText: `pstdev(data) - Return the population standard deviation of data

Parameters:
  data - List of numbers

Returns: Float

Example:
  import statistics
  statistics.pstdev([1, 2, 3, 4, 5])  # ~1.41`,
	},
	"variance": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			values, err := extractNumbers(args[0])
			if err != nil {
				return err
			}
			if len(values) < 2 {
				return errors.NewError("variance requires at least two data points")
			}
			return object.NewFloat(sampleVariance(values))
		},
		HelpText: `variance(data) - Return the sample variance of data

Parameters:
  data - List of numbers (at least 2)

Returns: Float

Example:
  import statistics
  statistics.variance([1, 2, 3, 4, 5])  # 2.5`,
	},
	"pvariance": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			values, err := extractNumbers(args[0])
			if err != nil {
				return err
			}
			if len(values) < 1 {
				return errors.NewError("pvariance requires at least one data point")
			}
			return object.NewFloat(populationVariance(values))
		},
		HelpText: `pvariance(data) - Return the population variance of data

Parameters:
  data - List of numbers

Returns: Float

Example:
  import statistics
  statistics.pvariance([1, 2, 3, 4, 5])  # 2.0`,
	},
}, nil, "Statistical functions library")
View Source
var StringLibrary = object.NewLibrary(StringLibraryName,
	nil,
	map[string]object.Object{
		"ascii_letters":   object.NewString(asciiLetters),
		"ascii_lowercase": object.NewString(asciiLowercase),
		"ascii_uppercase": object.NewString(asciiUppercase),
		"digits":          object.NewString(digits),
		"hexdigits":       object.NewString(hexdigits),
		"octdigits":       object.NewString(octdigits),
		"punctuation":     object.NewString(punctuation),
		"whitespace":      object.NewString(whitespace),
		"printable":       object.NewString(printable),
	},
	"String constants for character classification",
)
View Source
var StructTimeClass = &object.Class{
	Name: "struct_time",
	Methods: map[string]object.Object{
		"__getitem__": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.MinArgs(args, 2); err != nil {
					return err
				}
				inst := args[0].(*object.Instance)
				order, _ := inst.Field("_order").(*object.List)
				idx, err := args[1].AsInt()
				if err != nil {
					name, nerr := args[1].AsString()
					if nerr != nil {
						return err
					}
					if v, ok := inst.GetField(name); ok {
						return v
					}
					return errors.NewError("struct_time has no field %s", name)
				}
				if order == nil || idx < 0 || int(idx) >= len(order.Elements) {
					return errors.NewError("struct_time index out of range")
				}
				return order.Elements[idx]
			},
			HelpText: "__getitem__(i) - Index or field access",
		},
		"__len__": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				return object.NewInteger(9)
			},
			HelpText: "__len__() - Always 9 fields",
		},
	},
}

StructTimeClass is time.struct_time: indexable like a 9-tuple and with named tm_* attributes, as in Python.

View Source
var TextwrapLibrary = object.NewLibrary(TextwrapLibraryName, map[string]*object.Builtin{
	"wrap": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			text, w, err := textwrapArgs("wrap", args, kwargs)
			if err != nil {
				return err
			}
			lines, err := w.wrap(text)
			if err != nil {
				return err
			}
			elements := make([]object.Object, len(lines))
			for i, line := range lines {
				elements[i] = object.NewString(line)
			}
			return &object.List{Elements: elements}
		},
		HelpText: `wrap(text, width=70, **kwargs) - Wrap a single paragraph of text

Parameters (as Python's textwrap.TextWrapper):
  text                 - Text to wrap
  width                - Maximum line width (default 70)
  initial_indent       - String prepended to the first line (default "")
  subsequent_indent    - String prepended to all other lines (default "")
  expand_tabs          - Expand tabs to spaces first (default True)
  tabsize              - Tab size used by expand_tabs (default 8)
  replace_whitespace   - Replace each whitespace char with a space (default True)
  fix_sentence_endings - Two spaces after sentence endings (default False)
  break_long_words     - Break words longer than width (default True)
  drop_whitespace      - Drop whitespace at line starts/ends (default True)
  break_on_hyphens     - Allow breaks after hyphens in words (default True)
  max_lines            - Truncate output to at most this many lines (default None)
  placeholder          - Appended to truncated output (default " [...]")

Returns: List of lines

Example:
  import textwrap
  lines = textwrap.wrap("Hello world, this is a long line", 10)`,
	},

	"fill": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			text, w, err := textwrapArgs("fill", args, kwargs)
			if err != nil {
				return err
			}
			lines, err := w.wrap(text)
			if err != nil {
				return err
			}
			return object.NewString(strings.Join(lines, "\n"))
		},
		HelpText: `fill(text, width=70, **kwargs) - Wrap text and return a single string

Accepts the same keyword options as wrap().

Returns: Wrapped text as single string with newlines

Example:
  import textwrap
  result = textwrap.fill("Hello world, this is a long line", 10)`,
	},

	"dedent": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) != 1 {
				return errors.NewError("dedent() requires exactly 1 argument")
			}

			text, ok := args[0].(*object.String)
			if !ok {
				return errors.NewTypeError("STRING", args[0].Type().String())
			}

			return object.NewString(dedentText(text.StringValue()))
		},
		HelpText: `dedent(text) - Remove common leading whitespace from all lines

Parameters:
  text - Text to dedent

Returns: Dedented text

Example:
  import textwrap
  text = """
      Hello
      World
  """
  result = textwrap.dedent(text)  # Lines start at column 0`,
	},

	"indent": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) < 2 {
				return errors.NewError("indent() requires at least 2 arguments")
			}

			text, ok := args[0].(*object.String)
			if !ok {
				return errors.NewTypeError("STRING", args[0].Type().String())
			}

			prefix, ok := args[1].(*object.String)
			if !ok {
				return errors.NewTypeError("STRING", args[1].Type().String())
			}

			for _, k := range kwargs.Keys() {
				if k != "predicate" {
					return newArgTypeError("indent() got an unexpected keyword argument '%s'", k)
				}
			}
			var predicate object.Object
			if len(args) >= 3 {
				predicate = args[2]
			} else if p := kwargs.Get("predicate"); p != nil {
				predicate = p
			}
			if _, isNull := predicate.(*object.Null); isNull {
				predicate = nil
			}

			// Split keeping line endings, like str.splitlines(True).
			var b strings.Builder
			for _, line := range strings.SplitAfter(text.StringValue(), "\n") {
				if line == "" {
					continue
				}
				add := false
				if predicate != nil {
					res := callCallable(ctx, predicate, object.NewString(line))
					if object.IsError(res) || res.Type() == object.EXCEPTION_OBJ {
						return res
					}
					add = isTruthy(res)
				} else {

					add = strings.TrimSpace(line) != ""
				}
				if add {
					b.WriteString(prefix.StringValue())
				}
				b.WriteString(line)
			}
			return object.NewString(b.String())
		},
		HelpText: `indent(text, prefix, predicate=None) - Add prefix to lines

Parameters:
  text      - Text to indent
  prefix    - String to add to beginning of each line
  predicate - Optional callable; only lines for which it returns true are
              indented (default: lines that are not whitespace-only)

Returns: Indented text

Example:
  import textwrap
  result = textwrap.indent("Hello\nWorld", "  ")  # "  Hello\n  World"`,
	},

	"shorten": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if kwargs.Has("max_lines") {
				return newArgTypeError("shorten() got multiple values for keyword argument 'max_lines'")
			}
			if len(args) < 2 && !kwargs.Has("width") {
				return errors.NewError("shorten() missing required argument: 'width'")
			}
			text, w, err := textwrapArgs("shorten", args, kwargs)
			if err != nil {
				return err
			}
			w.maxLines = 1
			lines, err := w.wrap(strings.Join(strings.Fields(text), " "))
			if err != nil {
				return err
			}
			return object.NewString(strings.Join(lines, "\n"))
		},
		HelpText: `shorten(text, width, **kwargs) - Collapse and truncate text to fit width

Parameters:
  text        - Text to shorten
  width       - Maximum width including placeholder
  placeholder - String to indicate truncation (default " [...]")
  (other wrap() keyword options are also accepted)

Returns: Shortened text

Example:
  import textwrap
  result = textwrap.shorten("Hello World!", 11)  # "Hello [...]"`,
	},
}, nil, "Text wrapping and filling utilities")
View Source
var TimeLibrary = object.NewLibrary(TimeLibraryName, map[string]*object.Builtin{
	"monotonic": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			return object.NewFloat(time.Since(processStart).Seconds())
		},
		HelpText: `monotonic() - Monotonic clock in seconds`,
	},
	"now": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			return object.NewString(time.Now().Format("2006-01-02T15:04:05.999999"))
		},
		HelpText: `now() - Return current date and time

Returns the current date and time as an ISO 8601 formatted string (YYYY-MM-DDTHH:MM:SS.ffffff).`,
	},
	"time": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			return object.NewFloat(float64(time.Now().UnixNano()) / 1e9)
		},
		HelpText: `time() - Return current time in seconds

Returns the current time as a floating point number of seconds since the Unix epoch.`,
	},
	"perf_counter": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			return object.NewFloat(time.Since(startTime).Seconds())
		},
		HelpText: `perf_counter() - Return performance counter

Returns the value of a performance counter in fractional seconds.`,
	},
	"sleep": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			seconds, err := args[0].AsFloat()
			if err != nil {
				return errors.ParameterError("seconds", err)
			}

			timer := time.NewTimer(time.Duration(seconds * float64(time.Second)))
			defer timer.Stop()

			// Release the interpreter lock while blocked so other goroutines
			// (e.g. runtime.background shared threads) can run script code.
			var result object.Object = &object.Null{}
			object.RunBlocking(ctx, func() {
				select {
				case <-ctx.Done():
					if ctx.Err() == context.DeadlineExceeded {
						result = errors.NewTimeoutError()
					} else {
						result = errors.NewCancelledError()
					}
				case <-timer.C:
				}
			})
			return result
		},
		HelpText: `sleep(seconds) - Sleep for specified seconds

Suspends execution for the given number of seconds.`,
	},
	"localtime": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			var t time.Time
			if len(args) == 0 {
				t = time.Now()
			} else if len(args) == 1 {

				if ts, err := args[0].AsFloat(); err == nil {
					t = time.Unix(int64(ts), 0)
				} else if instance, ok := args[0].(*object.Instance); ok {

					if dt, err := GetTimeFromObject(instance); err == nil {
						t = dt
					} else {
						return errors.NewTypeError("INTEGER, FLOAT, or datetime instance", args[0].Type().String())
					}
				} else {
					return errors.NewTypeError("INTEGER, FLOAT, or datetime instance", args[0].Type().String())
				}
			} else {
				if err := errors.MaxArgs(args, 1); err != nil {
					return err
				}
			}

			return timeToTuple(t, false)
		},
		HelpText: `localtime([timestamp_or_datetime]) - Convert to local time tuple

Returns a time tuple in local time. If timestamp/datetime is omitted, uses current time.`,
	},
	"gmtime": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			var t time.Time
			if len(args) == 0 {
				t = time.Now()
			} else if len(args) == 1 {

				if ts, err := args[0].AsFloat(); err == nil {
					t = time.Unix(int64(ts), 0)
				} else if instance, ok := args[0].(*object.Instance); ok {

					if dt, err := GetTimeFromObject(instance); err == nil {
						t = dt
					} else {
						return errors.NewTypeError("INTEGER, FLOAT, or datetime instance", args[0].Type().String())
					}
				} else {
					return errors.NewTypeError("INTEGER, FLOAT, or datetime instance", args[0].Type().String())
				}
			} else {
				if err := errors.MaxArgs(args, 1); err != nil {
					return err
				}
			}

			return timeToTuple(t, true)
		},
		HelpText: `gmtime([timestamp_or_datetime]) - Convert to UTC time tuple

Returns a time tuple in UTC. If timestamp/datetime is omitted, uses current time.`,
	},
	"mktime": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}

			vals, verr := timeTupleValues(args[0])
			if verr != nil {
				return verr
			}

			t := time.Date(int(vals[0]), time.Month(vals[1]), int(vals[2]), int(vals[3]), int(vals[4]), int(vals[5]), 0, time.Local)
			return object.NewFloat(float64(t.Unix()))
		},
		HelpText: `mktime(tuple) - Convert time tuple to timestamp

Converts a time tuple (9 elements) to a Unix timestamp.`,
	},
	"strftime": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}

			format, err := args[0].AsString()
			if err != nil {
				return err
			}

			var t time.Time
			if len(args) == 1 {
				t = time.Now()
			} else {
				vals, verr := timeTupleValues(args[1])
				if verr != nil {
					return verr
				}
				year, month, day := vals[0], vals[1], vals[2]
				hour, minute, second := vals[3], vals[4], vals[5]

				t = time.Date(int(year), time.Month(month), int(day), int(hour), int(minute), int(second), 0, time.Local)
			}

			result := t.Format(pythonToGoFormat(format))

			if strings.Contains(format, "%j") {
				result = strings.ReplaceAll(result, "%j", fmt.Sprintf("%03d", t.YearDay()))
			}
			return object.NewString(result)
		},
		HelpText: `strftime(format[, tuple]) - Format time as string

Formats a time according to the given format string. If tuple is omitted, uses current time.`,
	},
	"strptime": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}

			str, err := args[0].AsString()
			if err != nil {
				return err
			}

			format, err := args[1].AsString()
			if err != nil {
				return err
			}

			t, parseErr := time.Parse(pythonToGoFormat(format), str)
			if parseErr != nil {
				return errors.NewError("strptime() parse error: %s", parseErr.Error())
			}

			return timeToTuple(t, false)
		},
		HelpText: `strptime(string, format) - Parse time from string

Parses a time string according to the given format and returns a time tuple.`,
	},
	"asctime": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			var t time.Time
			if len(args) == 0 {
				t = time.Now()
			} else if len(args) == 1 {
				vals, verr := timeTupleValues(args[0])
				if verr != nil {
					return verr
				}

				t = time.Date(int(vals[0]), time.Month(vals[1]), int(vals[2]), int(vals[3]), int(vals[4]), int(vals[5]), 0, time.Local)
			} else {
				if err := errors.MaxArgs(args, 1); err != nil {
					return err
				}
			}

			return object.NewString(t.Format("Mon Jan 2 15:04:05 2006"))
		},
		HelpText: `asctime([tuple]) - Convert time tuple to string

Converts a time tuple to a string in the format 'Mon Jan 2 15:04:05 2006'. If tuple is omitted, uses current time.`,
	},
	"ctime": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			var t time.Time
			if len(args) == 0 {
				t = time.Now()
			} else if len(args) == 1 {
				timestamp, err := args[0].AsFloat()
				if err != nil {
					return errors.ParameterError("timestamp", err)
				}
				t = time.Unix(int64(timestamp), 0)
			} else {
				if err := errors.MaxArgs(args, 1); err != nil {
					return err
				}
			}

			return object.NewString(t.Format("Mon Jan 2 15:04:05 2006"))
		},
		HelpText: `ctime([timestamp]) - Convert timestamp to string

Converts a Unix timestamp to a string in the format 'Mon Jan 2 15:04:05 2006'. If timestamp is omitted, uses current time.`,
	},
}, nil, "Time-related functions library")
View Source
var TimedeltaClass = &object.Class{
	Name: "timedelta",
	Methods: map[string]object.Object{
		"total_seconds": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.MinArgs(args, 1); err != nil {
					return err
				}
				inst, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewTypeError("timedelta instance", args[0].Type().String())
				}
				total, ok := inst.Field("_total").(*object.Float)
				if !ok {
					return errors.NewTypeError("timedelta instance", args[0].Type().String())
				}
				return object.NewFloat(total.FloatValue())
			},
			HelpText: "total_seconds() - Return the total duration in seconds",
		},
		"__str__": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.MinArgs(args, 1); err != nil {
					return err
				}
				inst, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewTypeError("timedelta instance", args[0].Type().String())
				}
				if repr, ok := inst.Field("__str_repr__").(*object.String); ok {
					return repr
				}
				return object.NewString("")
			},
			HelpText: "__str__() - Return the Python-style duration string",
		},
		"__lt__": timedeltaCompare("<"),
		"__gt__": timedeltaCompare(">"),
		"__le__": timedeltaCompare("<="),
		"__ge__": timedeltaCompare(">="),
		"__eq__": timedeltaCompare("=="),
		"__ne__": timedeltaCompare("!="),
	},
}

TimedeltaClass is the class behind datetime.timedelta values.

View Source
var TypingLibrary = object.NewLibrary(TypingLibraryName, nil, map[string]object.Object{
	"Any":      typingAlias("Any"),
	"Callable": typingAlias("Callable"),
	"Dict":     typingAlias("Dict"),
	"Iterable": typingAlias("Iterable"),
	"Iterator": typingAlias("Iterator"),
	"List":     typingAlias("List"),
	"Literal":  typingAlias("Literal"),
	"Mapping":  typingAlias("Mapping"),
	"Optional": typingAlias("Optional"),
	"Sequence": typingAlias("Sequence"),
	"Set":      typingAlias("Set"),
	"Tuple":    typingAlias("Tuple"),
	"Type":     typingAlias("Type"),
	"Union":    typingAlias("Union"),
}, "Typing placeholders: annotations are accepted and discarded, as always")
View Source
var URLLibLibrary = object.NewLibrary(URLLibLibraryName, nil, nil, "URL handling modules")

URLLibrary is the parent urllib module - registered so `import urllib` works

View Source
var URLParseLibrary = object.NewLibrary(URLParseLibraryName, map[string]*object.Builtin{
	"quote": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}

			str, err := args[0].AsString()
			if err != nil {
				return err
			}

			safe := "/"
			if len(args) == 2 {
				safe, err = args[1].AsString()
				if err != nil {
					return err
				}
			}

			encoded := urlQuote(str, safe)
			return object.NewString(encoded)
		},
		HelpText: `quote(string, safe='') - URL encode string

Returns a URL-encoded version of the string. Characters in 'safe' are not encoded.`,
	},
	"quote_plus": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}

			str, err := args[0].AsString()
			if err != nil {
				return err
			}

			safe := ""
			if len(args) == 2 {
				safe, err = args[1].AsString()
				if err != nil {
					return err
				}
			}

			encoded := urlQuote(str, safe)
			encoded = strings.ReplaceAll(encoded, "%20", "+")
			return object.NewString(encoded)
		},
		HelpText: `quote_plus(string, safe='') - URL encode string with + for spaces

Like quote(), but also replaces spaces with plus signs.`,
	},
	"unquote": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}

			str, err := args[0].AsString()
			if err != nil {
				return err
			}

			decoded, urlErr := url.PathUnescape(str)
			if urlErr != nil {
				return errors.NewError("unquote() invalid URL encoding")
			}
			return object.NewString(decoded)
		},
		HelpText: `unquote(string) - URL decode string

Returns a URL-decoded version of the string.`,
	},
	"unquote_plus": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}

			str, err := args[0].AsString()
			if err != nil {
				return err
			}

			str = strings.ReplaceAll(str, "+", " ")
			decoded, urlErr := url.PathUnescape(str)
			if urlErr != nil {
				return errors.NewError("unquote_plus() invalid URL encoding")
			}
			return object.NewString(decoded)
		},
		HelpText: `unquote_plus(string) - URL decode string with + as spaces

Like unquote(), but also replaces plus signs with spaces.`,
	},
	"urlparse": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}

			str, err := args[0].AsString()
			if err != nil {
				return err
			}

			u, urlErr := url.Parse(str)
			if urlErr != nil {
				return errors.NewError("urlparse() invalid URL")
			}

			netloc := u.Host
			if u.User != nil {
				if pwd, hasPwd := u.User.Password(); hasPwd {
					netloc = u.User.Username() + ":" + pwd + "@" + u.Host
				} else {
					netloc = u.User.Username() + "@" + u.Host
				}
			}

			return createParseResultInstance(u.Scheme, netloc, u.Path, "", u.RawQuery, u.Fragment)
		},
		HelpText: `urlparse(urlstring) - Parse URL into components

Returns a ParseResult object with URL components: scheme, netloc, path, params, query, fragment.
Access components as attributes: result.scheme, result.netloc, etc. Use result.geturl() to reconstruct URL.`,
	},
	"urlunparse": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}

			switch arg := args[0].(type) {
			case *object.Instance:

				if arg.Class == ParseResultClass {
					u := &url.URL{}
					if value, ok := arg.GetField("scheme"); ok {
						if str, err := value.AsString(); err == nil {
							u.Scheme = str
						}
					}
					if value, ok := arg.GetField("netloc"); ok {
						if str, err := value.AsString(); err == nil {
							u.Host = str
						}
					}
					if value, ok := arg.GetField("path"); ok {
						if str, err := value.AsString(); err == nil {
							u.Path = str
						}
					}
					if value, ok := arg.GetField("query"); ok {
						if str, err := value.AsString(); err == nil {
							u.RawQuery = str
						}
					}
					if value, ok := arg.GetField("fragment"); ok {
						if str, err := value.AsString(); err == nil {
							u.Fragment = str
						}
					}
					return object.NewString(u.String())
				}
				return errors.NewTypeError("DICT, LIST, TUPLE, or ParseResult", args[0].Type().String())

			case *object.Dict:
				u := &url.URL{}
				if value, ok := arg.GetByString("scheme"); ok {
					if str, err := value.Value.AsString(); err == nil {
						u.Scheme = str
					}
				}
				if value, ok := arg.GetByString("netloc"); ok {
					if str, err := value.Value.AsString(); err == nil {
						u.Host = str
					}
				}
				if value, ok := arg.GetByString("path"); ok {
					if str, err := value.Value.AsString(); err == nil {
						u.Path = str
					}
				}
				if value, ok := arg.GetByString("query"); ok {
					if str, err := value.Value.AsString(); err == nil {
						u.RawQuery = str
					}
				}
				if value, ok := arg.GetByString("fragment"); ok {
					if str, err := value.Value.AsString(); err == nil {
						u.Fragment = str
					}
				}
				return object.NewString(u.String())

			case *object.List:
				if len(arg.Elements) != 6 {
					return errors.NewError("urlunparse() requires exactly 6 elements")
				}
				components := make([]string, 6)
				for i, elem := range arg.Elements {
					str, err := elem.AsString()
					if err != nil {
						return err
					}
					components[i] = str
				}
				u := &url.URL{
					Scheme:   components[0],
					Host:     components[1],
					Path:     components[2],
					RawQuery: components[4],
					Fragment: components[5],
				}
				return object.NewString(u.String())

			case *object.Tuple:
				if len(arg.Elements) != 6 {
					return errors.NewError("urlunparse() requires exactly 6 elements")
				}
				components := make([]string, 6)
				for i, elem := range arg.Elements {
					str, err := elem.AsString()
					if err != nil {
						return err
					}
					components[i] = str
				}
				u := &url.URL{
					Scheme:   components[0],
					Host:     components[1],
					Path:     components[2],
					RawQuery: components[4],
					Fragment: components[5],
				}
				return object.NewString(u.String())

			default:
				return errors.NewTypeError("DICT, LIST, TUPLE, or ParseResult", args[0].Type().String())
			}
		},
		HelpText: `urlunparse(components) - Construct URL from components

Constructs a URL string from a 6-tuple or dict of URL components.`,
	},
	"urljoin": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}

			base, err := args[0].AsString()
			if err != nil {
				return err
			}

			ref, err := args[1].AsString()
			if err != nil {
				return err
			}

			baseURL, urlErr := url.Parse(base)
			if urlErr != nil {
				return errors.NewError("urljoin() invalid base URL")
			}

			refURL, urlErr := url.Parse(ref)
			if urlErr != nil {
				return errors.NewError("urljoin() invalid reference URL")
			}

			joined := baseURL.ResolveReference(refURL)
			return object.NewString(joined.String())
		},
		HelpText: `urljoin(base, url) - Join base URL with reference

Joins a base URL with a reference URL, resolving relative references.`,
	},
	"urlsplit": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}

			str, err := args[0].AsString()
			if err != nil {
				return err
			}

			u, urlErr := url.Parse(str)
			if urlErr != nil {
				return errors.NewError("urlsplit() invalid URL")
			}

			netloc := u.Host
			if u.User != nil {
				if pwd, hasPwd := u.User.Password(); hasPwd {
					netloc = u.User.Username() + ":" + pwd + "@" + u.Host
				} else {
					netloc = u.User.Username() + "@" + u.Host
				}
			}

			elements := []object.Object{
				object.NewString(u.Scheme),
				object.NewString(netloc),
				object.NewString(u.Path),
				object.NewString(u.RawQuery),
				object.NewString(u.Fragment),
			}

			return &object.List{Elements: elements}
		},
		HelpText: `urlsplit(urlstring) - Split URL into components

Returns a 5-tuple: (scheme, netloc, path, query, fragment).`,
	},
	"urlunsplit": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}

			elements, err := args[0].AsList()
			if err != nil {
				return errors.ParameterError("elements", err)
			}

			if len(elements) != 5 {
				return errors.NewError("urlunsplit() requires exactly 5 elements")
			}

			components := make([]string, 5)
			for i, elem := range elements {
				str, err := elem.AsString()
				if err != nil {
					return err
				}
				components[i] = str
			}

			u := &url.URL{
				Scheme:   components[0],
				Host:     components[1],
				Path:     components[2],
				RawQuery: components[3],
				Fragment: components[4],
			}

			return object.NewString(u.String())
		},
		HelpText: `urlunsplit(components) - Construct URL from component tuple

Constructs a URL string from a 5-tuple of URL components.`,
	},
	"parse_qs": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}

			str, err := args[0].AsString()
			if err != nil {
				return err
			}

			values, urlErr := url.ParseQuery(str)
			if urlErr != nil {
				return errors.NewError("parse_qs() invalid query string")
			}

			pairs := make(map[string]object.DictPair)
			for key, vals := range values {
				elements := make([]object.Object, len(vals))
				for i, val := range vals {
					elements[i] = object.NewString(val)
				}
				keyObj := object.NewString(key)
				pairs[object.DictKey(keyObj)] = object.DictPair{
					Key:   keyObj,
					Value: &object.List{Elements: elements},
				}
			}

			return &object.Dict{Pairs: pairs}
		},
		HelpText: `parse_qs(qs, keep_blank_values=False) - Parse query string

Parses a URL query string and returns a dictionary where values are lists.`,
	},
	"parse_qsl": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}

			str, err := args[0].AsString()
			if err != nil {
				return err
			}

			values, urlErr := url.ParseQuery(str)
			if urlErr != nil {
				return errors.NewError("parse_qsl() invalid query string")
			}

			// Convert to list of (key, value) tuples
			var result []object.Object
			for key, vals := range values {
				for _, val := range vals {
					tuple := &object.Tuple{
						Elements: []object.Object{
							object.NewString(key),
							object.NewString(val),
						},
					}
					result = append(result, tuple)
				}
			}

			return &object.List{Elements: result}
		},
		HelpText: `parse_qsl(qs, keep_blank_values=False) - Parse query string as list

Parses a URL query string and returns a list of (key, value) tuples.`,
	},
	"urlencode": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}

			values := make(url.Values)

			switch arg := args[0].(type) {
			case *object.Dict:
				for _, pair := range arg.Pairs {
					keyStr, keyErr := pair.Key.AsString()
					if keyErr != nil {
						continue
					}
					switch val := pair.Value.(type) {
					case *object.String:
						values[keyStr] = []string{val.StringValue()}
					case *object.List:
						strVals := make([]string, len(val.Elements))
						for i, elem := range val.Elements {
							if str, err := elem.AsString(); err == nil {
								strVals[i] = str
							}
						}
						values[keyStr] = strVals
					}
				}
			case *object.List:

				for _, elem := range arg.Elements {
					if tuple, ok := elem.(*object.Tuple); ok && len(tuple.Elements) == 2 {
						if key, err := tuple.Elements[0].AsString(); err == nil {
							if val, err := tuple.Elements[1].AsString(); err == nil {
								values.Add(key, val)
							}
						}
					}
				}
			default:
				return errors.NewTypeError("DICT or LIST", args[0].Type().String())
			}

			return object.NewString(values.Encode())
		},
		HelpText: `urlencode(query, doseq=False) - Encode dictionary as query string

Encodes a dictionary or list of tuples into a URL query string.`,
	},
}, nil, "URL parsing and manipulation (urllib.parse compatible)")

URLParseLibrary implements Python's urllib.parse module

View Source
var UUIDClass = &object.Class{
	Name: "UUID",
	Methods: map[string]object.Object{
		"__str__": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				inst := args[0].(*object.Instance)
				if repr, ok := inst.Field("__str_repr__").(*object.String); ok {
					return repr
				}
				return object.NewString("")
			},
			HelpText: "__str__() - Canonical hyphenated form",
		},
		"__eq__": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				a, okA := uuidReprOf(args[0])
				b, okB := uuidReprOf(args[1])
				return object.NewBoolean(okA && okB && a == b)
			},
			HelpText: "__eq__(other) - Compare by value",
		},

		"__lt__": uuidOrderMethod("<", func(c int) bool { return c < 0 }),
		"__le__": uuidOrderMethod("<=", func(c int) bool { return c <= 0 }),
		"__gt__": uuidOrderMethod(">", func(c int) bool { return c > 0 }),
		"__ge__": uuidOrderMethod(">=", func(c int) bool { return c >= 0 }),

		"__hash__": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				inst := args[0].(*object.Instance)
				repr, _ := inst.Field("__str_repr__").(*object.String)
				h := uint64(14695981039346656037)
				for _, c := range repr.StringValue() {
					h ^= uint64(c)
					h *= 1099511628211
				}
				return object.NewInteger(int64(h))
			},
			HelpText: "__hash__() - Hash by UUID value",
		},
		"__repr__": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				inst := args[0].(*object.Instance)
				if repr, ok := inst.Field("__str_repr__").(*object.String); ok {
					return object.NewString("UUID('" + repr.StringValue() + "')")
				}
				return object.NewString("UUID('')")
			},
			HelpText: "__repr__() - Constructor-style form",
		},
	},
}

UUIDClass backs uuid values: attribute access and comparisons.

View Source
var UUIDLibrary = object.NewLibrary(UUIDLibraryName, map[string]*object.Builtin{
	"uuid1": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 0); err != nil {
				return err
			}
			id, err := uuid.NewUUID()
			if err != nil {
				return errors.NewError("failed to generate UUID v1: %s", err.Error())
			}
			return uuidInstance(id)
		},
		HelpText: `uuid1() - Generate a UUID version 1 (time-based)

Returns a UUID based on current time and MAC address.
Format: xxxxxxxx-xxxx-1xxx-yxxx-xxxxxxxxxxxx

Example:
  import uuid
  id = uuid.uuid1()
  print(id)  # e.g., "f47ac10b-58cc-1e4c-a26f-e3fc32165abc"`,
	},
	"uuid4": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 0); err != nil {
				return err
			}
			return uuidInstance(uuid.New())
		},
		HelpText: `uuid4() - Generate a UUID version 4 (random)

Returns a randomly generated UUID.
Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx

Example:
  import uuid
  id = uuid.uuid4()
  print(id)  # e.g., "550e8400-e29b-41d4-a716-446655440000"`,
	},
	"uuid7": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 0); err != nil {
				return err
			}
			id, err := uuid.NewV7()
			if err != nil {
				return errors.NewError("failed to generate UUID v7: %s", err.Error())
			}
			return uuidInstance(id)
		},
		HelpText: `uuid7() - Generate a UUID version 7 (Unix timestamp-based, sortable)

Returns a UUID based on Unix timestamp in milliseconds.
UUIDs generated in sequence will sort in chronological order.
Format: xxxxxxxx-xxxx-7xxx-yxxx-xxxxxxxxxxxx

Example:
  import uuid
  id = uuid.uuid7()
  print(id)  # e.g., "018f6b1c-4e5d-7abc-8def-0123456789ab"`,
	},
	"UUID": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			s, err := args[0].AsString()
			if err != nil {
				return err
			}
			id, ok := parseUUIDString(s)
			if !ok {
				return errors.NewError("badly formed hexadecimal UUID string")
			}
			return uuidInstance(id)
		},
		HelpText: `UUID(hex_string) - UUID object from a canonical string

Accepts hyphenated, bare-hex or URN forms; str() renders the canonical
hyphenated form.`,
	},
	"uuid3": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			nsStr, err := uuidStringOf(args[0])
			if err != nil {
				return err
			}
			name, err := args[1].AsString()
			if err != nil {
				return err
			}
			ns, ok := parseUUIDString(nsStr)
			if !ok {
				return errors.NewError("uuid3: namespace must be a UUID string")
			}
			return uuidInstance(uuid.NewMD5(ns, []byte(name)))
		},
		HelpText: "uuid3(namespace, name) - MD5 name-based UUID (deterministic)",
	},
	"uuid5": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			nsStr, err := uuidStringOf(args[0])
			if err != nil {
				return err
			}
			name, err := args[1].AsString()
			if err != nil {
				return err
			}
			ns, ok := parseUUIDString(nsStr)
			if !ok {
				return errors.NewError("uuid5: namespace must be a UUID string")
			}
			return uuidInstance(uuid.NewSHA1(ns, []byte(name)))
		},
		HelpText: "uuid5(namespace, name) - SHA-1 name-based UUID (deterministic)",
	},
}, uuidNamespaceConstants, "UUID generation library")

Functions

func CompareDigest added in v0.14.0

func CompareDigest(a, b string) bool

CompareDigest performs a constant-time comparison of two strings. It is the single implementation shared by the hmac and secrets libraries (in CPython, secrets.compare_digest is hmac.compare_digest).

func GetCompiledRegex

func GetCompiledRegex(pattern string) (*regexp.Regexp, error)

GetCompiledRegex retrieves a compiled regex from cache or compiles and caches it

func GetTimeFromObject

func GetTimeFromObject(obj object.Object) (time.Time, object.Object)

GetTimeFromObject extracts time.Time from a datetime/date instance

func LibraryNames added in v0.33.0

func LibraryNames() []string

LibraryNames returns the names of every library RegisterAll registers, in registration order. It is derived from RegisterAll itself, so a library added there is listed here without a second list to maintain.

func NewMsgpackLibrary added in v0.19.0

func NewMsgpackLibrary(c MsgpackCodec) *object.Library

NewMsgpackLibrary builds a msgpack library backed by the given codec. Pass nil to use the default (gossip's ShamatonMsgpackCodec — matching the gossip DefaultConfig). The returned library can be registered with a Scriptling instance via RegisterLibrary.

Embedders wanting a different codec can build their own library directly:

p.RegisterLibrary(stdlib.NewMsgpackLibrary(gossipvmihailenco.New()))

or, to swap the default that stdlib.RegisterAll picks up, reassign before calling RegisterAll:

stdlib.MsgpackLibrary = stdlib.NewMsgpackLibrary(myCodec)

func PythonToGoDateFormat

func PythonToGoDateFormat(pyFormat string) string

PythonToGoDateFormat converts Python datetime format codes to Go format

func RegisterAll

func RegisterAll(p interface{ RegisterLibrary(*object.Library) })

RegisterAll registers all standard libraries

Types

type MsgpackCodec added in v0.19.0

type MsgpackCodec interface {
	Name() string
	Marshal(v interface{}) ([]byte, error)
	Unmarshal(data []byte, v interface{}) error
}

MsgpackCodec is the interface a MessagePack (or binary-compatible) codec must satisfy. It is structurally identical to github.com/paularlott/gossip/codec.Serializer, so any gossip codec (ShamatonMsgpackCodec, VmihailencoMsgpackCodec, HashicorpMsgpackCodec) can be passed directly to NewMsgpackLibrary — letting a single driver instance be shared between Scriptling's msgpack library and a gossip cluster so both sides agree on the wire format.

Example — share one codec between gossip and Scriptling:

import gossipcodec "github.com/paularlott/gossip/codec"

codec := gossipshamaton.New() // or Vmihailenco, etc.

// gossip side
gossipCfg := gossip.DefaultConfig() // already defaults to shamaton
gossipCfg.MsgCodec = codec

// scriptling side
p.RegisterLibrary(stdlib.NewMsgpackLibrary(codec))

type StringIOWriter added in v0.2.0

type StringIOWriter struct {
	// contains filtered or unexported fields
}

StringIOWriter implements io.Writer for a StringIO instance.

func GetStringIOWriter added in v0.2.0

func GetStringIOWriter(inst *object.Instance) (*StringIOWriter, bool)

GetStringIOWriter returns an io.Writer backed by a StringIO instance, for use with print(file=buf) in the evaluator.

func (*StringIOWriter) Write added in v0.2.0

func (w *StringIOWriter) Write(p []byte) (int, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL