stdlib

package
v0.20.1 Latest Latest
Warning

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

Go to latest
Published: Aug 6, 2026 License: MIT Imports: 33 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"
)

Library names as constants for easy reference

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 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
			}
			encoded := base64.StdEncoding.EncodeToString(data)
			return object.NewString(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
			}
			str, err := args[0].AsString()
			if err != nil {
				return err
			}
			decoded, decodeErr := base64.StdEncoding.DecodeString(str)
			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: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			counter := createCounterInstance()

			if len(args) == 0 {
				return counter
			}
			if err := errors.MaxArgs(args, 1); err != nil {
				return err
			}

			switch arg := args[0].(type) {
			case *object.List:
				for _, elem := range arg.Elements {
					key := elem.Inspect()
					if countObj, exists := counter.GetField(key); exists {
						if count, ok := countObj.(*object.Integer); ok {
							counter.SetField(key, object.NewInteger(count.IntValue()+1))
						}
					} else {
						counter.SetField(key, object.NewInteger(1))
					}
				}
			case *object.Tuple:
				for _, elem := range arg.Elements {
					key := elem.Inspect()
					if countObj, exists := counter.GetField(key); exists {
						if count, ok := countObj.(*object.Integer); ok {
							counter.SetField(key, object.NewInteger(count.IntValue()+1))
						}
					} else {
						counter.SetField(key, object.NewInteger(1))
					}
				}
			case *object.String:
				for _, ch := range arg.StringValue() {
					key := string(ch)
					if countObj, exists := counter.GetField(key); exists {
						if count, ok := countObj.(*object.Integer); ok {
							counter.SetField(key, object.NewInteger(count.IntValue()+1))
						}
					} else {
						counter.SetField(key, object.NewInteger(1))
					}
				}
			case *object.Dict:

				for _, v := range arg.Pairs {
					if count, ok := v.Value.(*object.Integer); ok {
						keyStr, _ := v.Key.AsString()
						counter.SetField(keyStr, count)
					}
				}
			default:
				return errors.NewTypeError("iterable or dict", args[0].Type().String())
			}

			return counter
		},
		HelpText: `Counter([iterable]) - Count elements

Creates a Counter object that counts occurrences of elements.

Example:
  c = collections.Counter([1, 1, 2, 3, 3, 3])
  c[1] -> 2  # Count of element 1
  c[4] -> 0  # Missing elements return 0
  c.most_common() -> [(3, 3), (1, 2), (2, 1)]
  c.elements() -> [1, 1, 2, 3, 3, 3]`,
	},
	"most_common": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if err := errors.RangeArgs(args, 1, 2); err != nil {
				return err
			}
			counter, ok := args[0].(*object.Instance)
			if !ok || counter.Class != CounterClass {
				return errors.NewTypeError("Counter", args[0].Type().String())
			}

			n := counter.FieldCount()
			if len(args) == 2 {
				if nArg, ok := args[1].(*object.Integer); ok {
					n = int(nArg.IntValue())
				} else {
					return errors.NewTypeError("INTEGER", args[1].Type().String())
				}
			}

			// Convert to sortable slice
			type pair struct {
				key   string
				count int64
			}
			pairs := make([]pair, 0, counter.FieldCount())
			counter.RangeFields(func(key string, countObj object.Object) bool {
				if count, ok := countObj.(*object.Integer); ok {
					pairs = append(pairs, pair{key: key, count: count.IntValue()})
				}
				return true
			})

			sort.Slice(pairs, func(i, j int) bool {
				return pairs[i].count > pairs[j].count
			})

			if n > len(pairs) {
				n = len(pairs)
			}
			result := make([]object.Object, n)
			for i := 0; i < n; i++ {
				key := pairs[i].key

				if intVal, err := strconv.ParseInt(key, 10, 64); err == nil {
					result[i] = &object.Tuple{Elements: []object.Object{
						object.NewInteger(intVal),
						object.NewInteger(pairs[i].count),
					}}
				} else {
					result[i] = &object.Tuple{Elements: []object.Object{
						object.NewString(key),
						object.NewInteger(pairs[i].count),
					}}
				}
			}
			return &object.List{Elements: result}
		},
		HelpText: `most_common(counter[, n]) - Return n most common elements

Returns a list of (element, count) tuples sorted by count.

Example:
  c = collections.Counter([1, 1, 2, 3, 3, 3])
  collections.most_common(c, 2) -> [(3, 3), (1, 2)]`,
	},

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

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

			if len(args) == 0 {
				return od
			}
			if err := errors.MaxArgs(args, 1); err != nil {
				return err
			}

			switch arg := args[0].(type) {
			case *object.List:
				for _, elem := range arg.Elements {
					tuple, ok := elem.(*object.Tuple)
					if !ok || len(tuple.Elements) != 2 {
						return errors.NewError("OrderedDict() items must be (key, value) tuples")
					}
					key := object.DictKey(tuple.Elements[0])
					od.Pairs[key] = object.DictPair{
						Key:   tuple.Elements[0],
						Value: tuple.Elements[1],
					}
				}
			case *object.Dict:
				for k, v := range arg.Pairs {
					od.Pairs[k] = v
				}
			default:
				return errors.NewTypeError("list of tuples or dict", args[0].Type().String())
			}
			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)])`,
	},
	"deque": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			elements := []object.Object{}

			if len(args) >= 1 {
				switch arg := args[0].(type) {
				case *object.List:
					elements = make([]object.Object, len(arg.Elements))
					copy(elements, arg.Elements)
				case *object.Tuple:
					elements = make([]object.Object, len(arg.Elements))
					copy(elements, arg.Elements)
				case *object.String:
					for _, ch := range arg.StringValue() {
						elements = append(elements, object.NewString(string(ch)))
					}
				default:
					return errors.NewTypeError("iterable", args[0].Type().String())
				}
			}

			maxlen := int64(-1)
			if len(args) >= 2 {
				if ml, ok := args[1].(*object.Integer); ok {
					maxlen = ml.IntValue()
					if maxlen >= 0 && int64(len(elements)) > maxlen {

						elements = elements[len(elements)-int(maxlen):]
					}
				} else if args[1].Type() != object.NULL_OBJ {
					return errors.NewTypeError("INTEGER or None", args[1].Type().String())
				}
			}

			dequeList := &object.List{Elements: elements}

			return dequeList
		},
		HelpText: `deque([iterable[, maxlen]]) - Double-ended queue

