Help us shape the future of Go! Take the 2026 Go Developer Survey

stdlib

package
v0.27.2 Latest Latest
Warning

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

Go to latest
Published: Oct 1, 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)
			mlObj := object.Object(nil)
			if len(args) >= 2 {
				mlObj = args[1]
			} else if v := kwargs.Get("maxlen"); v != nil {
				mlObj = v
			}
			if mlObj != nil {
				if ml, ok := mlObj.(*object.Integer); ok {
					maxlen = ml.IntValue()
					if maxlen >= 0 && int64(len(elements)) > maxlen {

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

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

Creates a double-ended queue with appendleft/popleft/rotate and friends,
like Python's collections.deque. maxlen (or None) bounds the length,
dropping from the opposite end on overflow.

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

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

			methods["__getitem__"] = &object.Builtin{
				Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
					if err := errors.ExactArgs(args, 2); err != nil {
						return err
					}
					nt := args[0].(*object.Instance)
					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)`,
			}

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

			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{}
			},
		},
		"__add__": &object.Builtin{Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if a, b, err := twoCounters(args); err != nil {
				return err
			} else if a != nil {
				return counterArithFn('+', a, b)
			}
			return nil
		}},
		"__sub__": &object.Builtin{Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if a, b, err := twoCounters(args); err != nil {
				return err
			} else if a != nil {
				return counterArithFn('-', a, b)
			}
			return nil
		}},
		"__or__": &object.Builtin{Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if a, b, err := twoCounters(args); err != nil {
				return err
			} else if a != nil {
				return counterArithFn('|', a, b)
			}
			return nil
		}},
		"__and__": &object.Builtin{Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if a, b, err := twoCounters(args); err != nil {
				return err
			} else if a != nil {
				return counterArithFn('&', a, b)
			}
			return nil
		}},
		"__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.`,
		},
	},
}
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 DequeClass = &object.Class{
	Name: "deque",
	Methods: map[string]object.Object{
		"append": &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 := args[0].(*object.Instance)
			elems := append(dequeElems(inst), args[1])
			setDequeElems(inst, dequeClamp(inst, elems, true))
			return &object.Null{}
		}},
		"appendleft": &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 := args[0].(*object.Instance)
			elems := append([]object.Object{args[1]}, dequeElems(inst)...)
			setDequeElems(inst, dequeClamp(inst, elems, false))
			return &object.Null{}
		}},
		"pop": &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 := args[0].(*object.Instance)
			elems := dequeElems(inst)
			if len(elems) == 0 {
				return &object.Exception{Message: "pop from an empty deque", ExceptionType: object.ExceptionTypeIndexError, Raised: true}
			}
			v := elems[len(elems)-1]
			setDequeElems(inst, elems[:len(elems)-1])
			return v
		}},
		"popleft": &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 := args[0].(*object.Instance)
			elems := dequeElems(inst)
			if len(elems) == 0 {
				return &object.Exception{Message: "pop from an empty deque", ExceptionType: object.ExceptionTypeIndexError, Raised: true}
			}
			v := elems[0]
			setDequeElems(inst, elems[1:])
			return v
		}},
		"extend": &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 := args[0].(*object.Instance)
			add, ok := object.IterableToSlice(args[1])
			if !ok {
				return errors.NewTypeError("iterable", args[1].Type().String())
			}
			elems := append(dequeElems(inst), add...)
			setDequeElems(inst, dequeClamp(inst, elems, true))
			return &object.Null{}
		}},
		"extendleft": &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 := args[0].(*object.Instance)
			add, ok := object.IterableToSlice(args[1])
			if !ok {
				return errors.NewTypeError("iterable", args[1].Type().String())
			}

			elems := dequeElems(inst)
			for _, v := range add {
				elems = append([]object.Object{v}, elems...)
			}
			setDequeElems(inst, dequeClamp(inst, elems, false))
			return &object.Null{}
		}},
		"rotate": &object.Builtin{Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) < 1 || len(args) > 2 {
				return errors.NewError("rotate() takes 0-1 arguments (%d given)", len(args)-1)
			}
			inst := args[0].(*object.Instance)
			n := int64(1)
			if len(args) == 2 {
				v, err := args[1].AsInt()
				if err != nil {
					return errors.ParameterError("n", err)
				}
				n = v
			}
			elems := dequeElems(inst)
			length := int64(len(elems))
			if length == 0 {
				return &object.Null{}
			}
			n = ((n % length) + length) % length
			rotated := append(append([]object.Object{}, elems[length-n:]...), elems[:length-n]...)
			setDequeElems(inst, rotated)
			return &object.Null{}
		}},
		"clear": &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
			}
			setDequeElems(args[0].(*object.Instance), []object.Object{})
			return &object.Null{}
		}},
		"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 := args[0].(*object.Instance)
			elems := append([]object.Object{}, dequeElems(inst)...)
			ml := int64(-1)
			if m, ok := inst.Field("maxlen").(*object.Integer); ok {
				ml = m.IntValue()
			}
			return newDeque(elems, ml)
		}},
		"count": &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
			}
			n := int64(0)
			for _, v := range dequeElems(args[0].(*object.Instance)) {
				if objectsEqual(v, args[1]) {
					n++
				}
			}
			return object.NewInteger(n)
		}},
		"__len__": &object.Builtin{Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			return object.NewInteger(int64(len(dequeElems(args[0].(*object.Instance)))))
		}},
		"__iter__": &object.Builtin{Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			elems := append([]object.Object{}, dequeElems(args[0].(*object.Instance))...)
			i := 0
			return object.NewIterator(func() (object.Object, bool) {
				if i >= len(elems) {
					return nil, false
				}
				v := elems[i]
				i++
				return v, true
			})
		}},
		"__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
			}
			elems := dequeElems(args[0].(*object.Instance))
			idx, err := args[1].AsInt()
			if err != nil {
				return errors.ParameterError("index", err)
			}
			if idx < 0 {
				idx += int64(len(elems))
			}
			if idx < 0 || idx >= int64(len(elems)) {
				return &object.Exception{Message: "deque index out of range", ExceptionType: object.ExceptionTypeIndexError, Raised: true}
			}
			return elems[idx]
		}},
		"__bool__": &object.Builtin{Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			return object.NewBoolean(len(dequeElems(args[0].(*object.Instance))) > 0)
		}},
		"__str__": &object.Builtin{Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			inst := args[0].(*object.Instance)
			parts := make([]string, 0, 8)
			for _, v := range dequeElems(inst) {
				parts = append(parts, v.Inspect())
			}
			out := "deque([" + strings.Join(parts, ", ") + "]"
			if m, ok := inst.Field("maxlen").(*object.Integer); ok {
				out += ", maxlen=" + m.Inspect()
			}
			return object.NewString(out + ")")
		}},
	},
}

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

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

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

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

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

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

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

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

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

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

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