Creates a double-ended queue (deque) from an iterable.
Use collections.deque_* functions for deque-specific operations.

Example:
  d = collections.deque([1, 2, 3])
  collections.deque_appendleft(d, 0)  # [0, 1, 2, 3]`,
	},
	"deque_appendleft": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			deque, ok := args[0].(*object.List)
			if !ok {
				return errors.NewTypeError("LIST (deque)", args[0].Type().String())
			}
			newElements := make([]object.Object, len(deque.Elements)+1)
			newElements[0] = args[1]
			copy(newElements[1:], deque.Elements)
			deque.Elements = newElements
			return &object.Null{}
		},
		HelpText: `deque_appendleft(deque, elem) - Add element to left side

Adds an element to the left side of the deque.

Example:
  d = collections.deque([1, 2, 3])
  collections.deque_appendleft(d, 0)  # d is now [0, 1, 2, 3]`,
	},
	"deque_popleft": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if err := errors.ExactArgs(args, 1); err != nil {
				return err
			}
			deque, ok := args[0].(*object.List)
			if !ok {
				return errors.NewTypeError("LIST (deque)", args[0].Type().String())
			}
			if len(deque.Elements) == 0 {
				return errors.NewError("popleft from empty deque")
			}
			elem := deque.Elements[0]
			deque.Elements = deque.Elements[1:]
			return elem
		},
		HelpText: `deque_popleft(deque) - Remove and return element from left

Removes and returns the leftmost element.

Example:
  d = collections.deque([1, 2, 3])
  x = collections.deque_popleft(d)  # x=1, d=[2, 3]`,
	},
	"deque_extendleft": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			deque, ok := args[0].(*object.List)
			if !ok {
				return errors.NewTypeError("LIST (deque)", args[0].Type().String())
			}
			var elements []object.Object
			switch arg := args[1].(type) {
			case *object.List:
				elements = arg.Elements
			case *object.Tuple:
				elements = arg.Elements
			default:
				return errors.NewTypeError("iterable", args[1].Type().String())
			}

			newElements := make([]object.Object, len(elements)+len(deque.Elements))
			for i, elem := range elements {
				newElements[len(elements)-1-i] = elem
			}
			copy(newElements[len(elements):], deque.Elements)
			deque.Elements = newElements
			return &object.Null{}
		},
		HelpText: `deque_extendleft(deque, iterable) - Extend left with iterable

Extends the left side with elements from iterable (in reverse order).

Example:
  d = collections.deque([1, 2, 3])
  collections.deque_extendleft(d, [4, 5])  # d is now [5, 4, 1, 2, 3]`,
	},
	"deque_rotate": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			deque, ok := args[0].(*object.List)
			if !ok {
				return errors.NewTypeError("LIST (deque)", args[0].Type().String())
			}
			n, ok := args[1].(*object.Integer)
			if !ok {
				return errors.NewTypeError("INTEGER", args[1].Type().String())
			}
			if len(deque.Elements) == 0 {
				return &object.Null{}
			}

			steps := int(n.IntValue()) % len(deque.Elements)
			if steps < 0 {
				steps += len(deque.Elements)
			}
			if steps == 0 {
				return &object.Null{}
			}

			newElements := make([]object.Object, len(deque.Elements))
			splitPoint := len(deque.Elements) - steps
			copy(newElements, deque.Elements[splitPoint:])
			copy(newElements[steps:], deque.Elements[:splitPoint])
			deque.Elements = newElements
			return &object.Null{}
		},
		HelpText: `deque_rotate(deque, n) - Rotate deque n steps

Rotates the deque n steps to the right. If n is negative, rotates left.

Example:
  d = collections.deque([1, 2, 3, 4])
  collections.deque_rotate(d, 1)  # d is now [4, 1, 2, 3]
  collections.deque_rotate(d, -1) # d is now [1, 2, 3, 4]`,
	},
	"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())
			}

			methods := make(map[string]object.Object)

			methods["__init__"] = &object.Builtin{
				Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
					if len(args) != len(fieldNames)+1 {
						return errors.NewArgumentError(len(args), len(fieldNames)+1)
					}
					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)
					}
					nt.SetField("__fields__", &object.Tuple{Elements: fieldNameObjs})
					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)
					key := args[1].Inspect()

					if key == "__typename__" || key == "__fields__" {
						return &object.Null{}
					}
					if value, exists := nt.GetField(key); exists {
						return value
					}
					return &object.Null{}
				},
				HelpText: `__getitem__(key) - Get field value (supports nt[key] syntax)`,
			}

			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)
				for k, v := range d.Pairs {
					chainMap.Pairs[k] = v
				}
			}

			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,
	"DefaultDict": DefaultDictClass,
	"defaultdict": DefaultDictClass,
}, "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 CounterClass = &object.Class{
	Name: "Counter",
	Methods: map[string]object.Object{
		"__init__": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

				if len(args) == 0 {
					return &object.Null{}
				}
				counter := args[0].(*object.Instance)

				if len(args) == 1 {
					return &object.Null{}
				}
				if err := errors.MaxArgs(args, 2); err != nil {
					return err
				}

				countKey := func(key string) {
					if countObj, exists := counter.GetField(key); exists {
						if count, ok := countObj.(*object.Integer); ok {
							counter.SetField(key, object.NewInteger(count.IntValue()+1))
						}
					} else {
						counter.SetField(key, object.NewInteger(1))
					}
				}

				switch arg := args[1].(type) {
				case *object.List:
					for _, elem := range arg.Elements {
						countKey(elem.Inspect())
					}
				case *object.Tuple:
					for _, elem := range arg.Elements {
						countKey(elem.Inspect())
					}
				case *object.String:
					for _, ch := range arg.StringValue() {
						countKey(string(ch))
					}
				case *object.Dict:

					for _, v := range arg.Pairs {
						if count, ok := v.Value.(*object.Integer); ok {
							keyStr, _ := v.Key.AsString()
							counter.SetField(keyStr, count)
						}
					}
				default:
					return errors.NewTypeError("iterable or dict", args[1].Type().String())
				}

				return &object.Null{}
			},
		},
		"__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
				}
				counter := args[0].(*object.Instance)
				key := args[1].Inspect()

				if count, ok := counter.GetField(key); ok {
					return count
				}

				return object.NewInteger(0)
			},
			HelpText: `__getitem__(key) - Get count for key (supports c[key] syntax)`,
		},
		"most_common": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

				counter := args[0].(*object.Instance)

				n := counter.FieldCount()
				if len(args) == 2 {
					if nArg, ok := args[1].(*object.Integer); ok {
						n = int(nArg.IntValue())
					} else {
						return errors.NewTypeError("INTEGER", args[1].Type().String())
					}
				}

				// Convert to sortable slice
				type pair struct {
					key   string
					count int64
				}
				pairs := make([]pair, 0, counter.FieldCount())
				counter.RangeFields(func(key string, countObj object.Object) bool {
					if count, ok := countObj.(*object.Integer); ok {
						pairs = append(pairs, pair{key: key, count: count.IntValue()})
					}
					return true
				})

				sort.Slice(pairs, func(i, j int) bool {
					return pairs[i].count > pairs[j].count
				})

				if n > len(pairs) {
					n = len(pairs)
				}
				result := make([]object.Object, n)
				for i := 0; i < n; i++ {
					key := pairs[i].key

					if intVal, err := strconv.ParseInt(key, 10, 64); err == nil {
						result[i] = &object.Tuple{Elements: []object.Object{
							object.NewInteger(intVal),
							object.NewInteger(pairs[i].count),
						}}
					} else {
						result[i] = &object.Tuple{Elements: []object.Object{
							object.NewString(key),
							object.NewInteger(pairs[i].count),
						}}
					}
				}
				return &object.List{Elements: result}
			},
			HelpText: `most_common([n]) - Return n most common elements

Returns a list of (element, count) tuples sorted by count descending.
If n is omitted, returns all elements.`,
		},
		"elements": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.NoArgs(args); err != nil {
					return err
				}
				counter := args[0].(*object.Instance)

				var result []object.Object
				counter.RangeFields(func(key string, countObj object.Object) bool {
					if count, ok := countObj.(*object.Integer); ok {
						for i := int64(0); i < count.IntValue(); i++ {
							result = append(result, object.NewString(key))
						}
					}
					return true
				})
				return &object.List{Elements: result}
			},
			HelpText: `elements() - Return iterator over elements

Returns an iterator over elements, repeating each element by its count.`,
		},
	},
}

Counter class for counting elements

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 DefaultDictClass = &object.Class{
	Name: "DefaultDict",
	Methods: map[string]object.Object{
		"__init__": &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
				}
				dd := args[0].(*object.Instance)
				factory := args[1]

				dd.SetField("__default_factory__", factory)
				return &object.Null{}
			},
		},
		"__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
				}
				dd := args[0].(*object.Instance)
				key := args[1].Inspect()

				if value, exists := dd.GetField(key); exists {
					return value
				}

				factory, hasFactory := dd.GetField("__default_factory__")
				if !hasFactory {
					return &object.Null{}
				}

				// Create default value based on factory
				var defaultValue object.Object
				switch f := factory.(type) {
				case *object.Builtin:

					defaultValue = f.Fn(ctx, object.NewKwargs(nil))
					if object.IsError(defaultValue) {

						defaultValue = f.Fn(ctx, object.NewKwargs(nil), object.NewInteger(0))
						if object.IsError(defaultValue) {
							return defaultValue
						}
					}
				case *object.String:

					switch f.StringValue() {
					case "int":
						defaultValue = object.NewInteger(0)
					case "float":
						defaultValue = object.NewFloat(0)
					case "str":
						defaultValue = object.NewString("")
					case "list":
						defaultValue = &object.List{Elements: []object.Object{}}
					case "dict":
						defaultValue = &object.Dict{Pairs: make(map[string]object.DictPair)}
					default:
						return errors.NewError("unknown default factory type: %s", f.StringValue())
					}
				default:
					return errors.NewError("default_factory must be a builtin function or type name")
				}

				dd.SetField(key, defaultValue)
				return defaultValue
			},
			HelpText: `__getitem__(key) - Get value with default creation (supports d[key] syntax)`,
		},
		"__setitem__": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {

				if err := errors.ExactArgs(args, 3); err != nil {
					return err
				}
				dd := args[0].(*object.Instance)
				key := args[1].Inspect()
				value := args[2]

				dd.SetField(key, value)
				return &object.Null{}
			},
			HelpText: `__setitem__(key, value) - Set value (supports d[key] = value syntax)`,
		},
	},
}