Returns: Reduced value

Example:
  import functools

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

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

			var fn *object.Function
			var 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:
					if elems, ok := object.IterableToSlice(arg); ok {
						result = append(result, elems...)
						break
					}
					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.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:
				if elems, ok := object.IterableToSlice(args[0]); ok {
					elements = elems
					break
				}
				return errors.NewTypeError("iterable", args[0].Type().String())
			}
			if len(args) == 2 {
				n, ok := args[1].(*object.Integer)
				if !ok {
					return errors.NewTypeError("INTEGER", args[1].Type().String())
				}
				if len(elements) == 0 || n.IntValue() <= 0 {
					return &object.List{Elements: []object.Object{}}
				}
				result := make([]object.Object, 0, len(elements)*int(n.IntValue()))
				for i := int64(0); i < n.IntValue(); i++ {
					result = append(result, elements...)
				}
				return &object.List{Elements: result}
			}
			if len(elements) == 0 {
				return &object.Exception{Message: "cycle() of empty iterable", ExceptionType: object.ExceptionTypeRuntimeError, Raised: true}
			}
			i := 0
			return object.NewIterator(func() (object.Object, bool) {
				elem := elements[i%len(elements)]
				i++
				return elem, true
			})
		},
		HelpText: `cycle(iterable) - Cycle through iterable endlessly

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

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

			if iter, isIter := args[0].(*object.Iterator); isIter {
				var start, stop, step int64 = 0, 0, 1
				if err := parseIsliceBounds(args, &start, &stop, &step); err != nil {
					return err
				}
				if step <= 0 {
					return errors.NewError("step must be positive")
				}
				skipped := int64(0)
				taken := int64(0)
				return object.NewIterator(func() (object.Object, bool) {
					for {
						if taken >= stop-start {
							return nil, false
						}
						val, ok := iter.Next()
						if !ok {
							return nil, false
						}
						pos := skipped
						skipped++
						if pos < start || (pos-start)%step != 0 {
							continue
						}
						taken++
						return val, true
					}
				})
			}

			elements, ok := object.IterableToSlice(args[0])
			if !ok {
				return errors.NewTypeError("iterable", args[0].Type().String())
			}

			var start, stop, step int64 = 0, 0, 1
			if err := parseIsliceBounds(args, &start, &stop, &step); err != nil {
				return err
			}

			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 := args[0]
			if !isCallable(pred) {
				return errors.NewTypeError("callable", pred.Type().String())
			}
			var elements []object.Object
			switch a := args[1].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			default:
				if elems, ok := object.IterableToSlice(args[1]); ok {
					elements = elems
					break
				}
				return errors.NewTypeError("iterable", args[1].Type().String())
			}
			result := []object.Object{}
			for _, elem := range elements {
				res := callCallable(ctx, pred, elem)
				if object.IsError(res) || res.Type() == object.EXCEPTION_OBJ {
					return res
				}
				if !isTruthy(res) {
					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 := args[0]
			if !isCallable(pred) {
				return errors.NewTypeError("callable", pred.Type().String())
			}
			var elements []object.Object
			switch a := args[1].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			default:
				if elems, ok := object.IterableToSlice(args[1]); ok {
					elements = elems
					break
				}
				return errors.NewTypeError("iterable", args[1].Type().String())
			}
			result := []object.Object{}
			dropping := true
			for _, elem := range elements {
				if dropping {
					res := callCallable(ctx, pred, elem)
					if object.IsError(res) || res.Type() == object.EXCEPTION_OBJ {
						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:
					if elems, ok := object.IterableToSlice(arg); ok {
						iterables[i] = elems
						break
					}
					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{}}}}
			}

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

			collected := make([][]object.Object, 0, len(args))
			for _, arg := range args {
				switch a := arg.(type) {
				case *object.List:
					collected = append(collected, a.Elements)
				case *object.Tuple:
					collected = append(collected, a.Elements)
				case *object.String:
					chars := []object.Object{}
					for _, ch := range a.StringValue() {
						chars = append(chars, object.NewString(string(ch)))
					}
					collected = append(collected, chars)
				default:
					if elems, ok := object.IterableToSlice(arg); ok {
						collected = append(collected, elems)
						break
					}
					return errors.NewTypeError("iterable", arg.Type().String())
				}
			}
			iterables := make([][]object.Object, 0, int(repeatN)*len(collected))
			for rep := int64(0); rep < repeatN; rep++ {
				iterables = append(iterables, collected...)
			}

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

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

			for {

				tuple := make([]object.Object, len(iterables))
				for i, idx := range indices {
					tuple[i] = iterables[i][idx]
				}
				result = append(result, &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:
				if elems, ok := object.IterableToSlice(args[0]); ok {
					elements = elems
					break
				}
				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:
				if elems, ok := object.IterableToSlice(args[0]); ok {
					elements = elems
					break
				}
				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:
				if elems, ok := object.IterableToSlice(args[0]); ok {
					elements = elems
					break
				}
				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:
				if elems, ok := object.IterableToSlice(args[0]); ok {
					elements = elems
					break
				}
				return errors.NewTypeError("iterable", args[0].Type().String())
			}

			var keyFunc object.Object
			if len(args) == 2 {
				keyFunc = args[1]
				if !isCallable(keyFunc) {
					return errors.NewTypeError("callable", keyFunc.Type().String())
				}
			}

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

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

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

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

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

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

			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:
				if elems, ok := object.IterableToSlice(args[0]); ok {
					elements = elems
					break
				}
				return errors.NewTypeError("iterable", args[0].Type().String())
			}

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

			var accumFunc object.Object
			if len(args) == 2 {
				accumFunc = args[1]
				if !isCallable(accumFunc) {
					return errors.NewTypeError("callable", accumFunc.Type().String())
				}
			}

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

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

					accumulator = addObjects(accumulator, elements[i])
					if object.IsError(accumulator) {
						return accumulator
					}
				}
				result = append(result, accumulator)
			}
			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 := args[0]
			if !isCallable(pred) {
				return errors.NewTypeError("callable", pred.Type().String())
			}
			var elements []object.Object
			switch a := args[1].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			default:
				if elems, ok := object.IterableToSlice(args[1]); ok {
					elements = elems
					break
				}
				return errors.NewTypeError("iterable", args[1].Type().String())
			}
			result := []object.Object{}
			for _, elem := range elements {
				res := callCallable(ctx, pred, elem)
				if object.IsError(res) || res.Type() == object.EXCEPTION_OBJ {
					return res
				}
				if !isTruthy(res) {
					result = append(result, elem)
				}
			}
			return &object.List{Elements: result}
		},
		HelpText: `filterfalse(predicate, iterable) - Filter elements where predicate is false