DefaultDict class for dicts with default factory behavior

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 FunctoolsLibrary = object.NewLibrary(FunctoolsLibraryName, map[string]*object.Builtin{
	"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")
			}

			fn, ok := args[0].(*object.Function)
			if !ok {
				if builtin, ok := args[0].(*object.Builtin); ok {
					return reduceWithBuiltin(ctx, builtin, args[1:])
				}
				return errors.NewTypeError("FUNCTION", args[0].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.CallFunction(ctx, fn, []object.Object{accumulator, list.Elements[i]}, nil)
				if result == nil {
					return errors.NewError("reduce function returned nil")
				}
				if object.IsError(result) {
					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 builtin *object.Builtin
			if f, ok := args[0].(*object.Function); ok {
				fn = f
			} 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
			}

			return &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)
					}
					return builtin.Fn(ctx, object.NewKwargs(allKwargs), allArgs...)
				},
				HelpText: "Partial function application",
			}
		},
		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.ExactArgs(args, 1); err != nil {
				return err
			}
			str, ok := args[0].(*object.String)
			if !ok {
				return errors.NewTypeError("STRING", args[0].Type().String())
			}
			return object.NewString(html.EscapeString(str.StringValue()))
		},
		HelpText: `escape(s) - Escape HTML special characters

Converts &, <, >, ", and ' to HTML-safe sequences.

Parameters:
  s - String to escape

Returns: Escaped string

Example:
  import html
  safe = html.escape("<script>alert('xss')</script>")
  print(safe)  # "&lt;script&gt;alert(&#39;xss&#39;)&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,
	"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 {

			result := []object.Object{}
			for _, arg := range args {
				switch a := arg.(type) {
				case *object.List:
					result = append(result, a.Elements...)
				case *object.Tuple:
					result = append(result, a.Elements...)
				case *object.String:
					for _, ch := range a.StringValue() {
						result = append(result, object.NewString(string(ch)))
					}
				default:
					return errors.NewTypeError("iterable", arg.Type().String())
				}
			}
			return &object.List{Elements: result}
		},
		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
			}
			elem := args[0]
			times := int64(1)
			if len(args) == 2 {
				if n, ok := args[1].(*object.Integer); ok {
					times = n.IntValue()
				} else {
					return errors.NewTypeError("INTEGER", args[1].Type().String())
				}
			}
			if times < 0 {
				times = 0
			}
			result := make([]object.Object, times)
			for i := int64(0); i < times; i++ {
				result[i] = elem
			}
			return &object.List{Elements: result}
		},
		HelpText: `repeat(elem, n) - Repeat element n times

Returns a list with the element repeated n times.

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

			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:
				return errors.NewTypeError("iterable", args[0].Type().String())
			}
			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...)
			}
			return &object.List{Elements: result}
		},
		HelpText: `cycle(iterable, n) - Cycle through iterable n times

Returns a list with elements of iterable repeated n times.
Note: Unlike Python's infinite cycle, this requires specifying the count.

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

			if err := errors.RangeArgs(args, 2, 3); err != nil {
				return err
			}
			start, ok := args[0].(*object.Integer)
			if !ok {
				return errors.NewTypeError("INTEGER", args[0].Type().String())
			}
			stop, ok := args[1].(*object.Integer)
			if !ok {
				return errors.NewTypeError("INTEGER", args[1].Type().String())
			}
			step := int64(1)
			if len(args) == 3 {
				if s, ok := args[2].(*object.Integer); ok {
					step = s.IntValue()
				} else {
					return errors.NewTypeError("INTEGER", args[2].Type().String())
				}
			}
			if step == 0 {
				return errors.NewError("step cannot be zero")
			}
			result := []object.Object{}
			if step > 0 {
				for i := start.IntValue(); i < stop.IntValue(); i += step {
					result = append(result, object.NewInteger(i))
				}
			} else {
				for i := start.IntValue(); i > stop.IntValue(); i += step {
					result = append(result, object.NewInteger(i))
				}
			}
			return &object.List{Elements: result}
		},
		HelpText: `count(start, stop[, step]) - Generate a sequence of numbers

Returns a list of numbers from start to stop (exclusive) with optional step.
Similar to range() but as an itertools function.

Example:
  itertools.count(0, 5) -> [0, 1, 2, 3, 4]
  itertools.count(0, 10, 2) -> [0, 2, 4, 6, 8]`,
	},
	"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
			}
			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:
				return errors.NewTypeError("iterable", args[0].Type().String())
			}

			var start, stop, step int64 = 0, 0, 1
			if len(args) == 2 {

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

				if s, ok := args[1].(*object.Integer); ok {
					start = s.IntValue()
				} else {
					return errors.NewTypeError("INTEGER", args[1].Type().String())
				}
				if s, ok := args[2].(*object.Integer); ok {
					stop = s.IntValue()
				} else {
					return errors.NewTypeError("INTEGER", args[2].Type().String())
				}
				if len(args) == 4 {
					if s, ok := args[3].(*object.Integer); ok {
						step = s.IntValue()
					} else {
						return errors.NewTypeError("INTEGER", args[3].Type().String())
					}
				}
			}

			if step <= 0 {
				return errors.NewError("step must be positive")
			}
			if start < 0 {
				start = 0
			}
			if stop > int64(len(elements)) {
				stop = int64(len(elements))
			}

			result := []object.Object{}
			for i := start; i < stop; i += step {
				result = append(result, elements[i])
			}
			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, ok := args[0].(*object.Builtin)
			if !ok {
				return errors.NewError("takewhile() predicate must be a builtin function")
			}
			var elements []object.Object
			switch a := args[1].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			default:
				return errors.NewTypeError("iterable", args[1].Type().String())
			}
			result := []object.Object{}
			for _, elem := range elements {
				res := pred.Fn(ctx, object.NewKwargs(nil), elem)
				if object.IsError(res) {
					return res
				}
				if !isTruthy(res) {
					break
				}
				result = append(result, elem)
			}
			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, ok := args[0].(*object.Builtin)
			if !ok {
				return errors.NewError("dropwhile() predicate must be a builtin function")
			}
			var elements []object.Object
			switch a := args[1].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			default:
				return errors.NewTypeError("iterable", args[1].Type().String())
			}
			result := []object.Object{}
			dropping := true
			for _, elem := range elements {
				if dropping {
					res := pred.Fn(ctx, object.NewKwargs(nil), elem)
					if object.IsError(res) {
						return res
					}
					if isTruthy(res) {
						continue
					}
					dropping = false
				}
				result = append(result, elem)
			}
			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")
				}
			}

			iterables := make([][]object.Object, len(args))
			maxLen := 0
			for i, arg := range args {
				switch a := arg.(type) {
				case *object.List:
					iterables[i] = a.Elements
				case *object.Tuple:
					iterables[i] = a.Elements
				case *object.String:
					chars := []object.Object{}
					for _, ch := range a.StringValue() {
						chars = append(chars, object.NewString(string(ch)))
					}
					iterables[i] = chars
				default:
					return errors.NewTypeError("iterable", arg.Type().String())
				}
				if len(iterables[i]) > maxLen {
					maxLen = len(iterables[i])
				}
			}

			result := []object.Object{}
			for j := 0; j < maxLen; j++ {
				tuple := make([]object.Object, len(iterables))
				for i, iter := range iterables {
					if j < len(iter) {
						tuple[i] = iter[j]
					} else {
						tuple[i] = fillvalue
					}
				}
				result = append(result, &object.Tuple{Elements: tuple})
			}
			return &object.List{Elements: result}
		},
		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{}}}}
			}

			iterables := make([][]object.Object, len(args))
			for i, arg := range args {
				switch a := arg.(type) {
				case *object.List:
					iterables[i] = a.Elements
				case *object.Tuple:
					iterables[i] = a.Elements
				case *object.String:
					chars := []object.Object{}
					for _, ch := range a.StringValue() {
						chars = append(chars, object.NewString(string(ch)))
					}
					iterables[i] = chars
				default:
					return errors.NewTypeError("iterable", arg.Type().String())
				}
			}

			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, &object.Tuple{Elements: 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
				}
			}
			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 {

			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:
				return errors.NewTypeError("iterable", args[0].Type().String())
			}

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

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

			result := []object.Object{}
			generatePermutations(elements, r, []object.Object{}, make([]bool, len(elements)), &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 {

			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:
				return errors.NewTypeError("iterable", args[0].Type().String())
			}

			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)
			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 {

			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:
				return errors.NewTypeError("iterable", args[0].Type().String())
			}

			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)
			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:
				return errors.NewTypeError("iterable", args[0].Type().String())
			}

			var keyFunc *object.Builtin
			if len(args) == 2 {
				var ok bool
				keyFunc, ok = args[1].(*object.Builtin)
				if !ok {
					return errors.NewError("groupby() key must be a builtin function")
				}
			}

			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 = keyFunc.Fn(ctx, object.NewKwargs(nil), elem)
					if object.IsError(key) {
						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},
				}})
			}

			return &object.List{Elements: result}
		},
		HelpText: `groupby(iterable[, key]) - 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
			}
			var elements []object.Object
			switch a := args[0].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			default:
				return errors.NewTypeError("iterable", args[0].Type().String())
			}

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

			var accumFunc *object.Builtin
			if len(args) == 2 {
				var ok bool
				accumFunc, ok = args[1].(*object.Builtin)
				if !ok {
					return errors.NewError("accumulate() func must be a builtin function")
				}
			}

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

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

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