Returns elements for which the predicate returns false.

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

			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			fn := args[0]
			if !isCallable(fn) {
				return errors.NewTypeError("callable", fn.Type().String())
			}
			var elements []object.Object
			switch a := args[1].(type) {
			case *object.List:
				elements = a.Elements
			case *object.Tuple:
				elements = a.Elements
			default:
				if elems, ok := object.IterableToSlice(args[1]); ok {
					elements = elems
					break
				}
				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 := callCallable(ctx, fn, fnArgs...)
				if object.IsError(res) || res.Type() == object.EXCEPTION_OBJ {
					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
			}
			data, dataOK := object.IterableToSlice(args[0])
			if !dataOK {
				return errors.NewTypeError("iterable", args[0].Type().String())
			}
			selectors, selOK := object.IterableToSlice(args[1])
			if !selOK {
				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:
				if elems, ok := object.IterableToSlice(args[0]); ok {
					elements = elems
					break
				}
				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:
				if elems, ok := object.IterableToSlice(args[0]); ok {
					elements = elems
					break
				}
				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: a string used verbatim, or a
number of spaces (indent=2 is the common idiom; indent=0 newline-separates
with no spaces). Object keys are always emitted in sorted order.`,
	},
	"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
				names := match.Field("_group_names").(*object.List).Elements
				groupNum := 0
				if len(args) == 2 {
					if args[1].Type() == object.STRING_OBJ {

						name, _ := args[1].AsString()
						for i, n := range names {
							if ns, ok := n.(*object.String); ok && ns.StringValue() == name {
								return groups[i]
							}
						}
						return &object.Exception{Message: "no such group", ExceptionType: object.ExceptionTypeIndexError, Raised: true}
					}
					if args[1].Type() != object.INTEGER_OBJ {
						return errors.NewTypeError("INTEGER or STRING", args[1].Type().String())
					}
					val, _ := args[1].AsInt()
					groupNum = int(val)
				}
				if groupNum < 0 || groupNum >= len(groups) {
					return &object.Exception{Message: "no such group", ExceptionType: object.ExceptionTypeIndexError, Raised: true}
				}
				return groups[groupNum]
			},
			HelpText: `group([n]) - Return the nth matched group (0 = full match), or the group captured under a (?P<name>...) name`,
		},
		"groupdict": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				match := args[0].(*object.Instance)
				groups := match.Field("_groups").(*object.List).Elements
				names := match.Field("_group_names").(*object.List).Elements
				result := &object.Dict{Pairs: make(map[string]object.DictPair)}
				for i, n := range names {
					ns, ok := n.(*object.String)
					if !ok || ns.StringValue() == "" || i >= len(groups) {
						continue
					}
					result.SetByString(ns.StringValue(), groups[i])
				}
				return result
			},
			HelpText: `groupdict() - Return a dict of named groups to their matched text`,
		},
		"groups": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				match := args[0].(*object.Instance)
				groups := match.Field("_groups").(*object.List).Elements
				if len(groups) <= 1 {
					return &object.Tuple{Elements: []object.Object{}}
				}
				elements := make([]object.Object, len(groups)-1)
				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.`,
	},
	"isclose": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			a, err := args[0].AsFloat()
			if err != nil {
				return errors.NewTypeError("NUMBER", args[0].Type().String())
			}
			b, err := args[1].AsFloat()
			if err != nil {
				return errors.NewTypeError("NUMBER", args[1].Type().String())
			}

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

With start keyword, begins multiplication from that value.
Returns an integer for all-integer inputs, float otherwise.`,
	},
	"dist": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 2); err != nil {
				return err
			}
			pList, 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], re.SubexpNames())
		},
		HelpText: `match(pattern, string, flags=0) - Match pattern at start of string

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

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

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

			flags, err := getFlags(args, 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], re.SubexpNames())
		},
		HelpText: `search(pattern, string, flags=0) - Search for pattern anywhere in string

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

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

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

			flags, err := getFlags(args, 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], re.SubexpNames())
			}
			return &object.List{Elements: elements}
		},
		HelpText: `finditer(pattern, string, flags=0) - Find all matches as Match objects

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

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

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

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

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

			flags, err := getFlags(args, 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], re.SubexpNames())

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

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

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

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

					lastEnd = match[1]
					replaced++
				}

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

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

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

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

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

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

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

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

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

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

			splitN := maxsplit
			if maxsplit > 0 {
				splitN = maxsplit + 1
			}
			parts := re.Split(text, splitN)
			elements := make([]object.Object, len(parts))
			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], re.SubexpNames())
		},
		HelpText: `fullmatch(pattern, string, flags=0) - Match entire string

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

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

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

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

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

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

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

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

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

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

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

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

				numGroups := re.NumSubexp()

				if numGroups == 0 {
					matches := re.FindAllString(text, -1)
					elements := make([]object.Object, len(matches))
					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], re.SubexpNames())
				}
				return &object.List{Elements: elements}
			},
			HelpText: `finditer(string) - Find all matches as Match objects

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