Returns list of accumulated values. Default is sum, but can provide custom function.

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, ok := args[0].(*object.Builtin)
			if !ok {
				return errors.NewError("filterfalse() predicate must be a builtin function")
			}
			var elements []object.Object
			switch a := args[1].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			default:
				return errors.NewTypeError("iterable", args[1].Type().String())
			}
			result := []object.Object{}
			for _, elem := range elements {
				res := pred.Fn(ctx, object.NewKwargs(nil), elem)
				if object.IsError(res) {
					return res
				}
				if !isTruthy(res) {
					result = append(result, elem)
				}
			}
			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, ok := args[0].(*object.Builtin)
			if !ok {
				return errors.NewError("starmap() func must be a builtin function")
			}
			var elements []object.Object
			switch a := args[1].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			default:
				return errors.NewTypeError("iterable", args[1].Type().String())
			}
			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 := fn.Fn(ctx, object.NewKwargs(nil), fnArgs...)
				if object.IsError(res) {
					return res
				}
				result = append(result, res)
			}
			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
			}
			var data []object.Object
			switch a := args[0].(type) {
			case *object.List:
				data = a.Elements
			case *object.Tuple:
				data = a.Elements
			default:
				return errors.NewTypeError("iterable", args[0].Type().String())
			}
			var selectors []object.Object
			switch a := args[1].(type) {
			case *object.List:
				selectors = a.Elements
			case *object.Tuple:
				selectors = a.Elements
			default:
				return errors.NewTypeError("iterable", args[1].Type().String())
			}

			result := []object.Object{}
			minLen := len(data)
			if len(selectors) < minLen {
				minLen = len(selectors)
			}
			for i := 0; i < minLen; i++ {
				if isTruthy(selectors[i]) {
					result = append(result, data[i])
				}
			}
			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:
				return errors.NewTypeError("iterable", args[0].Type().String())
			}

			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]}}
			}
			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 {

			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:
				return errors.NewTypeError("iterable", args[0].Type().String())
			}

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

			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, &object.Tuple{Elements: batch})
			}
			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="") - Serialize object to JSON string

Converts a Scriptling object to its JSON string representation.
Optional indent parameter for pretty-printing.`,
	},
	"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.`,
	},
}, nil, "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
				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 || groupNum >= len(groups) {
					return errors.NewError("no such group: %d", groupNum)
				}
				return groups[groupNum]
			},
			HelpText: `group(n=0) - Return the nth matched group (0 = full match)`,
		},
		"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)
				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{
	"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.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())
			}
			if x <= 0 {
				return errors.NewError("log: domain error")
			}
			return object.NewFloat(math.Log(x))
		},
		HelpText: `log(x) - Return the natural logarithm of x

x must be positive (integer or float).
Returns a float.`,
	},
	"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.`,
	},
	"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, ok := args[0].(*object.List)
			if !ok {
				return errors.NewTypeError("LIST", args[0].Type().String())
			}
			qList, ok := args[1].(*object.List)
			if !ok {
				return errors.NewTypeError("LIST", args[1].Type().String())
			}
			if len(pList.Elements) != len(qList.Elements) {
				return errors.NewError("dist: points must have the same dimension")
			}
			var sum float64
			for i := 0; i < len(pList.Elements); i++ {
				p, err := pList.Elements[i].AsFloat()
				if err != nil {
					return errors.NewTypeError("INTEGER or FLOAT", pList.Elements[i].Type().String())
				}
				q, err := qList.Elements[i].AsFloat()
				if err != nil {
					return errors.NewTypeError("INTEGER or FLOAT", qList.Elements[i].Type().String())
				}
				d := p - q
				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 {
			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 errors.NewError("choice() string cannot be empty")
				}
				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 errors.NewError("choice() list cannot be empty")
				}
				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 {
			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 {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			list, err := args[0].AsList()
			if err != nil {
				return err
			}
			k, ok := args[1].(*object.Integer)
			if !ok {
				return errors.NewTypeError("INTEGER", args[1].Type().String())
			}
			n := len(list)
			if k.IntValue() < 0 || k.IntValue() > int64(n) {
				return errors.NewError("sample larger than population or is negative")
			}

			indices := make([]int, n)
			for i := range indices {
				indices[i] = i
			}

			for i := 0; i < int(k.IntValue()); i++ {
				j := i + rng.Intn(n-i)
				indices[i], indices[j] = indices[j], indices[i]
			}

			result := make([]object.Object, k.IntValue())
			for i := 0; i < int(k.IntValue()); i++ {
				result[i] = list[indices[i]]
			}
			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.NewError("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
			}
			population, err := args[0].AsList()
			if err != nil {
				return err
			}
			n := len(population)
			if n == 0 {
				return errors.NewError("choices: population cannot be empty")
			}

			var weights []float64
			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]
			}
			if wObj != nil {
				if _, ok := wObj.(*object.Null); ok {
					wObj = nil
				}
			}
			if wObj != nil {
				wList, ok := wObj.(*object.List)
				if !ok {
					return errors.NewTypeError("LIST", wObj.Type().String())
				}
				if len(wList.Elements) != n {
					return errors.NewError("choices: weights length (%d) must match population length (%d)", len(wList.Elements), n)
				}
				weights = make([]float64, n)
				for i, w := range wList.Elements {
					f, err := w.AsFloat()
					if err != nil {
						return errors.NewTypeError("INTEGER or FLOAT", w.Type().String())
					}
					if f < 0 || math.IsNaN(f) || math.IsInf(f, 0) {
						return errors.NewError("choices: weights must be finite and non-negative")
					}
					weights[i] = f
				}
			} else {
				weights = make([]float64, n)
				for i := range weights {
					weights[i] = 1.0
				}
			}

			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 total <= 0 || math.IsInf(total, 0) || math.IsNaN(total) {
				return errors.NewError("choices: total of weights must be positive and finite")
			}

			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)
			}

			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]
			}
			return &object.List{Elements: result}
		},
		HelpText: `choices(population, weights=None, k=1) - Weighted random sampling with replacement

Select k items from population with the given weights.
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.RangeArgs(args, 2, 3); err != nil {
				return err
			}
			low, err := args[0].AsFloat()
			if err != nil {
				return errors.NewTypeError("INTEGER or FLOAT", args[0].Type().String())
			}
			high, err := args[1].AsFloat()
			if err != nil {
				return errors.NewTypeError("INTEGER or FLOAT", args[1].Type().String())
			}
			mode := (low + high) / 2.0
			if len(args) == 3 {
				mode, err = args[2].AsFloat()
				if err != nil {
					return errors.NewTypeError("INTEGER or FLOAT", args[2].Type().String())
				}
			}
			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, 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(groups, match[0], match[1])
		},
		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, 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(groups, match[0], match[1])
		},
		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, 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))
			numGroups := re.NumSubexp()
			for i, match := range matches {
				if numGroups == 0 {
					elements[i] = object.NewString(match[0])
				} else if numGroups == 1 {
					elements[i] = object.NewString(match[1])
				} else {
					groupElements := make([]object.Object, numGroups)
					for j := 1; j <= numGroups; j++ {
						groupElements[j-1] = object.NewString(match[j])
					}
					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, 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))
			for i, match := range allMatches {
				elements[i] = createMatchInstance(groupsFromIndices(text, match), match[0], match[1])
			}
			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
			if len(args) > 3 {
				if args[3].Type() != object.INTEGER_OBJ {
					return errors.NewError("count must be an integer")
				}
				val, _ := args[3].AsInt()
				count = int(val)
				if count == 0 {
					count = -1
				}
			}

			flags, err := getFlags(args, 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(groupsFromIndices(text, match), match[0], match[1])

					// 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 {
						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, replacementStr)
			} else {

				replaced := 0
				result = re.ReplaceAllStringFunc(text, func(match string) string {
					if replaced < count {
						replaced++
						return re.ReplaceAllString(match, 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`,
	},
	"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
			if len(args) > 2 {
				if args[2].Type() != object.INTEGER_OBJ {
					return errors.NewError("maxsplit must be an integer")
				}
				val, _ := args[2].AsInt()
				maxsplit = int(val)
				if maxsplit == 0 {
					maxsplit = -1
				}
			}

			flags, err := getFlags(args, 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())
			}

			parts := re.Split(text, maxsplit)
			elements := make([]object.Object, len(parts))
			for i, part := range parts {
				elements[i] = object.NewString(part)
			}
			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, 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, 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(groups, match[0], match[1])
		},
		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(groupsFromIndices(text, match), match[0], match[1])
			},
			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(groupsFromIndices(text, match), match[0], match[1])
			},
			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))
					for i, match := range matches {
						elements[i] = object.NewString(match)
					}
					return &object.List{Elements: elements}
				}

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

					groupElements := make([]object.Object, numGroups)
					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.NewString(text[start:end])
						} 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))
				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(groups, match[0], match[1])
				}
				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)
			}
			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 TextwrapLibrary = object.NewLibrary(TextwrapLibraryName, map[string]*object.Builtin{
	"wrap": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			width, _ := kwargs.GetInt("width", 70)
			widthInt := int(width)

			if len(args) < 1 {
				return errors.NewError("wrap() requires at least 1 argument")
			}

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

			if len(args) >= 2 {
				if intVal, ok := args[1].(*object.Integer); ok {
					widthInt = int(intVal.IntValue())
				}
			}

			lines := wrapText(text.StringValue(), widthInt)
			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) - Wrap a single paragraph of text

Parameters:
  text  - Text to wrap
  width - Maximum line width (default 70)

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 {
			width, _ := kwargs.GetInt("width", 70)
			widthInt := int(width)

			if len(args) < 1 {
				return errors.NewError("fill() requires at least 1 argument")
			}

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

			if len(args) >= 2 {
				if intVal, ok := args[1].(*object.Integer); ok {
					widthInt = int(intVal.IntValue())
				}
			}

			lines := wrapText(text.StringValue(), widthInt)
			return object.NewString(strings.Join(lines, "\n"))
		},
		HelpText: `fill(text, width=70) - Wrap text and return a single string

Parameters:
  text  - Text to wrap
  width - Maximum line width (default 70)

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())
			}

			lines := strings.Split(text.StringValue(), "\n")
			result := make([]string, len(lines))
			for i, line := range lines {

				if len(strings.TrimSpace(line)) > 0 {
					result[i] = prefix.StringValue() + line
				} else {
					result[i] = line
				}
			}
			return object.NewString(strings.Join(result, "\n"))
		},
		HelpText: `indent(text, prefix) - Add prefix to non-empty lines

Parameters:
  text   - Text to indent
  prefix - String to add to beginning of each line

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 {
			placeholder, _ := kwargs.GetString("placeholder", "[...]")

			if len(args) < 2 {
				return errors.NewError("shorten() requires at least 2 arguments")
			}

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

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

			collapsed := collapseWhitespace(text.StringValue())
			if len(collapsed) <= width {
				return object.NewString(collapsed)
			}

			if width <= len(placeholder) {
				return object.NewString(placeholder[:width])
			}

			maxLen := width - len(placeholder)
			truncated := collapsed[:maxLen]

			lastSpace := strings.LastIndex(truncated, " ")
			if lastSpace > 0 {
				truncated = truncated[:lastSpace]
			}

			return object.NewString(strings.TrimSpace(truncated) + placeholder)
		},
		HelpText: `shorten(text, width, placeholder="[...]") - Truncate text to fit width

Parameters:
  text        - Text to shorten
  width       - Maximum width including placeholder
  placeholder - String to indicate truncation (default "[...]")

Returns: Shortened text

Example:
  import textwrap
  result = textwrap.shorten("Hello World!", 10)  # "Hello[...]"`,
	},
}, nil, "Text wrapping and filling utilities")
View Source
var TimeLibrary = object.NewLibrary(TimeLibraryName, map[string]*object.Builtin{
	"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
			}

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

			if len(tuple) != 9 {
				return errors.NewError("time tuple must have exactly 9 elements")
			}

			year, _ := tuple[0].AsInt()
			month, _ := tuple[1].AsInt()
			day, _ := tuple[2].AsInt()
			hour, _ := tuple[3].AsInt()
			minute, _ := tuple[4].AsInt()
			second, _ := tuple[5].AsInt()

			t := time.Date(int(year), time.Month(month), int(day), int(hour), int(minute), int(second), 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 {
				tuple, err := args[1].AsList()
				if err != nil {
					return err
				}
				if len(tuple) != 9 {
					return errors.NewError("time tuple must have exactly 9 elements")
				}

				year, _ := tuple[0].AsInt()
				month, _ := tuple[1].AsInt()
				day, _ := tuple[2].AsInt()
				hour, _ := tuple[3].AsInt()
				minute, _ := tuple[4].AsInt()
				second, _ := tuple[5].AsInt()

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

			return object.NewString(t.Format(pythonToGoFormat(format)))
		},
		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 {
				tuple, err := args[0].AsList()
				if err != nil {
					return err
				}
				if len(tuple) != 9 {
					return errors.NewError("time tuple must have exactly 9 elements")
				}

				year, _ := tuple[0].AsInt()
				month, _ := tuple[1].AsInt()
				day, _ := tuple[2].AsInt()
				hour, _ := tuple[3].AsInt()
				minute, _ := tuple[4].AsInt()
				second, _ := tuple[5].AsInt()

				t = time.Date(int(year), time.Month(month), int(day), int(hour), int(minute), int(second), 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 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 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 object.NewString(id.String())
		},
		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 object.NewString(uuid.New().String())
		},
		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 object.NewString(id.String())
		},
		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"`,
	},
}, nil, "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 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