Regex class definition

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

Parameters:
  data - List of numbers

Returns: Float

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

Parameters:
  data - List of numbers

Returns: Float

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

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

Parameters:
  data - List of positive numbers

Returns: Float

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

Parameters:
  data - List of positive numbers

Returns: Float

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

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

Parameters:
  data - List of numbers

Returns: Float

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

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

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

Parameters:
  data - List of values

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

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

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

Returns: Float

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

Parameters:
  data - List of numbers

Returns: Float

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

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

Returns: Float

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

Parameters:
  data - List of numbers

Returns: Float

Example:
  import statistics
  statistics.pvariance([1, 2, 3, 4, 5])  # 2.0`,
	},
}, nil, "Statistical functions library")
View Source
var StringLibrary = object.NewLibrary(StringLibraryName,
	nil,
	map[string]object.Object{
		"ascii_letters":   object.NewString(asciiLetters),
		"ascii_lowercase": object.NewString(asciiLowercase),
		"ascii_uppercase": object.NewString(asciiUppercase),
		"digits":          object.NewString(digits),
		"hexdigits":       object.NewString(hexdigits),
		"octdigits":       object.NewString(octdigits),
		"punctuation":     object.NewString(punctuation),
		"whitespace":      object.NewString(whitespace),
		"printable":       object.NewString(printable),
	},
	"String constants for character classification",
)
View Source
var 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 TimedeltaClass = &object.Class{
	Name: "timedelta",
	Methods: map[string]object.Object{
		"total_seconds": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.MinArgs(args, 1); err != nil {
					return err
				}
				inst, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewTypeError("timedelta instance", args[0].Type().String())
				}
				total, ok := inst.Field("_total").(*object.Float)
				if !ok {
					return errors.NewTypeError("timedelta instance", args[0].Type().String())
				}
				return object.NewFloat(total.FloatValue())
			},
			HelpText: "total_seconds() - Return the total duration in seconds",
		},
		"__str__": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.MinArgs(args, 1); err != nil {
					return err
				}
				inst, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewTypeError("timedelta instance", args[0].Type().String())
				}
				if repr, ok := inst.Field("__str_repr__").(*object.String); ok {
					return repr
				}
				return object.NewString("")
			},
			HelpText: "__str__() - Return the Python-style duration string",
		},
	},
}

TimedeltaClass is the class behind datetime.timedelta values.

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