From 9b1d83342dc05c529039605db6b785ff5922c2f0 Mon Sep 17 00:00:00 2001 From: Branden J Brown Date: Mon, 6 Apr 2020 23:02:30 -0400 Subject: [PATCH] reorganize into multiple packages Most Core proto implementations have moved into package internal; some have moved to subpackages under coreext. Package iolang itself proxies important internal types and constants for external use. This change makes documentation much easier to read and allows embedders to be more selective about what features are available. Tests pass, but it is possible that dependencies were missed, so some methods might be broken. Some individual methods of core protos can also still be moved to external packages. Documentation on almost everything in package iolang will need to be fixed. --- addons/Range/Range/main.go | 4 +- addons/Range/addon.go | 3 +- cmd/gencore/gencore.go | 8 +- cmd/io/main.go | 2 + cmd/mkaddon/mkaddon.go | 7 +- coreext/README.md | 5 + coreext/addon/addon.go | 117 +++ coreext/addon/addon_init.go | 9 + io/15_Addon.io => coreext/addon/io/Addon.io | 0 .../collector/collector.go | 33 +- coreext/coreext.go | 16 + coreext/coroutine/coroutine.go | 90 +++ coreext/coroutine/coroutine_init.go | 9 + coreext/coroutine/coroutine_test.go | 85 ++ .../coroutine/io/Coroutine.io | 5 +- date.go => coreext/date/date.go | 306 +++---- coreext/date/date_init.go | 9 + io/11_Date.io => coreext/date/io/Date.io | 5 - coreext/debugger/debugger.go | 90 +++ coreext/debugger/debugger_init.go | 9 + .../debugger/io/Debugger.io | 0 .../directory/directory.go | 98 ++- coreext/directory/directory_init.go | 9 + {io => coreext/directory/io}/08_Directory.io | 0 duration.go => coreext/duration/duration.go | 147 ++-- coreext/duration/duration_init.go | 9 + coreext/duration/io/Duration.io | 4 + file.go => coreext/file/file.go | 282 +++---- file_darwin.go => coreext/file/file_darwin.go | 25 +- coreext/file/file_init.go | 9 + file_js.go => coreext/file/file_js.go | 25 +- file_plan9.go => coreext/file/file_plan9.go | 23 +- file_unix.go => coreext/file/file_unix.go | 27 +- .../file/file_windows.go | 17 +- {io => coreext/file/io}/07_File.io | 0 future.go => coreext/future/future.go | 86 +- coreext/future/future_init.go | 9 + coreext/future/future_test.go | 43 + coreext/future/io/Future.io | 4 + {io => coreext/path/io}/09_Path.io | 0 coreext/path/path.go | 55 ++ coreext/path/path_init.go | 9 + {io => coreext/unittest/io}/99_UnitTest.io | 0 coreext/unittest/unittest.go | 21 + coreext/unittest/unittest_init.go | 9 + coroutine.go | 135 ---- debugger.go | 119 --- go.mod | 8 +- go.sum | 18 + addon.go => internal/addon.go | 173 ++-- block.go => internal/block.go | 2 +- bool.go => internal/bool.go | 2 +- cfunction.go => internal/cfunction.go | 2 +- control.go => internal/control.go | 2 +- internal/coroutine.go | 74 ++ internal/debugger.go | 70 ++ exception.go => internal/exception.go | 2 +- {io => internal/io}/00_Object.io | 6 +- {io => internal/io}/01_Call.io | 0 {io => internal/io}/02_Sequence.io | 0 {io => internal/io}/04_Exception.io | 0 {io => internal/io}/05_Number.io | 0 {io => internal/io}/06_List.io | 0 {io => internal/io}/10_Map.io | 0 {io => internal/io}/12_Block.io | 0 {io => internal/io}/13_Message.io | 0 {io => internal/io}/14_OperatorTable.io | 0 {io => internal/io}/16_System.io | 0 {io => internal/io}/17_Stop.io | 0 lex.go => internal/lex.go | 2 +- lex_test.go => internal/lex_test.go | 2 +- list.go => internal/list.go | 2 +- map.go => internal/map.go | 2 +- message.go => internal/message.go | 2 +- message_test.go => internal/message_test.go | 73 +- number.go => internal/number.go | 2 +- number_test.go => internal/number_test.go | 6 +- object.go => internal/object.go | 27 +- internal/object_test.go | 703 +++++++++++++++++ optable.go => internal/optable.go | 2 +- optable_test.go => internal/optable_test.go | 29 +- parse.go => internal/parse.go | 2 +- parse_test.go => internal/parse_test.go | 10 +- scheduler.go => internal/scheduler.go | 20 +- sequence.go => internal/sequence.go | 2 +- .../sequence_immutable.go | 2 +- sequence_math.go => internal/sequence_math.go | 2 +- .../sequence_mutable.go | 2 +- .../sequence_string.go | 2 +- slots.go => internal/slots.go | 2 +- system.go => internal/system.go | 2 +- system_js.go => internal/system_js.go | 2 +- system_plan9.go => internal/system_plan9.go | 2 +- system_unix.go => internal/system_unix.go | 2 +- .../system_windows.go | 2 +- vm.go => internal/vm.go | 60 +- vm_init.go => internal/vm_init.go | 14 +- vm_test.go => internal/vm_test.go | 60 +- doc.go => iolang.go | 134 +++- object_test.go | 744 ------------------ path.go | 44 -- testutils/testutils.go | 24 +- testutils_test.go | 262 ------ 103 files changed, 2474 insertions(+), 2110 deletions(-) create mode 100644 coreext/README.md create mode 100644 coreext/addon/addon.go create mode 100644 coreext/addon/addon_init.go rename io/15_Addon.io => coreext/addon/io/Addon.io (100%) rename collector.go => coreext/collector/collector.go (66%) create mode 100644 coreext/coreext.go create mode 100644 coreext/coroutine/coroutine.go create mode 100644 coreext/coroutine/coroutine_init.go create mode 100644 coreext/coroutine/coroutine_test.go rename io/03_Coroutine.io => coreext/coroutine/io/Coroutine.io (91%) rename date.go => coreext/date/date.go (57%) create mode 100644 coreext/date/date_init.go rename io/11_Date.io => coreext/date/io/Date.io (89%) create mode 100644 coreext/debugger/debugger.go create mode 100644 coreext/debugger/debugger_init.go rename io/98_Debugger.io => coreext/debugger/io/Debugger.io (100%) rename directory.go => coreext/directory/directory.go (59%) create mode 100644 coreext/directory/directory_init.go rename {io => coreext/directory/io}/08_Directory.io (100%) rename duration.go => coreext/duration/duration.go (60%) create mode 100644 coreext/duration/duration_init.go create mode 100644 coreext/duration/io/Duration.io rename file.go => coreext/file/file.go (69%) rename file_darwin.go => coreext/file/file_darwin.go (65%) create mode 100644 coreext/file/file_init.go rename file_js.go => coreext/file/file_js.go (52%) rename file_plan9.go => coreext/file/file_plan9.go (59%) rename file_unix.go => coreext/file/file_unix.go (66%) rename file_windows.go => coreext/file/file_windows.go (72%) rename {io => coreext/file/io}/07_File.io (100%) rename future.go => coreext/future/future.go (67%) create mode 100644 coreext/future/future_init.go create mode 100644 coreext/future/future_test.go create mode 100644 coreext/future/io/Future.io rename {io => coreext/path/io}/09_Path.io (100%) create mode 100644 coreext/path/path.go create mode 100644 coreext/path/path_init.go rename {io => coreext/unittest/io}/99_UnitTest.io (100%) create mode 100644 coreext/unittest/unittest.go create mode 100644 coreext/unittest/unittest_init.go delete mode 100644 coroutine.go delete mode 100644 debugger.go rename addon.go => internal/addon.go (71%) rename block.go => internal/block.go (99%) rename bool.go => internal/bool.go (98%) rename cfunction.go => internal/cfunction.go (99%) rename control.go => internal/control.go (99%) create mode 100644 internal/coroutine.go create mode 100644 internal/debugger.go rename exception.go => internal/exception.go (99%) rename {io => internal/io}/00_Object.io (96%) rename {io => internal/io}/01_Call.io (100%) rename {io => internal/io}/02_Sequence.io (100%) rename {io => internal/io}/04_Exception.io (100%) rename {io => internal/io}/05_Number.io (100%) rename {io => internal/io}/06_List.io (100%) rename {io => internal/io}/10_Map.io (100%) rename {io => internal/io}/12_Block.io (100%) rename {io => internal/io}/13_Message.io (100%) rename {io => internal/io}/14_OperatorTable.io (100%) rename {io => internal/io}/16_System.io (100%) rename {io => internal/io}/17_Stop.io (100%) rename lex.go => internal/lex.go (99%) rename lex_test.go => internal/lex_test.go (99%) rename list.go => internal/list.go (99%) rename map.go => internal/map.go (99%) rename message.go => internal/message.go (99%) rename message_test.go => internal/message_test.go (74%) rename number.go => internal/number.go (99%) rename number_test.go => internal/number_test.go (87%) rename object.go => internal/object.go (98%) create mode 100644 internal/object_test.go rename optable.go => internal/optable.go (99%) rename optable_test.go => internal/optable_test.go (87%) rename parse.go => internal/parse.go (99%) rename parse_test.go => internal/parse_test.go (96%) rename scheduler.go => internal/scheduler.go (92%) rename sequence.go => internal/sequence.go (99%) rename sequence_immutable.go => internal/sequence_immutable.go (99%) rename sequence_math.go => internal/sequence_math.go (99%) rename sequence_mutable.go => internal/sequence_mutable.go (99%) rename sequence_string.go => internal/sequence_string.go (99%) rename slots.go => internal/slots.go (99%) rename system.go => internal/system.go (99%) rename system_js.go => internal/system_js.go (83%) rename system_plan9.go => internal/system_plan9.go (80%) rename system_unix.go => internal/system_unix.go (97%) rename system_windows.go => internal/system_windows.go (98%) rename vm.go => internal/vm.go (81%) rename vm_init.go => internal/vm_init.go (53%) rename vm_test.go => internal/vm_test.go (72%) rename doc.go => iolang.go (58%) delete mode 100644 object_test.go delete mode 100644 path.go delete mode 100644 testutils_test.go diff --git a/addons/Range/Range/main.go b/addons/Range/Range/main.go index 3063bcc..c3ff8fb 100644 --- a/addons/Range/Range/main.go +++ b/addons/Range/Range/main.go @@ -1,11 +1,11 @@ package main import ( - "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/coreext/addon" "github.com/zephyrtronium/iolang/addons/Range" ) // IoAddon returns an object to load the addon. -func IoAddon() iolang.Addon { +func IoAddon() addon.Interface { return Range.IoAddon() } diff --git a/addons/Range/addon.go b/addons/Range/addon.go index 1d55abe..d2870cc 100644 --- a/addons/Range/addon.go +++ b/addons/Range/addon.go @@ -7,10 +7,11 @@ import ( "compress/zlib" "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/coreext/addon" ) // IoAddon returns a loader for the Range addon. -func IoAddon() iolang.Addon { +func IoAddon() addon.Interface { return addonRange{} } diff --git a/cmd/gencore/gencore.go b/cmd/gencore/gencore.go index e40554f..e7d686b 100644 --- a/cmd/gencore/gencore.go +++ b/cmd/gencore/gencore.go @@ -11,8 +11,8 @@ import ( ) func main() { - if len(os.Args) < 3 { - fmt.Fprintln(os.Stderr, os.Args[0], "output.go iofiles/ iofiles/ ...") + if len(os.Args) < 4 { + fmt.Fprintln(os.Stderr, os.Args[0], "output.go package iofiles/ iofiles/ ...") os.Exit(1) } out, err := os.Create(os.Args[1]) @@ -20,11 +20,11 @@ func main() { panic(err) } defer out.Close() - if _, err = out.WriteString("package iolang\n\n// Code generated by gencore; DO NOT EDIT\n\nvar coreIo = []string{\n"); err != nil { + if _, err = out.WriteString("package " + os.Args[2] + "\n\n// Code generated by gencore; DO NOT EDIT\n\nvar coreIo = []string{\n"); err != nil { panic(err) } var paths []string - for _, dir := range os.Args[2:] { + for _, dir := range os.Args[3:] { fis, err := ioutil.ReadDir(dir) if err != nil { panic(err) diff --git a/cmd/io/main.go b/cmd/io/main.go index be51762..009db40 100644 --- a/cmd/io/main.go +++ b/cmd/io/main.go @@ -6,6 +6,8 @@ import ( "os" "github.com/zephyrtronium/iolang" + // import for side effects + _ "github.com/zephyrtronium/iolang/coreext" "runtime" "runtime/pprof" diff --git a/cmd/mkaddon/mkaddon.go b/cmd/mkaddon/mkaddon.go index ac8b360..e16acc9 100644 --- a/cmd/mkaddon/mkaddon.go +++ b/cmd/mkaddon/mkaddon.go @@ -163,10 +163,11 @@ import ( "compress/zlib" "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/coreext/addon" ) // IoAddon returns a loader for the {{.Addon}} addon. -func IoAddon() iolang.Addon { +func IoAddon() addon.Interface { return addon{{.Addon}}{} } @@ -224,12 +225,12 @@ var plugin = template.Must(template.New("plugin").Parse(pluginsource)) const pluginsource = `package main import ( - "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/coreext/addon" {{printf "%q" .Import}} ) // IoAddon returns an object to load the addon. -func IoAddon() iolang.Addon { +func IoAddon() addon.Interface { return {{.Addon}}.IoAddon() } ` diff --git a/coreext/README.md b/coreext/README.md new file mode 100644 index 0000000..e499c51 --- /dev/null +++ b/coreext/README.md @@ -0,0 +1,5 @@ +Package coreext sets up "optional" components of the iolang core. + +These packages may be imported for side effects only in order to make their functionality available on all VMs. Many, but not all, provide additional functionality (e.g. type definitions and constructors) that may be useful for adding values when embedding or for developing addons. + +Importing package coreext directly transitively sets up all core extensions. diff --git a/coreext/addon/addon.go b/coreext/addon/addon.go new file mode 100644 index 0000000..65b39af --- /dev/null +++ b/coreext/addon/addon.go @@ -0,0 +1,117 @@ +//go:generate go run ../../cmd/gencore addon_init.go addon ./io +//go:generate gofmt -s -w addon_init.go + +package addon + +import ( + "os" + "plugin" + "sync" + + "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/internal" + + // importing for side effects + _ "github.com/zephyrtronium/iolang/coreext/directory" +) + +// Interface is the interface via which an addon is loaded into a VM. +// +// Addons in iolang are separate packages, which may be linked dynamically (on +// platforms supporting -buildmode=plugin) or statically. The addon is loaded +// by calling its IoAddon function, which must be of type func() Interface. This +// function will be called no more than once per interpreter. The plugin itself +// is opened only once per program, so its init functions may run less than +// once per time it is added to an interpreter. +// +// For dynamically loaded addons, the Io program's Importer uses a CFunction to +// lookup the IoAddon function in the plugin when needed. For statically +// linked addons, however, the program which creates the VM must manually load +// all addons using the VM's LoadAddon method. +type Interface = internal.Addon + +func init() { + internal.Register(initAddon) +} + +func initAddon(vm *iolang.VM) { + addonOnce.Do(initAddonOnce) + internal.InitAddon(vm) + slots := iolang.Slots{ + "havePlugins": vm.IoBool(havePlugins), + "open": vm.NewCFunction(open, nil), + "scanForNewAddons": vm.NewCFunction(scanForNewAddons, nil), + "type": vm.NewString("Addon"), + } + internal.CoreInstall(vm, "Addon", slots, nil, nil) + internal.Ioz(vm, coreIo, coreFiles) +} + +// addonOpen is an Addon method. +// +// open loads the addon at the receiver's path and returns the addon's object. +func open(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { + p, proto := vm.GetSlot(target, "path") + if proto == nil { + return vm.RaiseExceptionf("addon path unset") + } + p.Lock() + path, ok := p.Value.(iolang.Sequence) + if !ok { + p.Unlock() + return vm.RaiseExceptionf("addon path must be Sequence, not %s", vm.TypeName(p)) + } + plug, err := plugin.Open(path.String()) + p.Unlock() + if err != nil { + return vm.IoError(err) + } + open, err := plug.Lookup("IoAddon") + if err != nil { + return vm.IoError(err) + } + f, ok := open.(func() Interface) + if !ok { + return vm.RaiseExceptionf("%s is not an iolang addon", path) + } + <-vm.LoadAddon(f()) + return target +} + +// addonScanForNewAddons is an Addon method. +// +// scanForNewAddons marks a directory to be scanned for new addons. +func scanForNewAddons(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { + path, exc, stop := msg.StringArgAt(vm, locals, 0) + if stop != iolang.NoStop { + return vm.Stop(exc, stop) + } + file, err := os.Open(path) + if err != nil { + return vm.IoError(err) + } + fi, err := file.Stat() + if err != nil { + return vm.IoError(err) + } + if !fi.IsDir() { + return vm.RaiseExceptionf("%s is not a directory", path) + } + internal.ScanForAddons(vm, file) + return target +} + +// havePlugins indicates whether Go's plugin system is available on the current +// system. Currently this should become true on Linux or Darwin with cgo +// enabled, but the cgo requirement might drop in the future (unlikely) and more +// platforms might be added (likely). +var havePlugins = false + +func initAddonOnce() { + _, err := plugin.Open("/dev/null") + if err == nil || err.Error() != "plugin: not implemented" { + havePlugins = true + } +} + +var addonOnce sync.Once diff --git a/coreext/addon/addon_init.go b/coreext/addon/addon_init.go new file mode 100644 index 0000000..1b2f9ca --- /dev/null +++ b/coreext/addon/addon_init.go @@ -0,0 +1,9 @@ +package addon + +// Code generated by gencore; DO NOT EDIT + +var coreIo = []string{ + "x\x9c\xa4RM\x8b\xdb0\x10=[\xbfb\xd0ɂ\xb0\xbe\xa7\xe4\xb0P\x97.\xb4I m\xef\x13i\x1cO+KF\x1a\xecl\u007f}\x91\x93.\r\x84v\xe9\x1eg\u07bc\x0f[O\xd8;j\xcf#\x06\x97\xbfDXo\x80\xbb\xfa\xf0\x9c\x85\x06\x18=J\x17\xd3\x00\x9b\r虃\x8bs\xd6+U\r$}t\xbf\xafN$m\x988\xc50P\x90o\x98\x18\x8f\x9ej\xfd5Sڧر'm\x8cz5\xef\xe3\xees[\bF\xa9G\xe7b\x00\x17kU\x8d(=\xac\xd7\x1b\xd0\xfa\x05\xf9\x14\xd1Q*\x99w\xc7\xefd\x05\xac\x8f\x81.\xf7\x1cX8\x9c\xee\x81<\x96\xed5\x05\x87,\xe8\xfd>Q\xc7\xe7w T\xb0۟bT\x95\t\x93\xed\xf7(}.\xb8\xe7,\xb5\xaa^Lx\x04\xee\xb61lٷ\x13\xfa\xfa\x8f\xfd\xe2\xfa8\x8e\x14\\\xa1\u05fa!JL\xcd\x1135X\xbe\"k\x03\x98w\x87\x02\x9b\xd5\xdbT\xd1\nOԆ\xe9\x1f\xd2B\xf7\xa5\x85\xeeH?\xbc:\xf1\xff\xc8\xfe-\xb2\xaa\fd\xf2d\xe5)\xec=Z\xaaK\rV\xb0\x94\xe1\xc6\xe9='\xb2\x12\xd33\xcc,\xfdrf\x80Μ%\x1b\xa3\xaaDC\x9c\xe8\xe0\xa3\xd4\xfa\x1aJ\x1bU5\r<\x95\t=\xff$X\xfc\xe1G\x88\xb3'w\xa2\x87\xdbg\xefb\"\xb4\xfd5\xc1\xa5\x99\xd9b\xf8\x10Ӗ\xe6e\xce\x17\xdfR\xdd_\x01\x00\x00\xff\xff`8#d", +} + +var coreFiles = []string{"io/Addon.io"} diff --git a/io/15_Addon.io b/coreext/addon/io/Addon.io similarity index 100% rename from io/15_Addon.io rename to coreext/addon/io/Addon.io diff --git a/collector.go b/coreext/collector/collector.go similarity index 66% rename from collector.go rename to coreext/collector/collector.go index 02b6b0c..5f75b37 100644 --- a/collector.go +++ b/coreext/collector/collector.go @@ -1,31 +1,38 @@ -package iolang +package collector import ( "fmt" "runtime" "time" + + "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/internal" ) // Io has its own mark-and-sweep garbage collector that has more features than // Go's. Because of this, the Collector interface is very different. -func (vm *VM) initCollector() { - slots := Slots{ - "collect": vm.NewCFunction(CollectorCollect, nil), - "showStats": vm.NewCFunction(CollectorShowStats, nil), - "timeUsed": vm.NewCFunction(CollectorTimeUsed, nil), +func init() { + internal.Register(initCollector) +} + +func initCollector(vm *iolang.VM) { + slots := iolang.Slots{ + "collect": vm.NewCFunction(collectorCollect, nil), + "showStats": vm.NewCFunction(collectorShowStats, nil), + "timeUsed": vm.NewCFunction(collectorTimeUsed, nil), "type": vm.NewString("Collector"), } - vm.coreInstall("Collector", slots, nil, nil) + internal.CoreInstall(vm, "Collector", slots, nil, nil) } -// CollectorCollect is a Collector method. +// collectorCollect is a Collector method. // // collect triggers a garbage collection cycle and returns the number of // objects collected program-wide (not only in the Io VM). This is much slower // than allowing collection to happen automatically, as the GC statistics must // be recorded twice to retrieve the freed object count. -func CollectorCollect(vm *VM, target, locals *Object, msg *Message) *Object { +func collectorCollect(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { var stats runtime.MemStats runtime.ReadMemStats(&stats) old := stats.Frees @@ -34,10 +41,10 @@ func CollectorCollect(vm *VM, target, locals *Object, msg *Message) *Object { return vm.NewNumber(float64(stats.Frees - old)) } -// CollectorShowStats is a Collector method. +// collectorShowStats is a Collector method. // // showStats prints detailed garbage collector information to standard output. -func CollectorShowStats(vm *VM, target, locals *Object, msg *Message) *Object { +func collectorShowStats(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { var s runtime.MemStats runtime.ReadMemStats(&s) if s.NumGC > 0 { @@ -61,11 +68,11 @@ func CollectorShowStats(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// CollectorTimeUsed is a Collector method. +// collectorTimeUsed is a Collector method. // // timeUsed reports the number of seconds spent in stop-the-world garbage // collection. -func CollectorTimeUsed(vm *VM, target, locals *Object, msg *Message) *Object { +func collectorTimeUsed(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { var stats runtime.MemStats runtime.ReadMemStats(&stats) return vm.NewNumber(float64(stats.PauseTotalNs) / 1e9) diff --git a/coreext/coreext.go b/coreext/coreext.go new file mode 100644 index 0000000..becf0cb --- /dev/null +++ b/coreext/coreext.go @@ -0,0 +1,16 @@ +package coreext + +import ( + // importing for side effects + _ "github.com/zephyrtronium/iolang/coreext/addon" + _ "github.com/zephyrtronium/iolang/coreext/collector" + _ "github.com/zephyrtronium/iolang/coreext/coroutine" + _ "github.com/zephyrtronium/iolang/coreext/date" + _ "github.com/zephyrtronium/iolang/coreext/debugger" + _ "github.com/zephyrtronium/iolang/coreext/directory" + _ "github.com/zephyrtronium/iolang/coreext/duration" + _ "github.com/zephyrtronium/iolang/coreext/file" + _ "github.com/zephyrtronium/iolang/coreext/future" + _ "github.com/zephyrtronium/iolang/coreext/path" + _ "github.com/zephyrtronium/iolang/coreext/unittest" +) diff --git a/coreext/coroutine/coroutine.go b/coreext/coroutine/coroutine.go new file mode 100644 index 0000000..ac44937 --- /dev/null +++ b/coreext/coroutine/coroutine.go @@ -0,0 +1,90 @@ +//go:generate go run ../../cmd/gencore coroutine_init.go coroutine ./io +//go:generate gofmt -s -w coroutine_init.go + +package coroutine + +import ( + "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/internal" +) + +// A Coroutine holds control flow and debugging for a single Io coroutine. +type Coroutine = internal.Coroutine + +// CoroutineTag is the Tag for Coroutine objects. Activate returns the +// coroutine. CloneValue creates a new control flow channel and no debugging. +var CoroutineTag = internal.CoroutineTag + +func init() { + internal.Register(initCoroutine) +} + +func initCoroutine(vm *iolang.VM) { + slots := iolang.Slots{ + "currentCoroutine": vm.NewCFunction(coroutineCurrentCoroutine, nil), + "implementation": vm.NewString("goroutines"), + "implementationVersion": vm.NewNumber(0), // in case API changes + "isCurrent": vm.NewCFunction(coroutineIsCurrent, CoroutineTag), + "pause": vm.NewCFunction(coroutinePause, CoroutineTag), + "resume": vm.NewCFunction(coroutineResume, CoroutineTag), + "run": vm.NewCFunction(coroutineRun, CoroutineTag), + "type": vm.NewString("Coroutine"), + "yield": vm.NewCFunction(coroutineYield, CoroutineTag), + } + slots["resumeLater"] = slots["resume"] + vm.SetSlots(vm.Coro, slots) + vm.SetSlot(vm.Core, "Coroutine", vm.Coro) + internal.Ioz(vm, coreIo, coreFiles) +} + +// coroutineCurrentCoroutine is a Coroutine method. +// +// currentCoroutine returns the current coroutine. +func coroutineCurrentCoroutine(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { + return vm.Coro +} + +// coroutineIsCurrent is a Coroutine method. +// +// isCurrent returns whether the receiver is the current coroutine. +func coroutineIsCurrent(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { + return vm.IoBool(vm.Coro == target) +} + +// coroutinePause is a Coroutine method. +// +// pause stops the coroutine's execution until it is sent the resume message. It +// will finish evaluating its current message before pausing. If all coroutines +// are paused, the program ends. +func coroutinePause(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { + target.Value.(Coroutine).Control <- iolang.RemoteStop{Control: internal.PauseStop} + return target +} + +// coroutineResume is a Coroutine method. +// +// resume unpauses the coroutine, or starts it if it was not started. +func coroutineResume(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { + target.Value.(Coroutine).Control <- iolang.RemoteStop{Control: internal.ResumeStop} + return target +} + +// coroutineRun is a Coroutine method. +// +// run starts this coroutine if it was not already running. The coroutine +// activates its main slot, which by default performs the message in runMessage +// upon runTarget using runLocals. +func coroutineRun(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { + coro := vm.VMFor(target) + vm.Sched.Start(coro) + go internal.RunCoro(coro) + return target +} + +// coroutineYield is a Coroutine method. +// +// yield reschedules all goroutines. +func coroutineYield(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { + target.Value.(Coroutine).Control <- iolang.RemoteStop{} + return target +} diff --git a/coreext/coroutine/coroutine_init.go b/coreext/coroutine/coroutine_init.go new file mode 100644 index 0000000..5d670c6 --- /dev/null +++ b/coreext/coroutine/coroutine_init.go @@ -0,0 +1,9 @@ +package coroutine + +// Code generated by gencore; DO NOT EDIT + +var coreIo = []string{ + "x\x9c\x9cTQ\x8b\xdb0\f~\xb6\u007f\x85\xf0S\f\xe1\xb8\xe7B\a\xe3Fa\xd0\xe3`W\x18{\x1a\xbeD\x97x\xb8Vg\xcb\xd7m\xbf~\xd8I\x9b&\x14\xd6\xddS[\xf9\xd3\xf7I\x9fT=7=\xb6\xc9a\x80\x96*)\x9a\x14\x02z~\xa0@\x89\xadGX\xada\xfa\xd1!?;\xe2J-aJKq4\x967\x14r,\xee\xe8\x81\xf6\a\x87\\\b\xf6\xc8=\xb5ձ\xb7\x0e\xab\xdf\x16]k}Wp\x10\xed\x1f\x84\x0fp_C\x89k-\xb5\x94\x93`\xa9\xe9`\x16%\xad\xd6\u0b53\"$\xff\x881\x9an\x1eۙ\xd0!\xcfB[j\x8c\x8bS\b\u007f5x`K\xfe\x02\x851\xb9)K\x8ay\xa1\xab5LV\x9d}\x98a\x94\x96R8\xf3\x82\xee\xa2\xe9\x88\xee\x15\x92\xb7?\x13~n\xb5\x14\x11y\xbb\x84\xd4PPC\xea\x1a\"\xdc݁\xfa\xae\xf2\xc7\"]\x8a\xd8\xd3\xf1ۨ:\xe3\x90Bl\xacC\x88l|kB\xfb\x94\xf8\x90\x18\x8e\xc12:_)\x00P\xf5\xa0Q\x83\xcaߣ\x96b\xd1\xe5+\x054M_\xbd\xd5\xf0/\xb2\xcc\xf06\xf0i-E\xae\xcd\xc6+\x95\xcd\x05\x1a\xf2l\xac\x8f\xc5\x17\x9d\x93\xf6\xc6\xfa\x99_\xfc\xa5Lbp\xee\xec\xf4y\xaeJCK\xe3ثi\x03F\x0f/\xf1\xc3Е\xd6\xc3V=\xbd\xfc\xc0\x86\xc75\xa7@\x9f\xe8B\xb61\xceA\x8b\x0e;ø\xa3\xc7ix5\xa8\x8c\xdePP\x1aB\xf2\xfa\x94\xbd5\x8c\xe1\u007f(\xbeZ\xeeg\x1c\x1b\xba̟\x16\xbcq\xe4\x11\xb2\x13\xa7\xa6\a\xf2\x88\xbeŠǗ\xa1\xbdk/'wʓ\t\xddG\xae\xee\xb3\t\xe2Tŭ\xaaeH\uf5db\x9d\x93뚋;\xa2ǿ\xddM\xe0\xf1d\xe4\x03\x91\"ޖR\xa0y\x1d\xfe\x06\x00\x00\xff\xff.\x88\xaf0", +} + +var coreFiles = []string{"io/Coroutine.io"} diff --git a/coreext/coroutine/coroutine_test.go b/coreext/coroutine/coroutine_test.go new file mode 100644 index 0000000..a397d75 --- /dev/null +++ b/coreext/coroutine/coroutine_test.go @@ -0,0 +1,85 @@ +package coroutine_test + +import ( + "testing" + + "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/coreext/coroutine" + "github.com/zephyrtronium/iolang/testutils" +) + +func TestObjectMethods(t *testing.T) { + vm := testutils.VM() + config := iolang.Slots{ + // coroWaitTime is the time in seconds that coros should wait while + // testing methods that spawn new coroutines. The new coroutines may + // take any amount of time to execute, as the VM does not wait for them + // to finish. + "coroWaitTime": vm.NewNumber(0.02), + } + vm.SetSlot(vm.Lobby, "testValues", vm.NewObject(config)) + cases := map[string]map[string]testutils.SourceTestCase{ + "coroDo": { + "spawns": {Source: `while(Scheduler coroCount > 0, yield); testValues coroDoSync := true; coroDo(while(testValues coroDoSync, yield)); wait(testValues coroWaitTime); testValues coroDoCoros := Scheduler coroCount; testValues coroDoSync = false; testValues coroDoCoros`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "sideEffects": {Source: `testValues coroDoSideEffect := 0; coroDo(testValues coroDoSideEffect := 1); wait(testValues coroWaitTime); testValues coroDoSideEffect`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + // TODO: test that this has the right target + }, + "coroDoLater": { + "spawns": {Source: `while(Scheduler coroCount > 0, yield); testValues coroDoLaterSync := true; coroDoLater(while(testValues coroDoLaterSync, yield)); wait(testValues coroWaitTime); testValues coroDoLaterCoros := Scheduler coroCount; testValues coroDoLaterSync = false; testValues coroDoLaterCoros`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "sideEffects": {Source: `testValues coroDoLaterSideEffect := 0; coroDoLater(testValues coroDoLaterSideEffect := 1); wait(testValues coroWaitTime); testValues coroDoLaterSideEffect`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + // TODO: test that this has the right target + }, + "coroFor": { + "noSpawns": {Source: `while(Scheduler coroCount > 0, yield); testValues coroForSync := true; coroFor(while(testValues coroForSync, yield)); wait(testValues coroWaitTime); testValues coroForCoros := Scheduler coroCount; testValues coroForSync = false; testValues coroForCoros`, Pass: testutils.PassEqual(vm.NewNumber(0))}, + "noSideEffects": {Source: `testValues coroForSideEffect := 0; coroFor(testValues coroForSideEffect := 1); wait(testValues coroWaitTime); testValues coroForSideEffect`, Pass: testutils.PassEqual(vm.NewNumber(0))}, + "type": {Source: `coroFor(nil)`, Pass: testutils.PassTag(coroutine.CoroutineTag)}, + "message": {Source: `coroFor(nil) runMessage name`, Pass: testutils.PassEqual(vm.NewString("nil"))}, + "target": {Source: `0 coroFor(nil) runTarget`, Pass: testutils.PassIdentical(vm.Lobby)}, + "locals": {Source: `0 coroFor(nil) runLocals`, Pass: testutils.PassIdentical(vm.Lobby)}, + }, + "coroWith": { + "noSpawns": {Source: `while(Scheduler coroCount > 0, yield); testValues coroWithSync := true; coroWith(while(testValues coroWithSync, yield)); wait(testValues coroWaitTime); testValues coroWithCoros := Scheduler coroCount; testValues coroWithSync = false; testValues coroWithCoros`, Pass: testutils.PassEqual(vm.NewNumber(0))}, + "noSideEffects": {Source: `testValues coroWithSideEffect := 0; coroWith(testValues coroWithSideEffect := 1); wait(testValues coroWaitTime); testValues coroWithSideEffect`, Pass: testutils.PassEqual(vm.NewNumber(0))}, + "type": {Source: `coroWith(nil)`, Pass: testutils.PassTag(coroutine.CoroutineTag)}, + "message": {Source: `coroWith(nil) runMessage name`, Pass: testutils.PassEqual(vm.NewString("nil"))}, + "target": {Source: `0 coroWith(nil) runTarget`, Pass: testutils.PassEqual(vm.NewNumber(0))}, + "locals": {Source: `0 coroWith(nil) runLocals`, Pass: testutils.PassIdentical(vm.Lobby)}, + }, + "currentCoro": { + "isCurrent": {Source: `currentCoro`, Pass: testutils.PassIdentical(vm.Coro)}, + }, + "pause": { + "pause": {Source: `testValues pauseValue := 0; testValues pauseCoro := coroDo(testValues pauseValue = 1; Object pause; testValues pauseValue = 2); while(testValues pauseValue == 0, yield); while(Scheduler coroCount > 0, yield); testValues pauseObs := testValues pauseValue; testValues pauseCoro resume; while(testValues pauseValue < 2, yield); testValues pauseObs`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + for name, s := range c { + t.Run(name, s.TestFunc("TestObjectMethods")) + } + }) + } + vm.RemoveSlot(vm.Lobby, "testValues") +} + +// TestPerformSynchronous tests that multiple coroutines updating the same slot +// see each others' changes. +func TestPerformSynchronous(t *testing.T) { + vm := testutils.VM() + vm.SetSlot(vm.Lobby, "testValue", vm.NewNumber(0)) + defer vm.RemoveSlot(vm.Lobby, "testValue") + r, stop := vm.DoString("100 repeat(coroDo(1000 repeat(testValue = testValue + 1))); wait(0.2); while(Scheduler coroCount > 0, yield)", "TestPerformSynchronous") + if stop != iolang.NoStop { + t.Errorf("%s (%v)", vm.AsString(r), stop) + } + x, ok := vm.GetLocalSlot(vm.Lobby, "testValue") + if !ok { + t.Fatal("no slot testValue on Lobby") + } + if x.Tag() != iolang.NumberTag { + t.Fatalf("Lobby testValue has tag %#v, not %#v", x.Tag(), iolang.NumberTag) + } + if x.Value.(float64) != 100000 { + t.Errorf("Lobby testValue has wrong value: want 100000, got %g", x.Value.(float64)) + } +} diff --git a/io/03_Coroutine.io b/coreext/coroutine/io/Coroutine.io similarity index 91% rename from io/03_Coroutine.io rename to coreext/coroutine/io/Coroutine.io index 167865a..4f540eb 100644 --- a/io/03_Coroutine.io +++ b/coreext/coroutine/io/Coroutine.io @@ -27,13 +27,12 @@ Coroutine do( ) Object do( - setSlot("@", getSlot("futureSend")) - setSlot("@@", getSlot("asyncSend")) - coroDo := method(call delegateToMethod(self, "coroFor") run) coroDoLater := method(call delegateToMethod(self, "coroWith") run) coroFor := method(Coroutine clone setRunTarget(call sender) setRunLocals(call sender) setRunMessage(call argAt(0))) coroWith := method(Coroutine clone setRunTarget(self) setRunLocals(call sender) setRunMessage(call argAt(0))) currentCoro := method(Coroutine currentCoroutine) + yield := method(Coroutine currentCoroutine yield) + pause := method(Coroutine currentCoroutine pause) ) diff --git a/date.go b/coreext/date/date.go similarity index 57% rename from date.go rename to coreext/date/date.go index 7d307a0..f4f9e20 100644 --- a/date.go +++ b/coreext/date/date.go @@ -1,81 +1,92 @@ -package iolang +//go:generate go run ../../cmd/gencore date_init.go date ./io +//go:generate gofmt -s -w date_init.go + +package date import ( "fmt" "math" "time" + "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/coreext/duration" + "github.com/zephyrtronium/iolang/internal" + "gitlab.com/variadico/lctime" ) // DateTag is the Tag for Date objects. -const DateTag = BasicTag("Date") +const DateTag = iolang.BasicTag("Date") -// NewDate creates a new Date object with the given time. -func (vm *VM) NewDate(date time.Time) *Object { +// New creates a new Date object with the given time. +func New(vm *iolang.VM, date time.Time) *iolang.Object { return vm.ObjectWith(nil, vm.CoreProto("Date"), date, DateTag) } -// DateArgAt evaluates the nth argument and returns it as a time.Time. If a +// ArgAt evaluates the nth argument and returns it as a time.Time. If a // stop occurs during evaluation, the time will be zero, and the stop status // and result will be returned. If the evaluated result is not a Date, the // result will be zero, and an exception will be returned with an // ExceptionStop. -func (m *Message) DateArgAt(vm *VM, locals *Object, n int) (time.Time, *Object, Stop) { +func ArgAt(vm *iolang.VM, m *iolang.Message, locals *iolang.Object, n int) (time.Time, *iolang.Object, iolang.Stop) { v, s := m.EvalArgAt(vm, locals, n) - if s == NoStop { + if s == iolang.NoStop { v.Lock() d, ok := v.Value.(time.Time) v.Unlock() if ok { - return d, nil, NoStop + return d, nil, iolang.NoStop } // Not the expected type, so return an error. v = vm.NewExceptionf("argument %d to %s must be Date, not %s", n, m.Text, vm.TypeName(v)) - s = ExceptionStop + s = iolang.ExceptionStop } return time.Time{}, v, s } -func (vm *VM) initDate() { - slots := Slots{ - "+=": vm.NewCFunction(DatePlusEq, DateTag), - "-": vm.NewCFunction(DateMinus, DateTag), - "-=": vm.NewCFunction(DateMinusEq, DateTag), - "asNumber": vm.NewCFunction(DateAsNumber, DateTag), - "asString": vm.NewCFunction(DateAsString, DateTag), - "clock": vm.NewCFunction(DateClock, nil), - "convertToLocal": vm.NewCFunction(DateConvertToLocal, DateTag), - "convertToLocation": vm.NewCFunction(DateConvertToLocation, DateTag), - "convertToUTC": vm.NewCFunction(DateConvertToUTC, DateTag), - "copy": vm.NewCFunction(DateCopy, DateTag), - "cpuSecondsToRun": vm.NewCFunction(DateCPUSecondsToRun, nil), - "day": vm.NewCFunction(DateDay, DateTag), - "fromNumber": vm.NewCFunction(DateFromNumber, DateTag), - "fromString": vm.NewCFunction(DateFromString, DateTag), - "gmtOffset": vm.NewCFunction(DateGmtOffset, DateTag), - "gmtOffsetSeconds": vm.NewCFunction(DateGmtOffsetSeconds, DateTag), - "hour": vm.NewCFunction(DateHour, DateTag), - "isDST": vm.NewCFunction(DateIsDST, DateTag), - "isPast": vm.NewCFunction(DateIsPast, DateTag), - "isValidTime": vm.NewCFunction(DateIsValidTime, nil), - "location": vm.NewCFunction(DateLocation, nil), - "minute": vm.NewCFunction(DateMinute, DateTag), - "month": vm.NewCFunction(DateMonth, DateTag), - "now": vm.NewCFunction(DateNow, DateTag), - "second": vm.NewCFunction(DateSecond, DateTag), - "secondsSince": vm.NewCFunction(DateSecondsSince, DateTag), - "secondsSinceNow": vm.NewCFunction(DateSecondsSinceNow, DateTag), - "setDay": vm.NewCFunction(DateSetDay, DateTag), - "setGmtOffset": vm.NewCFunction(DateSetGmtOffset, DateTag), - "setHour": vm.NewCFunction(DateSetHour, DateTag), - "setMinute": vm.NewCFunction(DateSetMinute, DateTag), - "setMonth": vm.NewCFunction(DateSetMonth, DateTag), - "setSecond": vm.NewCFunction(DateSetSecond, DateTag), - "setToUTC": vm.NewCFunction(DateSetToUTC, DateTag), - "setYear": vm.NewCFunction(DateSetYear, DateTag), +func init() { + internal.Register(initDate) +} + +func initDate(vm *iolang.VM) { + slots := iolang.Slots{ + "+=": vm.NewCFunction(plusEq, DateTag), + "-": vm.NewCFunction(minus, DateTag), + "-=": vm.NewCFunction(minusEq, DateTag), + "asNumber": vm.NewCFunction(asNumber, DateTag), + "asString": vm.NewCFunction(asString, DateTag), + "clock": vm.NewCFunction(clock, nil), + "convertToLocal": vm.NewCFunction(convertToLocal, DateTag), + "convertToLocation": vm.NewCFunction(convertToLocation, DateTag), + "convertToUTC": vm.NewCFunction(convertToUTC, DateTag), + "copy": vm.NewCFunction(dateCopy, DateTag), + "cpuSecondsToRun": vm.NewCFunction(cpuSecondsToRun, nil), + "day": vm.NewCFunction(day, DateTag), + "fromNumber": vm.NewCFunction(fromNumber, DateTag), + "fromString": vm.NewCFunction(fromString, DateTag), + "gmtOffset": vm.NewCFunction(gmtOffset, DateTag), + "gmtOffsetSeconds": vm.NewCFunction(gmtOffsetSeconds, DateTag), + "hour": vm.NewCFunction(hour, DateTag), + "isDST": vm.NewCFunction(isDST, DateTag), + "isPast": vm.NewCFunction(isPast, DateTag), + "isValidTime": vm.NewCFunction(isValidTime, nil), + "location": vm.NewCFunction(location, nil), + "minute": vm.NewCFunction(minute, DateTag), + "month": vm.NewCFunction(month, DateTag), + "now": vm.NewCFunction(now, DateTag), + "second": vm.NewCFunction(second, DateTag), + "secondsSince": vm.NewCFunction(secondsSince, DateTag), + "secondsSinceNow": vm.NewCFunction(secondsSinceNow, DateTag), + "setDay": vm.NewCFunction(setDay, DateTag), + "setGmtOffset": vm.NewCFunction(setGmtOffset, DateTag), + "setHour": vm.NewCFunction(setHour, DateTag), + "setMinute": vm.NewCFunction(setMinute, DateTag), + "setMonth": vm.NewCFunction(setMonth, DateTag), + "setSecond": vm.NewCFunction(setSecond, DateTag), + "setToUTC": vm.NewCFunction(setToUTC, DateTag), + "setYear": vm.NewCFunction(setYear, DateTag), "type": vm.NewString("Date"), - "year": vm.NewCFunction(DateYear, DateTag), + "year": vm.NewCFunction(year, DateTag), } // isDST and isDaylightSavingsTime are distinct in Io, but they seem to // serve the same purpose, with the former inspecting the struct timezone @@ -83,13 +94,14 @@ func (vm *VM) initDate() { // Since we don't have a forward-facing DST concept in Go, there isn't any // obvious reason to have them be distinct in this implementation. slots["isDaylightSavingsTime"] = slots["isDST"] - vm.coreInstall("Date", slots, time.Now(), DateTag) + internal.CoreInstall(vm, "Date", slots, time.Now(), DateTag) + internal.Ioz(vm, coreIo, coreFiles) } -// DateAsNumber is a Date method. +// asNumber is a Date method. // // asNumber converts the date into seconds since 1970-01-01 00:00:00 UTC. -func DateAsNumber(vm *VM, target, locals *Object, msg *Message) *Object { +func asNumber(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Time) target.Unlock() @@ -97,19 +109,19 @@ func DateAsNumber(vm *VM, target, locals *Object, msg *Message) *Object { return vm.NewNumber(float64(s) / 1e9) } -// DateAsString is a Date method. +// asString is a Date method. // // asString converts the date to a string representation using ANSI C datetime // formatting. See https://godoc.org/github.com/variadico/lctime for the full // list of supported directives. -func DateAsString(vm *VM, target, locals *Object, msg *Message) *Object { +func asString(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Time) target.Unlock() format := "%Y-%m-%d %H:%M:%S %Z" if len(msg.Args) > 0 { s, exc, stop := msg.StringArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } format = s @@ -117,18 +129,18 @@ func DateAsString(vm *VM, target, locals *Object, msg *Message) *Object { return vm.NewString(lctime.Strftime(format, d)) } -// DateClock is a Date method. +// clock is a Date method. // // clock returns the number of seconds since Io initialization as a Number. -func DateClock(vm *VM, target, locals *Object, msg *Message) *Object { +func clock(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { dur := time.Since(vm.StartTime) return vm.NewNumber(dur.Seconds()) } -// DateConvertToLocal is a Date method. +// convertToLocal is a Date method. // // convertToLocal converts the date to the local timezone. -func DateConvertToLocal(vm *VM, target, locals *Object, msg *Message) *Object { +func convertToLocal(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Time) target.Value = d.Local() @@ -136,16 +148,16 @@ func DateConvertToLocal(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DateConvertToLocation is a Date method. +// convertToLocation is a Date method. // // convertToLocation converts the time to have the given IANA Time Zone // database location, e.g. "America/New_York". See // https://golang.org/pkg/time/#LoadLocation for more information. -func DateConvertToLocation(vm *VM, target, locals *Object, msg *Message) *Object { +func convertToLocation(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { // I'm providing this as an alternative to Io's Date convertToZone, because // that would be a lot of effort to support and less consistent. s, exc, stop := msg.StringArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } loc, err := time.LoadLocation(s) @@ -159,10 +171,10 @@ func DateConvertToLocation(vm *VM, target, locals *Object, msg *Message) *Object return target } -// DateConvertToUTC is a Date method. +// convertToUTC is a Date method. // // convertToUTC converts the date to UTC. -func DateConvertToUTC(vm *VM, target, locals *Object, msg *Message) *Object { +func convertToUTC(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Time) target.Value = d.UTC() @@ -170,12 +182,12 @@ func DateConvertToUTC(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DateCopy is a Date method. +// dateCopy is a Date method. // // copy sets the receiver to the same date as the argument. -func DateCopy(vm *VM, target, locals *Object, msg *Message) *Object { - dd, exc, stop := msg.DateArgAt(vm, locals, 0) - if stop != NoStop { +func dateCopy(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { + dd, exc, stop := ArgAt(vm, msg, locals, 0) + if stop != iolang.NoStop { return vm.Stop(exc, stop) } target.Lock() @@ -184,37 +196,37 @@ func DateCopy(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DateCPUSecondsToRun is a Date method. +// cpuSecondsToRun is a Date method. // // cpuSecondsToRun returns the duration taken to evaluate its argument. -func DateCPUSecondsToRun(vm *VM, target, locals *Object, msg *Message) *Object { +func cpuSecondsToRun(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { m := msg.ArgAt(0) t := time.Now() r, stop := m.Eval(vm, locals) - if stop == ExceptionStop { + if stop == iolang.ExceptionStop { return vm.Stop(r, stop) } dur := time.Since(t) return vm.NewNumber(float64(dur) / 1e9) } -// DateDay is a Date method. +// day is a Date method. // // day returns the day of the month of the date. -func DateDay(vm *VM, target, locals *Object, msg *Message) *Object { +func day(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Time) target.Unlock() return vm.NewNumber(float64(d.Day())) } -// DateFromNumber is a Date method. +// fromNumber is a Date method. // // fromNumber sets the date to the date corresponding to the given number of // seconds since the Unix epoch. -func DateFromNumber(vm *VM, target, locals *Object, msg *Message) *Object { +func fromNumber(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { n, err, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(err, stop) } target.Lock() @@ -223,17 +235,17 @@ func DateFromNumber(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DateFromString is a Date method. +// fromString is a Date method. // // fromString creates a date from the given string representation. -func DateFromString(vm *VM, target, locals *Object, msg *Message) *Object { +func fromString(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { str, err, stop := msg.StringArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(err, stop) } format, err, stop := msg.StringArgAt(vm, locals, 1) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(err, stop) } @@ -252,10 +264,10 @@ func DateFromString(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DateGmtOffset is a Date method. +// gmtOffset is a Date method. // // gmtOffset returns the date's timezone offset to UTC as a string. -func DateGmtOffset(vm *VM, target, locals *Object, msg *Message) *Object { +func gmtOffset(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Time) target.Unlock() @@ -264,10 +276,10 @@ func DateGmtOffset(vm *VM, target, locals *Object, msg *Message) *Object { return vm.NewString(fmt.Sprintf("%+03d%02d", s/-3600, s/60%60)) } -// DateGmtOffsetSeconds is a Date method. +// gmtOffsetSeconds is a Date method. // // gmtOffsetSeconds returns the date's timezone offset to UTC in seconds. -func DateGmtOffsetSeconds(vm *VM, target, locals *Object, msg *Message) *Object { +func gmtOffsetSeconds(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Time) target.Unlock() @@ -275,20 +287,20 @@ func DateGmtOffsetSeconds(vm *VM, target, locals *Object, msg *Message) *Object return vm.NewNumber(-float64(s)) } -// DateHour is a Date method. +// hour is a Date method. // // hour returns the hour component of the date. -func DateHour(vm *VM, target, locals *Object, msg *Message) *Object { +func hour(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Time) target.Unlock() return vm.NewNumber(float64(d.Hour())) } -// DateIsDST is a Date method. +// isDST is a Date method. // // isDST returns whether the date is a daylight savings time. -func DateIsDST(vm *VM, target, locals *Object, msg *Message) *Object { +func isDST(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { // Go doesn't have anything like this explicitly, so what we can do instead // is create a new time six months before and see whether it has a larger // UTC difference. No idea whether this will actually work, though. :) @@ -309,31 +321,31 @@ func DateIsDST(vm *VM, target, locals *Object, msg *Message) *Object { return vm.IoBool(s1 > s2) } -// DateIsPast is a Date method. +// isPast is a Date method. // // isPast returns true if the date is in the past. -func DateIsPast(vm *VM, target, locals *Object, msg *Message) *Object { +func isPast(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Time) target.Unlock() return vm.IoBool(d.Before(time.Now())) } -// DateIsValidTime is a Date method. +// isValidTime is a Date method. // // isValidTime returns whether the given hour, minute, and second combination has // valid values for each component. -func DateIsValidTime(vm *VM, target, locals *Object, msg *Message) *Object { +func isValidTime(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { h, exc, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } m, exc, stop := msg.NumberArgAt(vm, locals, 1) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } s, exc, stop := msg.NumberArgAt(vm, locals, 2) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } if h < 0 { @@ -348,41 +360,41 @@ func DateIsValidTime(vm *VM, target, locals *Object, msg *Message) *Object { return vm.IoBool(h >= 0 && h < 24 && m >= 0 && m < 60 && s >= 0 && s < 60) } -// DateLocation is a Date method. +// location is a Date method. // // location returns the system's time location, either "Local" or "UTC". -func DateLocation(vm *VM, target, locals *Object, msg *Message) *Object { +func location(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { return vm.NewString(time.Local.String()) } -// DateMinus is a Date method. +// minus is a Date method. // // - produces a Date that is before the receiver by the given Duration, or // produces the Duration between the receiver and the given Date. -func DateMinus(vm *VM, target, locals *Object, msg *Message) *Object { +func minus(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Time) target.Unlock() r, stop := msg.EvalArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(r, stop) } switch dd := r.Value.(type) { case time.Time: - return vm.NewDuration(d.Sub(dd)) + return duration.New(vm, d.Sub(dd)) case time.Duration: - return vm.NewDate(d.Add(-dd)) + return New(vm, d.Add(-dd)) } return vm.RaiseExceptionf("argument 0 to - must be Date or Duration, not %s", vm.TypeName(r)) } -// DateMinusEq is a Date method. +// minusEq is a Date method. // // -= sets the receiver to the date that is before the receiver by the given // duration. -func DateMinusEq(vm *VM, target, locals *Object, msg *Message) *Object { - dur, exc, stop := msg.DurationArgAt(vm, locals, 0) - if stop != NoStop { +func minusEq(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { + dur, exc, stop := duration.ArgAt(vm, msg, locals, 0) + if stop != iolang.NoStop { return vm.Stop(exc, stop) } target.Lock() @@ -392,43 +404,43 @@ func DateMinusEq(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DateMinute is a Date method. +// minute is a Date method. // // minute returns the minute portion of the date. -func DateMinute(vm *VM, target, locals *Object, msg *Message) *Object { +func minute(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Time) target.Unlock() return vm.NewNumber(float64(d.Minute())) } -// DateMonth is a Date method. +// month is a Date method. // // month returns the month portion of the date. -func DateMonth(vm *VM, target, locals *Object, msg *Message) *Object { +func month(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Time) target.Unlock() return vm.NewNumber(float64(d.Month())) } -// DateNow is a Date method. +// now is a Date method. // // now sets the date to the current local time. -func DateNow(vm *VM, target, locals *Object, msg *Message) *Object { +func now(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() target.Value = time.Now() target.Unlock() return target } -// DatePlusEq is a Date method. +// plusEq is a Date method. // // += sets the receiver to the date that is after the receiver by the given // duration. -func DatePlusEq(vm *VM, target, locals *Object, msg *Message) *Object { - dur, exc, stop := msg.DurationArgAt(vm, locals, 0) - if stop != NoStop { +func plusEq(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { + dur, exc, stop := duration.ArgAt(vm, msg, locals, 0) + if stop != iolang.NoStop { return vm.Stop(exc, stop) } target.Lock() @@ -436,37 +448,37 @@ func DatePlusEq(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DateSecond is a Date method. +// second is a Date method. // // second returns the fractional number of seconds within the minute of the // date. -func DateSecond(vm *VM, target, locals *Object, msg *Message) *Object { +func second(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Time) target.Unlock() return vm.NewNumber(float64(d.Second()) + float64(d.Nanosecond())/1e9) } -// DateSecondsSince is a Date method. +// secondsSince is a Date method. // // secondsSince returns the number of seconds between the receiver and the // argument, i.e. receiver - argument. -func DateSecondsSince(vm *VM, target, locals *Object, msg *Message) *Object { +func secondsSince(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Time) target.Unlock() - dd, exc, stop := msg.DateArgAt(vm, locals, 0) - if stop != NoStop { + dd, exc, stop := ArgAt(vm, msg, locals, 0) + if stop != iolang.NoStop { return vm.Stop(exc, stop) } dur := d.Sub(dd) return vm.NewNumber(dur.Seconds()) } -// DateSecondsSinceNow is a Date method. +// secondsSinceNow is a Date method. // // secondsSinceNow returns the number of seconds between now and the receiver. -func DateSecondsSinceNow(vm *VM, target, locals *Object, msg *Message) *Object { +func secondsSinceNow(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Time) target.Unlock() @@ -474,12 +486,12 @@ func DateSecondsSinceNow(vm *VM, target, locals *Object, msg *Message) *Object { return vm.NewNumber(dur.Seconds()) } -// DateSetDay is a Date method. +// setDay is a Date method. // // setDay sets the day of the date. -func DateSetDay(vm *VM, target, locals *Object, msg *Message) *Object { +func setDay(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { n, err, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(err, stop) } target.Lock() @@ -489,13 +501,13 @@ func DateSetDay(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DateSetGmtOffset is a Date method. +// setGmtOffset is a Date method. // // setGmtOffset sets the timezone of the date to the given number of minutes // west of UTC. -func DateSetGmtOffset(vm *VM, target, locals *Object, msg *Message) *Object { +func setGmtOffset(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { n, err, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(err, stop) } sw := int(n * -60) @@ -513,12 +525,12 @@ func DateSetGmtOffset(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DateSetHour is a Date method. +// setHour is a Date method. // // setHour sets the hour of the date. -func DateSetHour(vm *VM, target, locals *Object, msg *Message) *Object { +func setHour(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { n, err, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(err, stop) } target.Lock() @@ -528,12 +540,12 @@ func DateSetHour(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DateSetMinute is a Date method. +// setMinute is a Date method. // // setMinute sets the minute of the date. -func DateSetMinute(vm *VM, target, locals *Object, msg *Message) *Object { +func setMinute(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { n, err, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(err, stop) } target.Lock() @@ -543,12 +555,12 @@ func DateSetMinute(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DateSetMonth is a Date method. +// setMonth is a Date method. // // setMonth sets the month of the date. -func DateSetMonth(vm *VM, target, locals *Object, msg *Message) *Object { +func setMonth(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { n, err, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(err, stop) } target.Lock() @@ -558,12 +570,12 @@ func DateSetMonth(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DateSetSecond is a Date method. +// setSecond is a Date method. // // setSecond sets the (fractional) second of the date. -func DateSetSecond(vm *VM, target, locals *Object, msg *Message) *Object { +func setSecond(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { n, err, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(err, stop) } s := int(n) @@ -575,10 +587,10 @@ func DateSetSecond(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DateSetToUTC is a Date method. +// setToUTC is a Date method. // // setToUTC sets the location of the date to UTC. -func DateSetToUTC(vm *VM, target, locals *Object, msg *Message) *Object { +func setToUTC(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Time) target.Value = time.Date(d.Year(), d.Month(), d.Day(), d.Hour(), d.Minute(), d.Second(), d.Nanosecond(), time.UTC) @@ -586,12 +598,12 @@ func DateSetToUTC(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DateSetYear is a Date method. +// setYear is a Date method. // // setYear sets the year of the date. -func DateSetYear(vm *VM, target, locals *Object, msg *Message) *Object { +func setYear(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { n, err, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(err, stop) } target.Lock() @@ -601,10 +613,10 @@ func DateSetYear(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DateYear is a Date method. +// year is a Date method. // // year returns the year of the date. -func DateYear(vm *VM, target, locals *Object, msg *Message) *Object { +func year(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Time) target.Unlock() diff --git a/coreext/date/date_init.go b/coreext/date/date_init.go new file mode 100644 index 0000000..a109daf --- /dev/null +++ b/coreext/date/date_init.go @@ -0,0 +1,9 @@ +package date + +// Code generated by gencore; DO NOT EDIT + +var coreIo = []string{ + "x\x9clRMo\xdb0\f=ǿ\x82\b\x10\xc0F\xd34\xd8e@\x81\x1c\x8a\xee\xd0\rkQ\xcc\xe9a\xbb\xb16\x1d\v\x93\xc9L\xa4j\xf8\xdf\x0f\x92\xd35\xddv\x92ď\xf7\xc8\xf7\xf4\t\x8d\xa0\x95\xb2X(Y\xed\xc5\xca\xe5\xc5r\r\x03Y/m\xd9ư\x06%\xdfA\xe3\x85\t.v\xd0\xc6PUE\xb1\xb8\xba\x82}O \xc1\x1d\x1c\xa3\a7\x1c=\rĆ\xe6\x84A:\xc8\xd0&-N\xa0\x86\xc1\x14Fg\xfd\x1cf\x19\xd70\xf6\xae\xe93\xd2@\xc8\n\xce`\x90\xd6u\x8e\x14\xac'\xb8\x95@s\xb9c5\xc26\xa1\xa6D\xa0\x86\xdc\v\x85\xcdfS,f\x86\xeb\xdd\xeb\xcc,#(ٝ\xc4Pn\xabt\xbdw\x1c\x8dN\x8f\x9a\x1a\xe1\xb6\xdcVU\xb1p\xba\xff\xab\xb9X,8=3\xeb\xbc3˘\xa3\x13a\x80\xddn>\x91[`\x18\x84\xadO\xb1\xf92\a\x13\xe0n\x97\x8ebq.\x14\xfb\t\x02\xa1\x87\xd6u\x1d\x05\xe2\x86\xe0\x99l$b\xd0<\x94\xee\xe5[\xe4\f\xd3\x1cc}\x1es\x9a\x81\xacG\xcb\nt\x12\x06\n\x10\xc8㔄SP\x93c\xd2٢\xaeA\x05\x90'h\x84-\x88\x87\xce\xcb\b\x8eS\xe7InU<\x10\x8c\xce{h0*%\xedM\xe0Wt\xf6*\xf6\x06>w0\x12\x8c\x14\x92\x8d0\xe0O\xca\xdc\x1e\xcd(d l,\xa2\xf7S2Pc \xb8}|\x02sü\xee:\xa1\x8e\x12}\v\xcf\x04\xf8gqKÆi\x93\xfe\xdcَ\xef\\\xb0\xff\xbaР\xf7@/\xe8o\xc2\xe1\xc6\xcam\x95+O(\xb5\xe3\x86\x1eR]6\xfa\x11Uk\x93\xa3\x96\x16\"%'PoL\x86\f\xfa\xc65\xc37\xc2/\x14l/O\xfb[@\xad-8>\x94\xcb\xd5\xf7\xcb\xd5p\xb9j\xf7\xab\xbb\xeb\xd5\xfd\xf5\xaa\xfe\xb1L\xff\x06\xf5\x8b\xca\xf9\xc0\xaf\x1d0gf\xb2\x878\x85\x95*n\xf6\x00_\xfa\x15\xdbRQP\abB\xa4\xc4F\x8e\x97\xf3#Њ\x86v\xf4ff\xe5\xcbV+\x1b\x16M\x10\x9e\xe3\xdcz?\xb1\x94#ϙ+\xe3\xdf?\x0e\xf6\xbb\x96\x04!\xa4\xf8\xe5?2E\\\x16\x9c\xdco8\xf6\xae\xe4\xc1\xef0R$|X\x13\xef\x02\x81 \x94}堦\x9b7\xf9\xdc\\\xd5t\xd54=\x9b\x13\x10\xbc\x02\x00\x00\xff\xffO\xdc1\xc7", +} + +var coreFiles = []string{"io/Debugger.io"} diff --git a/io/98_Debugger.io b/coreext/debugger/io/Debugger.io similarity index 100% rename from io/98_Debugger.io rename to coreext/debugger/io/Debugger.io diff --git a/directory.go b/coreext/directory/directory.go similarity index 59% rename from directory.go rename to coreext/directory/directory.go index 14b41de..32979d1 100644 --- a/directory.go +++ b/coreext/directory/directory.go @@ -1,63 +1,75 @@ -package iolang +//go:generate go run ../../cmd/gencore directory_init.go directory ./io +//go:generate gofmt -s -w directory_init.go + +package directory import ( "os" "path/filepath" + + "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/coreext/file" + "github.com/zephyrtronium/iolang/internal" ) // DirectoryTag is the Tag for Directory objects. -const DirectoryTag = BasicTag("Directory") +const DirectoryTag = iolang.BasicTag("Directory") -// NewDirectory creates a new Directory with the given path. -func (vm *VM) NewDirectory(path string) *Object { +// New creates a new Directory with the given path. +func New(vm *iolang.VM, path string) *iolang.Object { return vm.ObjectWith(nil, vm.CoreProto("Directory"), path, DirectoryTag) } -// DirectoryArgAt evaluates the nth argument and returns it as a string. If a +// ArgAt evaluates the nth argument and returns it as a string. If a // stop occurs during evaluation, the path will be empty, and the stop status // and result will be returned. If the evaluated result is not a Directory, the // result will be empty, and an exception will be returned with an // ExceptionStop. -func (m *Message) DirectoryArgAt(vm *VM, locals *Object, n int) (string, *Object, Stop) { +func ArgAt(vm *iolang.VM, m *iolang.Message, locals *iolang.Object, n int) (string, *iolang.Object, iolang.Stop) { v, s := m.EvalArgAt(vm, locals, n) - if s == NoStop { + if s == iolang.NoStop { v.Lock() d, ok := v.Value.(string) v.Unlock() if ok { - return d, nil, NoStop + return d, nil, iolang.NoStop } // Not the expected type, so return an error. v = vm.NewExceptionf("argument %d to %s must be Directory, not %s", n, m.Text, vm.TypeName(v)) - s = ExceptionStop + s = iolang.ExceptionStop } return "", v, s } -func (vm *VM) initDirectory() { - slots := Slots{ - "at": vm.NewCFunction(DirectoryAt, DirectoryTag), - "create": vm.NewCFunction(DirectoryCreate, DirectoryTag), - "createSubdirectory": vm.NewCFunction(DirectoryCreateSubdirectory, DirectoryTag), - "currentWorkingDirectory": vm.NewCFunction(DirectoryCurrentWorkingDirectory, nil), - "exists": vm.NewCFunction(DirectoryExists, DirectoryTag), - "items": vm.NewCFunction(DirectoryItems, DirectoryTag), - "name": vm.NewCFunction(DirectoryName, DirectoryTag), - "path": vm.NewCFunction(DirectoryPath, DirectoryTag), - "setCurrentWorkingDirectory": vm.NewCFunction(DirectorySetCurrentWorkingDirectory, nil), - "setPath": vm.NewCFunction(DirectorySetPath, nil), +func init() { + internal.Register(initDirectory) +} + +func initDirectory(vm *iolang.VM) { + slots := iolang.Slots{ + "at": vm.NewCFunction(at, DirectoryTag), + "create": vm.NewCFunction(create, DirectoryTag), + "createSubdirectory": vm.NewCFunction(createSubdirectory, DirectoryTag), + "currentWorkingDirectory": vm.NewCFunction(currentWorkingDirectory, nil), + "exists": vm.NewCFunction(exists, DirectoryTag), + "items": vm.NewCFunction(items, DirectoryTag), + "name": vm.NewCFunction(name, DirectoryTag), + "path": vm.NewCFunction(path, DirectoryTag), + "setCurrentWorkingDirectory": vm.NewCFunction(setCurrentWorkingDirectory, nil), + "setPath": vm.NewCFunction(setPath, nil), "type": vm.NewString("Directory"), } - vm.coreInstall("Directory", slots, "", DirectoryTag) + internal.CoreInstall(vm, "Directory", slots, "", DirectoryTag) + internal.Ioz(vm, coreIo, coreFiles) } // DirectoryAt is a Directory method. // // at returns a File or Directory object at the given path (always) relative to // the directory, or nil if there is no such file. -func DirectoryAt(vm *VM, target, locals *Object, msg *Message) *Object { +func at(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { s, exc, stop := msg.StringArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } target.Lock() @@ -72,15 +84,15 @@ func DirectoryAt(vm *VM, target, locals *Object, msg *Message) *Object { return vm.IoError(err) } if !fi.IsDir() { - return vm.NewFileAt(p) + return file.NewAt(vm, p) } - return vm.NewDirectory(p) + return New(vm, p) } // DirectoryCreate is a Directory method. // // create creates the directory if it does not exist. Returns nil on failure. -func DirectoryCreate(vm *VM, target, locals *Object, msg *Message) *Object { +func create(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(string) target.Unlock() @@ -101,9 +113,9 @@ func DirectoryCreate(vm *VM, target, locals *Object, msg *Message) *Object { // // createSubdirectory creates a subdirectory with the given name and returns a // Directory object for it. -func DirectoryCreateSubdirectory(vm *VM, target, locals *Object, msg *Message) *Object { +func createSubdirectory(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { nm, exc, stop := msg.StringArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } target.Lock() @@ -116,12 +128,12 @@ func DirectoryCreateSubdirectory(vm *VM, target, locals *Object, msg *Message) * if err = os.Mkdir(p, 0755); err != nil { return vm.IoError(err) } - return vm.NewDirectory(p) + return New(vm, p) } return vm.IoError(err) } if fi.IsDir() { - return vm.NewDirectory(p) + return New(vm, p) } return vm.RaiseExceptionf("%s already exists", p) } @@ -130,7 +142,7 @@ func DirectoryCreateSubdirectory(vm *VM, target, locals *Object, msg *Message) * // // currentWorkingDirectory returns the path of the current working directory // with the operating system's path style. -func DirectoryCurrentWorkingDirectory(vm *VM, target, locals *Object, msg *Message) *Object { +func currentWorkingDirectory(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { d, err := os.Getwd() if err != nil { return vm.NewString(".") @@ -141,7 +153,7 @@ func DirectoryCurrentWorkingDirectory(vm *VM, target, locals *Object, msg *Messa // DirectoryExists is a Directory method. // // exists returns true if the directory exists and is a directory. -func DirectoryExists(vm *VM, target, locals *Object, msg *Message) *Object { +func exists(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(string) target.Unlock() @@ -158,7 +170,7 @@ func DirectoryExists(vm *VM, target, locals *Object, msg *Message) *Object { // DirectoryItems is a Directory method. // // items returns a list of the files and directories within this directory. -func DirectoryItems(vm *VM, target, locals *Object, msg *Message) *Object { +func items(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(string) target.Unlock() @@ -171,13 +183,13 @@ func DirectoryItems(vm *VM, target, locals *Object, msg *Message) *Object { if err != nil { return vm.IoError(err) } - l := make([]*Object, len(fis)) + l := make([]*iolang.Object, len(fis)) for i, fi := range fis { p := filepath.Join(d, fi.Name()) if fi.IsDir() { - l[i] = vm.NewDirectory(p) + l[i] = New(vm, p) } else { - l[i] = vm.NewFileAt(p) + l[i] = file.NewAt(vm, p) } } return vm.NewList(l...) @@ -187,7 +199,7 @@ func DirectoryItems(vm *VM, target, locals *Object, msg *Message) *Object { // // name returns the name of the file or directory at the directory's path, // similar to Unix basename. -func DirectoryName(vm *VM, target, locals *Object, msg *Message) *Object { +func name(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(string) target.Unlock() @@ -197,7 +209,7 @@ func DirectoryName(vm *VM, target, locals *Object, msg *Message) *Object { // DirectoryPath is a Directory method. // // path returns the directory's path. -func DirectoryPath(vm *VM, target, locals *Object, msg *Message) *Object { +func path(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(string) target.Unlock() @@ -207,9 +219,9 @@ func DirectoryPath(vm *VM, target, locals *Object, msg *Message) *Object { // DirectorySetCurrentWorkingDirectory is a Directory method. // // setCurrentWorkingDirectory sets the program's current working directory. -func DirectorySetCurrentWorkingDirectory(vm *VM, target, locals *Object, msg *Message) *Object { +func setCurrentWorkingDirectory(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { s, exc, stop := msg.StringArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } if err := os.Chdir(s); err != nil { @@ -221,9 +233,9 @@ func DirectorySetCurrentWorkingDirectory(vm *VM, target, locals *Object, msg *Me // DirectorySetPath is a Directory method. // // setPath sets the path of the Directory object. -func DirectorySetPath(vm *VM, target, locals *Object, msg *Message) *Object { +func setPath(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { s, exc, stop := msg.StringArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } target.Lock() diff --git a/coreext/directory/directory_init.go b/coreext/directory/directory_init.go new file mode 100644 index 0000000..10cc4df --- /dev/null +++ b/coreext/directory/directory_init.go @@ -0,0 +1,9 @@ +package directory + +// Code generated by gencore; DO NOT EDIT + +var coreIo = []string{ + "x\x9c\xa4TMk\x1c1\f={~\x85\x9a\xd3\x18\x86\xd0sa[B\x9bB(M\x02\r\xf4\xec\x8c5]S\x8f=\xb5\xb5\xc9n\u007f}\x91<\xbb\U000d1924\xf4\xb2\xec\xd8ғ\xf4\xfc\xf4>\xb9\x84-\xc5t\x00\x1b\xebJ=:\xda»\r\xf4H\xdbh\xeb\xc1ж\x81\x8c\xbe\x83\xd6ǀ\x90\x91n\rm\xe5B\xeb\xaaR\xd9\xfd\xc6Y\x82\x84:\xc2>\x03\xdfpD\xe7<^\x9b\x1e\xed,,\x98\x1e\x9bJ\xa9\xcf\xce#p͚Q\xcb?\x81(\x859L\xebJ\xe9J\xd9c\x9f'\xa8.z\x8b\xe9%\xe4i\xae\xd7\xc0Wj0\t\x03MY\x13b\xa5\x94\xebd`\xd8l\xe0\xec\xfc\xac\x81\x84\xb4K\x01\x82\xf3\xbaRj\xe0`\xb9矏\xb1\x1fb\xc0@c\x1e\xb8|\xd9\x0fth`\x00I\xe7\x94\x11`\xd5\xe4PF5\xa1\xc5L1\x1do\x1d\xe6R\x81;\xcc\xcb\xce<\u007fz\x97\xb9\x9a\xd0\xc0\xe3UJ=n\x9d\xc7\xda\xc2\x06,\xacFk\xc0\x83\x19\x06\f\xb6\xb6<\xbd\xf2\x90\xf0\x01Sƫp\xebM\x8b\xa5\x89\xb6ŜݽNj\xb1\x1d\xa9<\x1d\xdf>\xd7\xces\xadg\xf4\xd8҈]\xbb|q\x82\x18\xa9\xf7\xb15\xfeJ4\xb3d\xbd\xc8h\x99N\xd87\"0y=x#\x94\x82\tv}(Dτ3\xb2XT\xf3o\x95\\\xfe₽\xe9\xea\x13\x87\xfa\xb9\x8a\u007fm\x83\xb7\xe05U\x8f\xa5x3\xf4\x94\xca2_\xa5\x17\xc4\xde\f\xc7\\\x91\xf3T컣\xed\xe5\x9e0d\x17\xc3,\x15\xf7\xd4\x14m➠\x8d\x81\x8c\v\xf9\x1b\xfe\xaaY\x9c\x10\"5\xc07e\xa4\xf3s\xfeЧz\xcbveN\fV\x8a1\x04ǎ\xcf\xda&4\x84W\xdd\xc5}\xc6@O\x16J\xf6\x10\xf7.S\x96\x9a\x95RO\x96\xf0\xc3\x12\x83C\x8a\x17\xc9q\xa5xVU\x14\xaf+\x95\xb0\x8f\x0f\xb8ڏI[]Lh\xdam]\xc2\xf4\xc2}N\xa6\xa0\xa1\\\xcfq\xf9\xfb.\xae]\xf1\xa5\xfc\x12]\frDY\xbafa\xe7\xd1\xf8\x9f\xcbV[\xe3=X\xf4\xf8\xc3\x10\xdeů\xe5\u2fd7\xa0\x81\xb3\xde\f\xe2:\xd6\x15\xe1\xcfV\xa2\xbc\x85\\\x88\x93\xbf\x87\xb7\r\xc8\xe7\x91.\xebR\x03\xab\xde\xf8P\x8bw\x04\xe7G\xee\xdb]\xca\xee\x01\x99\x93|\xd3\xdd\x1d\x86\x85^\xf3\xae\xeb\xdc\x1es\xb3\xf2,\xa6\xa1\xccÏ\xeb\xbaz\xb9p\xb2\x052\xd8\x11\x00,\x12\xb6T\xe79\x01G\x05\xd6Yk\x01R'\x8b\xe3 nT\xa4\"nW\xf8\x9f\xfb\xd0\xf2\x1d(\x1d\n\xeb\x1a\\w\xed|=\x9a5\xa5\x9d\xa8\xa63>\x8bG\xea\xeaO\x00\x00\x00\xff\xff\xb2\xdaF\x12", +} + +var coreFiles = []string{"io/08_Directory.io"} diff --git a/io/08_Directory.io b/coreext/directory/io/08_Directory.io similarity index 100% rename from io/08_Directory.io rename to coreext/directory/io/08_Directory.io diff --git a/duration.go b/coreext/duration/duration.go similarity index 60% rename from duration.go rename to coreext/duration/duration.go index 69846fa..4b8543f 100644 --- a/duration.go +++ b/coreext/duration/duration.go @@ -1,75 +1,86 @@ -package iolang +//go:generate go run ../../cmd/gencore duration_init.go duration ./io +//go:generate gofmt -s -w duration_init.go + +package duration import ( "fmt" "math" "strings" "time" + + "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/internal" ) // DurationTag is the Tag for Duration objects. -const DurationTag = BasicTag("Duration") +const DurationTag = iolang.BasicTag("Duration") -// NewDuration creates a new Duration object with the given duration. -func (vm *VM) NewDuration(d time.Duration) *Object { +// New creates a new Duration object with the given duration. +func New(vm *iolang.VM, d time.Duration) *iolang.Object { return vm.ObjectWith(nil, vm.CoreProto("Duration"), d, DurationTag) } -// DurationArgAt evaluates the nth argument and returns it as a time.Duration. +// ArgAt evaluates the nth argument and returns it as a time.Duration. // If a stop occurs during evaluation, the duration will be zero, and the stop // status and result will be returned. If the evaluated result is not a // Duration, the result will be zero, and an exception will be returned with an // ExceptionStop. -func (m *Message) DurationArgAt(vm *VM, locals *Object, n int) (time.Duration, *Object, Stop) { +func ArgAt(vm *iolang.VM, m *iolang.Message, locals *iolang.Object, n int) (time.Duration, *iolang.Object, iolang.Stop) { v, s := m.EvalArgAt(vm, locals, n) - if s == NoStop { + if s == iolang.NoStop { v.Lock() d, ok := v.Value.(time.Duration) v.Unlock() if ok { - return d, nil, NoStop + return d, nil, iolang.NoStop } // Not the expected type, so return an error. v = vm.NewExceptionf("argument %d to %s must be Duration, not %s", n, m.Text, vm.TypeName(v)) - s = ExceptionStop + s = iolang.ExceptionStop } return 0, v, s } -func (vm *VM) initDuration() { - slots := Slots{ - "+=": vm.NewCFunction(DurationPlusEq, DurationTag), - "-=": vm.NewCFunction(DurationMinusEq, DurationTag), - "asNumber": vm.NewCFunction(DurationAsNumber, DurationTag), - "asString": vm.NewCFunction(DurationAsString, DurationTag), - "days": vm.NewCFunction(DurationDays, DurationTag), - "fromNumber": vm.NewCFunction(DurationFromNumber, DurationTag), - "hours": vm.NewCFunction(DurationHours, DurationTag), - "minutes": vm.NewCFunction(DurationMinutes, DurationTag), - "seconds": vm.NewCFunction(DurationSeconds, DurationTag), - "setDays": vm.NewCFunction(DurationSetDays, DurationTag), - "setHours": vm.NewCFunction(DurationSetHours, DurationTag), - "setMinutes": vm.NewCFunction(DurationSetMinutes, DurationTag), - "setSeconds": vm.NewCFunction(DurationSetSeconds, DurationTag), - "setYears": vm.NewCFunction(DurationSetYears, DurationTag), +func init() { + internal.Register(initDuration) +} + +func initDuration(vm *iolang.VM) { + slots := iolang.Slots{ + "+=": vm.NewCFunction(plusEq, DurationTag), + "-=": vm.NewCFunction(minusEq, DurationTag), + "asNumber": vm.NewCFunction(asNumber, DurationTag), + "asString": vm.NewCFunction(asString, DurationTag), + "days": vm.NewCFunction(days, DurationTag), + "fromNumber": vm.NewCFunction(fromNumber, DurationTag), + "hours": vm.NewCFunction(hours, DurationTag), + "minutes": vm.NewCFunction(minutes, DurationTag), + "seconds": vm.NewCFunction(seconds, DurationTag), + "setDays": vm.NewCFunction(setDays, DurationTag), + "setHours": vm.NewCFunction(setHours, DurationTag), + "setMinutes": vm.NewCFunction(setMinutes, DurationTag), + "setSeconds": vm.NewCFunction(setSeconds, DurationTag), + "setYears": vm.NewCFunction(setYears, DurationTag), "type": vm.NewString("Duration"), - "years": vm.NewCFunction(DurationYears, DurationTag), + "years": vm.NewCFunction(years, DurationTag), } slots["totalSeconds"] = slots["asNumber"] - vm.coreInstall("Duration", slots, time.Duration(0), DurationTag) + internal.CoreInstall(vm, "Duration", slots, time.Duration(0), DurationTag) + internal.Ioz(vm, coreIo, coreFiles) } -// DurationAsNumber is a Duration method. +// asNumber is a Duration method. // // asNumber returns the duration as the number of seconds it represents. -func DurationAsNumber(vm *VM, target, locals *Object, msg *Message) *Object { +func asNumber(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Duration) target.Unlock() return vm.NewNumber(d.Seconds()) } -// DurationAsString is a Duration method. +// asString is a Duration method. // // asString formats the duration. The format may use the following directives: // @@ -83,13 +94,13 @@ func DurationAsNumber(vm *VM, target, locals *Object, msg *Message) *Object { // The default format is "%Y years %d days %H:%M:%S". Note that the definitions // of years and days never account for leap years or leap seconds, so it is // probably better to avoid them. -func DurationAsString(vm *VM, target, locals *Object, msg *Message) *Object { +func asString(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { // There's no way to escape % characters, and years and days are kinda // nonsense, but I guess it's easy to program. format := "%Y years %d days %H:%M:%S" if msg.ArgCount() > 0 { s, exc, stop := msg.StringArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } format = s @@ -111,23 +122,23 @@ func DurationAsString(vm *VM, target, locals *Object, msg *Message) *Object { return vm.NewString(rep.Replace(format)) } -// DurationDays is a Duration method. +// days is a Duration method. // // days returns the number of days represented by the duration, with a day // defined as 60*60*24 seconds, not including multiples of 365 days. -func DurationDays(vm *VM, target, locals *Object, msg *Message) *Object { +func days(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Duration) target.Unlock() return vm.NewNumber(float64(d / (60 * 60 * 24 * time.Second) % 365)) } -// DurationFromNumber is a Duration method. +// fromNumber is a Duration method. // // fromNumber sets the duration to the given number of seconds. -func DurationFromNumber(vm *VM, target, locals *Object, msg *Message) *Object { +func fromNumber(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { n, exc, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } target.Lock() @@ -136,22 +147,22 @@ func DurationFromNumber(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DurationHours is a Duration method. +// hours is a Duration method. // // hours returns the number of whole hours the duration represents, modulo 24. -func DurationHours(vm *VM, target, locals *Object, msg *Message) *Object { +func hours(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Duration) target.Unlock() return vm.NewNumber(float64(int64(d.Hours()) % 24)) } -// DurationMinusEq is a Duration method. +// minusEq is a Duration method. // // -= decreases this duration by the argument duration. -func DurationMinusEq(vm *VM, target, locals *Object, msg *Message) *Object { - dd, exc, stop := msg.DurationArgAt(vm, locals, 0) - if stop != NoStop { +func minusEq(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { + dd, exc, stop := ArgAt(vm, msg, locals, 0) + if stop != iolang.NoStop { return vm.Stop(exc, stop) } target.Lock() @@ -161,23 +172,23 @@ func DurationMinusEq(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DurationMinutes is a Duration method. +// minutes is a Duration method. // // minutes returns the number of whole minutes the duration represents, modulo // 60. -func DurationMinutes(vm *VM, target, locals *Object, msg *Message) *Object { +func minutes(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Duration) target.Unlock() return vm.NewNumber(float64(int64(d.Minutes()) % 60)) } -// DurationPlusEq is a Duration method. +// plusEq is a Duration method. // // += increases this duration by the argument duration. -func DurationPlusEq(vm *VM, target, locals *Object, msg *Message) *Object { - dd, exc, stop := msg.DurationArgAt(vm, locals, 0) - if stop != NoStop { +func plusEq(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { + dd, exc, stop := ArgAt(vm, msg, locals, 0) + if stop != iolang.NoStop { return vm.Stop(exc, stop) } target.Lock() @@ -187,24 +198,24 @@ func DurationPlusEq(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DurationSeconds is a Duration method. +// seconds is a Duration method. // // seconds returns the fractional number of seconds the duration represents, // modulo 60. -func DurationSeconds(vm *VM, target, locals *Object, msg *Message) *Object { +func seconds(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Duration) target.Unlock() return vm.NewNumber(math.Mod(d.Seconds(), 60)) } -// DurationSetDays is a Duration method. +// setDays is a Duration method. // // setDays sets the number of days the duration represents, with a day defined // as 60*60*24 seconds. Overflow into years is allowed. -func DurationSetDays(vm *VM, target, locals *Object, msg *Message) *Object { +func setDays(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { n, exc, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } target.Lock() @@ -216,13 +227,13 @@ func DurationSetDays(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DurationSetHours is a Duration method. +// setHours is a Duration method. // // setHours sets the number of hours the duration represents. Overflow into // days is allowed. -func DurationSetHours(vm *VM, target, locals *Object, msg *Message) *Object { +func setHours(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { n, exc, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } target.Lock() @@ -234,13 +245,13 @@ func DurationSetHours(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DurationSetMinutes is a Duration method. +// setMinutes is a Duration method. // // setMinutes sets the number of minutes the duration represents. Overflow into // hours is allowed. -func DurationSetMinutes(vm *VM, target, locals *Object, msg *Message) *Object { +func setMinutes(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { n, exc, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } target.Lock() @@ -252,13 +263,13 @@ func DurationSetMinutes(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DurationSetSeconds is a Duration method. +// setSeconds is a Duration method. // // setSeconds sets the number of seconds the duration represents. Overflow and // underflow are handled correctly. -func DurationSetSeconds(vm *VM, target, locals *Object, msg *Message) *Object { +func setSeconds(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { n, exc, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } target.Lock() @@ -270,15 +281,15 @@ func DurationSetSeconds(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DurationSetYears is a Duration method. +// setYears is a Duration method. // // setYears sets the number of years the duration represents, with a year // defined as 60*60*24*365 seconds. Overflow, underflow, and fractional values // are handled correctly. However, because durations are represented as integer // nanoseconds internally, this conversion is never exact. -func DurationSetYears(vm *VM, target, locals *Object, msg *Message) *Object { +func setYears(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { n, exc, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } target.Lock() @@ -290,11 +301,11 @@ func DurationSetYears(vm *VM, target, locals *Object, msg *Message) *Object { return target } -// DurationYears is a Duration method. +// years is a Duration method. // // years returns the number of whole years represented by the duration, with a // year defined as 60*60*24*365 seconds. -func DurationYears(vm *VM, target, locals *Object, msg *Message) *Object { +func years(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() d := target.Value.(time.Duration) target.Unlock() diff --git a/coreext/duration/duration_init.go b/coreext/duration/duration_init.go new file mode 100644 index 0000000..328f42e --- /dev/null +++ b/coreext/duration/duration_init.go @@ -0,0 +1,9 @@ +package duration + +// Code generated by gencore; DO NOT EDIT + +var coreIo = []string{ + "x\x9cr)-J,\xc9\xcc\xcfSH\xc9\xd7\xe0\xe2,N-\t\xce\xc9/\xd1P\xd2V\xd2Q\xc8M-\xc9\xc8O\xd1\xc8/\xc9H-\xd2Q(N\xcdISH\xce\xc9\xcfKUжU\x00\vjj\"\xe9\xd0ŧC\x17\xa1C\x93\v\x10\x00\x00\xff\xff}7$\x87", +} + +var coreFiles = []string{"io/Duration.io"} diff --git a/coreext/duration/io/Duration.io b/coreext/duration/io/Duration.io new file mode 100644 index 0000000..96c6e0f --- /dev/null +++ b/coreext/duration/io/Duration.io @@ -0,0 +1,4 @@ +Duration do( + setSlot("+", method(other, self clone += other)) + setSlot("-", method(other, self clone -= other)) +) diff --git a/file.go b/coreext/file/file.go similarity index 69% rename from file.go rename to coreext/file/file.go index d4e2acc..34a7577 100644 --- a/file.go +++ b/coreext/file/file.go @@ -1,4 +1,7 @@ -package iolang +//go:generate go run ../../cmd/gencore file_init.go file ./io +//go:generate gofmt -s -w file_init.go + +package file import ( "bytes" @@ -7,6 +10,10 @@ import ( "io/ioutil" "os" "path/filepath" + + "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/coreext/date" + "github.com/zephyrtronium/iolang/internal" ) // A File is an object allowing interfacing with the operating system's files. @@ -22,7 +29,7 @@ type File struct { // tagFile is the Tag type for File objects. type tagFile struct{} -func (tagFile) Activate(vm *VM, self, target, locals, context *Object, msg *Message) *Object { +func (tagFile) Activate(vm *iolang.VM, self, target, locals, context *iolang.Object, msg *iolang.Message) *iolang.Object { return self } @@ -38,10 +45,10 @@ func (tagFile) String() string { // returns a new, unopened file with an empty path and mode set to "update". var FileTag tagFile -// NewFile creates a File object with the given file. The mode string should +// New creates a File object with the given file. The mode string should // be one of "read", "update", or "append", depending on the flags used when // opening the file. -func (vm *VM) NewFile(file *os.File, mode string) *Object { +func New(vm *iolang.VM, file *os.File, mode string) *iolang.Object { f := File{ File: file, Mode: mode, @@ -52,9 +59,9 @@ func (vm *VM) NewFile(file *os.File, mode string) *Object { return vm.ObjectWith(nil, vm.CoreProto("File"), f, FileTag) } -// NewFileAt creates a File object unopened at the given path. The mode will be +// NewAt creates a File object unopened at the given path. The mode will be // set to "read". The path should use the OS's separator convention. -func (vm *VM) NewFileAt(path string) *Object { +func NewAt(vm *iolang.VM, path string) *iolang.Object { f := File{ Path: path, Mode: "read", @@ -129,85 +136,90 @@ func (f File) ReadLine() (line []byte, eof bool, err error) { return } -func (vm *VM) initFile() { - slots := Slots{ - "at": vm.NewCFunction(FileAt, FileTag), - "atPut": vm.NewCFunction(FileAtPut, FileTag), - "close": vm.NewCFunction(FileClose, FileTag), - "contents": vm.NewCFunction(FileContents, FileTag), - "descriptor": vm.NewCFunction(FileDescriptor, FileTag), - "exists": vm.NewCFunction(FileExists, FileTag), - "flush": vm.NewCFunction(FileFlush, FileTag), - "foreach": vm.NewCFunction(FileForeach, FileTag), - "foreachLine": vm.NewCFunction(FileForeachLine, FileTag), - "isAtEnd": vm.NewCFunction(FileIsAtEnd, FileTag), - "isDirectory": vm.NewCFunction(FileIsDirectory, FileTag), - "isLink": vm.NewCFunction(FileIsLink, FileTag), - "isOpen": vm.NewCFunction(FileIsOpen, FileTag), - "isPipe": vm.NewCFunction(FileIsPipe, FileTag), - "isRegularFile": vm.NewCFunction(FileIsRegularFile, FileTag), - "isSocket": vm.NewCFunction(FileIsSocket, FileTag), - "isUserExecutable": vm.NewCFunction(FileIsUserExecutable, FileTag), - "lastDataChangeDate": vm.NewCFunction(FileLastDataChangeDate, FileTag), - "mode": vm.NewCFunction(FileMode, FileTag), - "moveTo": vm.NewCFunction(FileMoveTo, FileTag), - "name": vm.NewCFunction(FileName, FileTag), - "open": vm.NewCFunction(FileOpen, FileTag), - "openForAppending": vm.NewCFunction(FileOpenForAppending, FileTag), - "openForReading": vm.NewCFunction(FileOpenForReading, FileTag), - "openForUpdating": vm.NewCFunction(FileOpenForUpdating, FileTag), - "path": vm.NewCFunction(FilePath, FileTag), - "position": vm.NewCFunction(FilePosition, FileTag), - "positionAtEnd": vm.NewCFunction(FilePositionAtEnd, FileTag), - "protectionMode": vm.NewCFunction(FileProtectionMode, FileTag), - "readBufferOfLength": vm.NewCFunction(FileReadBufferOfLength, FileTag), - "readLine": vm.NewCFunction(FileReadLine, FileTag), - "readLines": vm.NewCFunction(FileReadLines, FileTag), - "readStringOfLength": vm.NewCFunction(FileReadStringOfLength, FileTag), - "readToBufferLength": vm.NewCFunction(FileReadToBufferLength, FileTag), - "readToEnd": vm.NewCFunction(FileReadToEnd, FileTag), - "remove": vm.NewCFunction(FileRemove, FileTag), - "rewind": vm.NewCFunction(FileRewind, FileTag), - "setPath": vm.NewCFunction(FileSetPath, FileTag), - "setPosition": vm.NewCFunction(FileSetPosition, FileTag), - "size": vm.NewCFunction(FileSize, FileTag), - "temporaryFile": vm.NewCFunction(FileTemporaryFile, nil), - "truncateToSize": vm.NewCFunction(FileTruncateToSize, FileTag), +func init() { + internal.Register(initFile) +} + +func initFile(vm *iolang.VM) { + slots := iolang.Slots{ + "at": vm.NewCFunction(at, FileTag), + "atPut": vm.NewCFunction(atPut, FileTag), + "close": vm.NewCFunction(fileClose, FileTag), + "contents": vm.NewCFunction(contents, FileTag), + "descriptor": vm.NewCFunction(descriptor, FileTag), + "exists": vm.NewCFunction(exists, FileTag), + "flush": vm.NewCFunction(flush, FileTag), + "foreach": vm.NewCFunction(foreach, FileTag), + "foreachLine": vm.NewCFunction(foreachLine, FileTag), + "isAtEnd": vm.NewCFunction(isAtEnd, FileTag), + "isDirectory": vm.NewCFunction(isDirectory, FileTag), + "isLink": vm.NewCFunction(isLink, FileTag), + "isOpen": vm.NewCFunction(isOpen, FileTag), + "isPipe": vm.NewCFunction(isPipe, FileTag), + "isRegularFile": vm.NewCFunction(isRegularFile, FileTag), + "isSocket": vm.NewCFunction(isSocket, FileTag), + "isUserExecutable": vm.NewCFunction(isUserExecutable, FileTag), + "lastDataChangeDate": vm.NewCFunction(lastDataChangeDate, FileTag), + "mode": vm.NewCFunction(mode, FileTag), + "moveTo": vm.NewCFunction(moveTo, FileTag), + "name": vm.NewCFunction(name, FileTag), + "open": vm.NewCFunction(open, FileTag), + "openForAppending": vm.NewCFunction(openForAppending, FileTag), + "openForReading": vm.NewCFunction(openForReading, FileTag), + "openForUpdating": vm.NewCFunction(openForUpdating, FileTag), + "path": vm.NewCFunction(path, FileTag), + "position": vm.NewCFunction(position, FileTag), + "positionAtEnd": vm.NewCFunction(positionAtEnd, FileTag), + "protectionMode": vm.NewCFunction(protectionMode, FileTag), + "readBufferOfLength": vm.NewCFunction(readBufferOfLength, FileTag), + "readLine": vm.NewCFunction(readLine, FileTag), + "readLines": vm.NewCFunction(readLines, FileTag), + "readStringOfLength": vm.NewCFunction(readStringOfLength, FileTag), + "readToBufferLength": vm.NewCFunction(readToBufferLength, FileTag), + "readToEnd": vm.NewCFunction(readToEnd, FileTag), + "remove": vm.NewCFunction(remove, FileTag), + "rewind": vm.NewCFunction(rewind, FileTag), + "setPath": vm.NewCFunction(setPath, FileTag), + "setPosition": vm.NewCFunction(setPosition, FileTag), + "size": vm.NewCFunction(size, FileTag), + "temporaryFile": vm.NewCFunction(temporaryFile, nil), + "truncateToSize": vm.NewCFunction(truncateToSize, FileTag), "type": vm.NewString("File"), - "write": vm.NewCFunction(FileWrite, FileTag), + "write": vm.NewCFunction(write, FileTag), // Methods with platform-dependent implementations: - "groupId": vm.NewCFunction(FileGroupID, FileTag), - "lastAccessDate": vm.NewCFunction(FileLastAccessDate, FileTag), - "lastInfoChangeDate": vm.NewCFunction(FileLastInfoChangeDate, FileTag), - "userId": vm.NewCFunction(FileUserID, FileTag), + "groupId": vm.NewCFunction(groupID, FileTag), + "lastAccessDate": vm.NewCFunction(lastAccessDate, FileTag), + "lastInfoChangeDate": vm.NewCFunction(lastInfoChangeDate, FileTag), + "userId": vm.NewCFunction(userID, FileTag), } slots["asBuffer"] = slots["contents"] slots["descriptorId"] = slots["descriptor"] - proto := vm.coreInstall("File", slots, File{}, FileTag) + proto := internal.CoreInstall(vm, "File", slots, File{}, FileTag) - stdin := vm.NewFile(os.Stdin, "read") - stdout := vm.NewFile(os.Stdout, "") - stderr := vm.NewFile(os.Stderr, "") + stdin := New(vm, os.Stdin, "read") + stdout := New(vm, os.Stdout, "") + stderr := New(vm, os.Stderr, "") vm.SetSlot(stdout, "mode", vm.Nil) vm.SetSlot(stderr, "mode", vm.Nil) - slots = Slots{ + slots = iolang.Slots{ "standardInput": stdin, "standardOutput": stdout, "standardError": stderr, } vm.SetSlots(proto, slots) + internal.Ioz(vm, coreIo, coreFiles) } // FileAt is a File method. // // at returns as a Number the byte in the file at a given position. -func FileAt(vm *VM, target, locals *Object, msg *Message) *Object { +func at(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() n, exc, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } b := []byte{0} @@ -224,16 +236,16 @@ func FileAt(vm *VM, target, locals *Object, msg *Message) *Object { // FileAtPut is a File method. // // atPut writes a single byte to the file at a given position. -func FileAtPut(vm *VM, target, locals *Object, msg *Message) *Object { +func atPut(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() n, exc, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } c, exc, stop := msg.NumberArgAt(vm, locals, 1) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } if _, err := f.File.WriteAt([]byte{byte(c)}, int64(n)); err != nil { @@ -245,7 +257,7 @@ func FileAtPut(vm *VM, target, locals *Object, msg *Message) *Object { // FileClose is a File method. // // close closes the file. -func FileClose(vm *VM, target, locals *Object, msg *Message) *Object { +func fileClose(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -269,7 +281,7 @@ func FileClose(vm *VM, target, locals *Object, msg *Message) *Object { // // contents reads the contents of the file into a buffer object. Same as // asBuffer, but can also read standardInput. -func FileContents(vm *VM, target, locals *Object, msg *Message) *Object { +func contents(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -290,7 +302,7 @@ func FileContents(vm *VM, target, locals *Object, msg *Message) *Object { // FileDescriptor is a File method. // // descriptor returns the underlying file descriptor as a Number. -func FileDescriptor(vm *VM, target, locals *Object, msg *Message) *Object { +func descriptor(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -300,7 +312,7 @@ func FileDescriptor(vm *VM, target, locals *Object, msg *Message) *Object { // FileExists is a File method. // // exists returns whether a file with this file's path exists. -func FileExists(vm *VM, target, locals *Object, msg *Message) *Object { +func exists(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -318,7 +330,7 @@ func FileExists(vm *VM, target, locals *Object, msg *Message) *Object { // // flush synchronizes the file state between the program and the operating // system. -func FileFlush(vm *VM, target, locals *Object, msg *Message) *Object { +func flush(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -331,15 +343,15 @@ func FileFlush(vm *VM, target, locals *Object, msg *Message) *Object { // FileForeach is a File method. // // foreach executes a message for each byte of the file. -func FileForeach(vm *VM, target, locals *Object, msg *Message) (result *Object) { - kn, vn, hkn, hvn, ev := ForeachArgs(msg) +func foreach(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) (result *iolang.Object) { + kn, vn, hkn, hvn, ev := iolang.ForeachArgs(msg) if ev == nil { return vm.RaiseExceptionf("foreach requires 2 or 3 arguments") } target.Lock() f := target.Value.(File) target.Unlock() - var control Stop + var control iolang.Stop if info, err := f.File.Stat(); err == nil && info.Mode().IsRegular() { // Regular file, so we can read into a buffer and then seek back if we // encounter a Stop. @@ -361,10 +373,10 @@ func FileForeach(vm *VM, target, locals *Object, msg *Message) (result *Object) } result, control = ev.Send(vm, v, locals) switch control { - case NoStop, ContinueStop: // do nothing - case BreakStop: + case iolang.NoStop, iolang.ContinueStop: // do nothing + case iolang.BreakStop: return result - case ReturnStop, ExceptionStop, ExitStop: + case iolang.ReturnStop, iolang.ExceptionStop, iolang.ExitStop: return vm.Stop(result, control) default: panic(fmt.Sprintf("iolang: invalid Stop: %v", control)) @@ -408,10 +420,10 @@ func FileForeach(vm *VM, target, locals *Object, msg *Message) (result *Object) } result, control = ev.Send(vm, v, locals) switch control { - case NoStop, ContinueStop: // do nothing - case BreakStop: + case iolang.NoStop, iolang.ContinueStop: // do nothing + case iolang.BreakStop: return result - case ReturnStop, ExceptionStop, ExitStop: + case iolang.ReturnStop, iolang.ExceptionStop, iolang.ExitStop: return vm.Stop(result, control) default: panic(fmt.Sprintf("iolang: invalid Stop: %v", control)) @@ -424,8 +436,8 @@ func FileForeach(vm *VM, target, locals *Object, msg *Message) (result *Object) // FileForeachLine is a File method. // // foreachLine executes a message for each line of the file. -func FileForeachLine(vm *VM, target, locals *Object, msg *Message) (result *Object) { - kn, vn, hkn, hvn, ev := ForeachArgs(msg) +func foreachLine(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) (result *iolang.Object) { + kn, vn, hkn, hvn, ev := iolang.ForeachArgs(msg) if ev == nil { return vm.RaiseExceptionf("foreach requires 1, 2, or 3 arguments") } @@ -433,7 +445,7 @@ func FileForeachLine(vm *VM, target, locals *Object, msg *Message) (result *Obje f := target.Value.(File) target.Unlock() k := 0 - var control Stop + var control iolang.Stop for { // f.ReadLine implements the same logic as FileForeach above. line, eof, err := f.ReadLine() @@ -453,10 +465,10 @@ func FileForeachLine(vm *VM, target, locals *Object, msg *Message) (result *Obje } result, control = ev.Send(vm, v, locals) switch control { - case NoStop, ContinueStop: // do nothing - case BreakStop: + case iolang.NoStop, iolang.ContinueStop: // do nothing + case iolang.BreakStop: return result - case ReturnStop, ExceptionStop, ExitStop: + case iolang.ReturnStop, iolang.ExceptionStop, iolang.ExitStop: return vm.Stop(result, control) default: panic(fmt.Sprintf("iolang: invalid Stop: %v", control)) @@ -473,7 +485,7 @@ func FileForeachLine(vm *VM, target, locals *Object, msg *Message) (result *Obje // FileIsAtEnd is a File method. // // isAtEnd returns true if the file is at EOF. -func FileIsAtEnd(vm *VM, target, locals *Object, msg *Message) *Object { +func isAtEnd(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -483,7 +495,7 @@ func FileIsAtEnd(vm *VM, target, locals *Object, msg *Message) *Object { // FileIsDirectory is a File method. // // isDirectory returns true if the path of the file is a directory. -func FileIsDirectory(vm *VM, target, locals *Object, msg *Message) *Object { +func isDirectory(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -497,7 +509,7 @@ func FileIsDirectory(vm *VM, target, locals *Object, msg *Message) *Object { // FileIsLink is a File method. // // isLink returns true if the path of the file is a symbolic link. -func FileIsLink(vm *VM, target, locals *Object, msg *Message) *Object { +func isLink(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -511,7 +523,7 @@ func FileIsLink(vm *VM, target, locals *Object, msg *Message) *Object { // FileIsOpen is a File method. // // isOpen returns true if the file is open. -func FileIsOpen(vm *VM, target, locals *Object, msg *Message) *Object { +func isOpen(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -521,7 +533,7 @@ func FileIsOpen(vm *VM, target, locals *Object, msg *Message) *Object { // FileIsPipe is a File method. // // isPipe returns true if the path of the file is a named pipe. -func FileIsPipe(vm *VM, target, locals *Object, msg *Message) *Object { +func isPipe(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -535,7 +547,7 @@ func FileIsPipe(vm *VM, target, locals *Object, msg *Message) *Object { // FileIsRegularFile is a File method. // // isRegularFile returns true if the path of the file is a regular file. -func FileIsRegularFile(vm *VM, target, locals *Object, msg *Message) *Object { +func isRegularFile(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -549,7 +561,7 @@ func FileIsRegularFile(vm *VM, target, locals *Object, msg *Message) *Object { // FileIsSocket is a File method. // // isSocket returns true if the path of the file is a socket. -func FileIsSocket(vm *VM, target, locals *Object, msg *Message) *Object { +func isSocket(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -564,7 +576,7 @@ func FileIsSocket(vm *VM, target, locals *Object, msg *Message) *Object { // // isUserExecutable returns true if the path of the file is executable by its // owner. Always false on Windows. -func FileIsUserExecutable(vm *VM, target, locals *Object, msg *Message) *Object { +func isUserExecutable(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -579,7 +591,7 @@ func FileIsUserExecutable(vm *VM, target, locals *Object, msg *Message) *Object // // lastDataChangeDate returns the date at which the file's contents were last // modified. -func FileLastDataChangeDate(vm *VM, target, locals *Object, msg *Message) *Object { +func lastDataChangeDate(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -587,14 +599,14 @@ func FileLastDataChangeDate(vm *VM, target, locals *Object, msg *Message) *Objec if err != nil { return vm.IoError(err) } - return vm.NewDate(fi.ModTime()) + return date.New(vm, fi.ModTime()) } // FileMode is a File method. // // mode returns a string describing the file's mode; one of "read", "update", // or "append". -func FileMode(vm *VM, target, locals *Object, msg *Message) *Object { +func mode(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -604,12 +616,12 @@ func FileMode(vm *VM, target, locals *Object, msg *Message) *Object { // FileMoveTo is a File method. // // moveTo moves the file at the file's path to the given path. -func FileMoveTo(vm *VM, target, locals *Object, msg *Message) *Object { +func moveTo(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() s, exc, stop := msg.StringArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } to := filepath.FromSlash(s) @@ -623,7 +635,7 @@ func FileMoveTo(vm *VM, target, locals *Object, msg *Message) *Object { // // FileName returns the name of the file or directory at the file's path, // similar to UNIX basename. -func FileName(vm *VM, target, locals *Object, msg *Message) *Object { +func name(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -633,7 +645,7 @@ func FileName(vm *VM, target, locals *Object, msg *Message) *Object { // FileOpen is a File method. // // open opens the file. -func FileOpen(vm *VM, target, locals *Object, msg *Message) *Object { +func open(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -662,43 +674,43 @@ func FileOpen(vm *VM, target, locals *Object, msg *Message) *Object { // FileOpenForAppending is a File method. // // openForAppending opens the file for appending. -func FileOpenForAppending(vm *VM, target, locals *Object, msg *Message) *Object { +func openForAppending(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) f.Mode = "append" target.Value = f target.Unlock() - return FileOpen(vm, target, locals, msg) + return open(vm, target, locals, msg) } // FileOpenForReading is a File method. // // openForReading opens the file for reading. -func FileOpenForReading(vm *VM, target, locals *Object, msg *Message) *Object { +func openForReading(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) f.Mode = "read" target.Value = f target.Unlock() - return FileOpen(vm, target, locals, msg) + return open(vm, target, locals, msg) } // FileOpenForUpdating is a File method. // // openForUpdating opens the file for updating. -func FileOpenForUpdating(vm *VM, target, locals *Object, msg *Message) *Object { +func openForUpdating(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) f.Mode = "update" target.Value = f target.Unlock() - return FileOpen(vm, target, locals, msg) + return open(vm, target, locals, msg) } // FilePath is a File method. // // path returns the file's absolute path. -func FilePath(vm *VM, target, locals *Object, msg *Message) *Object { +func path(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -708,7 +720,7 @@ func FilePath(vm *VM, target, locals *Object, msg *Message) *Object { // FilePosition is a File method. // // position returns the current position of the file cursor. -func FilePosition(vm *VM, target, locals *Object, msg *Message) *Object { +func position(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -722,7 +734,7 @@ func FilePosition(vm *VM, target, locals *Object, msg *Message) *Object { // FilePositionAtEnd is a File method. // // positionAtEnd moves the file cursor to the end of the file. -func FilePositionAtEnd(vm *VM, target, locals *Object, msg *Message) *Object { +func positionAtEnd(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -740,7 +752,7 @@ func FilePositionAtEnd(vm *VM, target, locals *Object, msg *Message) *Object { // FileProtectionMode is a File method. // // protectionMode returns the stat mode of the path of the file as a Number. -func FileProtectionMode(vm *VM, target, locals *Object, msg *Message) *Object { +func protectionMode(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -754,12 +766,12 @@ func FileProtectionMode(vm *VM, target, locals *Object, msg *Message) *Object { // FileReadBufferOfLength is a File method. // // readBufferOfLength reads the specified number of bytes into a Sequence. -func FileReadBufferOfLength(vm *VM, target, locals *Object, msg *Message) *Object { +func readBufferOfLength(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() count, exc, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } if count < 0 { @@ -785,7 +797,7 @@ func FileReadBufferOfLength(vm *VM, target, locals *Object, msg *Message) *Objec // FileReadLine is a File method. // // readLine reads a line from the file. -func FileReadLine(vm *VM, target, locals *Object, msg *Message) *Object { +func readLine(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -808,11 +820,11 @@ func FileReadLine(vm *VM, target, locals *Object, msg *Message) *Object { // FileReadLines is a File method. // // readLines returns a List containing all lines in the file. -func FileReadLines(vm *VM, target, locals *Object, msg *Message) *Object { +func readLines(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() - l := []*Object{} + l := []*iolang.Object{} for { b, eof, err := f.ReadLine() if eof { @@ -839,12 +851,12 @@ func FileReadLines(vm *VM, target, locals *Object, msg *Message) *Object { // FileReadStringOfLength is a File method. // // readStringOfLength reads a string up to the given length from the file. -func FileReadStringOfLength(vm *VM, target, locals *Object, msg *Message) *Object { +func readStringOfLength(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() count, exc, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } if count < 0 { @@ -872,13 +884,13 @@ func FileReadStringOfLength(vm *VM, target, locals *Object, msg *Message) *Objec // readToBufferLength reads the number of items given in the second argument // and appends them to the sequence given in the first. Returns the number of // elements actually read. -func FileReadToBufferLength(vm *VM, target, locals *Object, msg *Message) *Object { +func readToBufferLength(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { seq, obj, stop := msg.SequenceArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(obj, stop) } n, exc, stop := msg.NumberArgAt(vm, locals, 1) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } if n < 0 { @@ -915,14 +927,14 @@ func FileReadToBufferLength(vm *VM, target, locals *Object, msg *Message) *Objec // // readToEnd reads chunks of a given size (default 4096) to the end of the file // and returns a Sequence containing the bytes read. -func FileReadToEnd(vm *VM, target, locals *Object, msg *Message) *Object { +func readToEnd(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() sz := 4096 if len(msg.Args) > 0 { n, exc, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } if n >= 1 { @@ -959,7 +971,7 @@ func FileReadToEnd(vm *VM, target, locals *Object, msg *Message) *Object { // FileRemove is a File method. // // remove removes the file at the file's path. -func FileRemove(vm *VM, target, locals *Object, msg *Message) *Object { +func remove(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -973,7 +985,7 @@ func FileRemove(vm *VM, target, locals *Object, msg *Message) *Object { // FileRewind is a File method. // // rewind returns the file cursor to the beginning of the file. -func FileRewind(vm *VM, target, locals *Object, msg *Message) *Object { +func rewind(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -991,12 +1003,12 @@ func FileRewind(vm *VM, target, locals *Object, msg *Message) *Object { // FileSetPath is a File method. // // setPath sets the file's path. -func FileSetPath(vm *VM, target, locals *Object, msg *Message) *Object { +func setPath(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() s, exc, stop := msg.StringArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } f.Path = filepath.FromSlash(s) @@ -1009,12 +1021,12 @@ func FileSetPath(vm *VM, target, locals *Object, msg *Message) *Object { // FileSetPosition is a File method. // // setPosition changes the file cursor's location. -func FileSetPosition(vm *VM, target, locals *Object, msg *Message) *Object { +func setPosition(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() n, exc, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } _, err := f.File.Seek(int64(n), io.SeekStart) @@ -1035,7 +1047,7 @@ func FileSetPosition(vm *VM, target, locals *Object, msg *Message) *Object { // FileSize is a File method. // // size determines the file size. -func FileSize(vm *VM, target, locals *Object, msg *Message) *Object { +func size(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -1050,23 +1062,23 @@ func FileSize(vm *VM, target, locals *Object, msg *Message) *Object { // // temporaryFile creates a file that did not exist previously. It is not // guaranteed to be removed at any point. -func FileTemporaryFile(vm *VM, target, locals *Object, msg *Message) *Object { +func temporaryFile(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { fp, err := ioutil.TempFile("", "iolang_temp") if err != nil { return vm.IoError(err) } - return vm.NewFile(fp, "update") + return New(vm, fp, "update") } // FileTruncateToSize is a File method. // // truncateToSize truncates the file to the given size. -func FileTruncateToSize(vm *VM, target, locals *Object, msg *Message) *Object { +func truncateToSize(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() n, exc, stop := msg.NumberArgAt(vm, locals, 0) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(exc, stop) } err := f.File.Truncate(int64(n)) @@ -1079,13 +1091,13 @@ func FileTruncateToSize(vm *VM, target, locals *Object, msg *Message) *Object { // FileWrite is a File method. // // write writes its arguments to the file. -func FileWrite(vm *VM, target, locals *Object, msg *Message) *Object { +func write(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() for i := range msg.Args { s, obj, stop := msg.SequenceArgAt(vm, locals, i) - if stop != NoStop { + if stop != iolang.NoStop { return vm.Stop(obj, stop) } obj.Lock() diff --git a/file_darwin.go b/coreext/file/file_darwin.go similarity index 65% rename from file_darwin.go rename to coreext/file/file_darwin.go index 0efdc92..51f244f 100644 --- a/file_darwin.go +++ b/coreext/file/file_darwin.go @@ -1,18 +1,21 @@ // +build darwin -package iolang +package file import ( "fmt" "os" "syscall" "time" + + "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/coreext/date" ) -// FileGroupID is a File method. +// groupID is a File method. // // groupId returns the group ID owning the file. -func FileGroupID(vm *VM, target, locals *Object, msg *Message) *Object { +func groupID(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -28,10 +31,10 @@ func FileGroupID(vm *VM, target, locals *Object, msg *Message) *Object { return vm.NewNumber(float64(s.Gid)) } -// FileLastAccessDate is a File method. +// lastAccessDate is a File method. // // lastAccessDate returns the date at which the file was last accessed. -func FileLastAccessDate(vm *VM, target, locals *Object, msg *Message) *Object { +func lastAccessDate(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -44,14 +47,14 @@ func FileLastAccessDate(vm *VM, target, locals *Object, msg *Message) *Object { if !ok { panic(fmt.Sprintf("iolang: %T.Sys() returned wrong type %T", fi, si)) } - return vm.NewDate(time.Unix(s.Atimespec.Sec, s.Atimespec.Nsec)) + return date.New(vm, time.Unix(s.Atimespec.Sec, s.Atimespec.Nsec)) } -// FileLastInfoChangeDate is a File method. +// lastInfoChangeDate is a File method. // // lastInfoChangeDate returns the date at which the file's metadata was last // changed. -func FileLastInfoChangeDate(vm *VM, target, locals *Object, msg *Message) *Object { +func lastInfoChangeDate(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -64,13 +67,13 @@ func FileLastInfoChangeDate(vm *VM, target, locals *Object, msg *Message) *Objec if !ok { panic(fmt.Sprintf("iolang: %T.Sys() returned wrong type %T", fi, si)) } - return vm.NewDate(time.Unix(s.Ctimespec.Sec, s.Ctimespec.Nsec)) + return date.New(vm, time.Unix(s.Ctimespec.Sec, s.Ctimespec.Nsec)) } -// FileUserID is a File method. +// userID is a File method. // // userId returns the user ID owning the file. -func FileUserID(vm *VM, target, locals *Object, msg *Message) *Object { +func userID(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() diff --git a/coreext/file/file_init.go b/coreext/file/file_init.go new file mode 100644 index 0000000..6e48521 --- /dev/null +++ b/coreext/file/file_init.go @@ -0,0 +1,9 @@ +package file + +// Code generated by gencore; DO NOT EDIT + +var coreIo = []string{ + "x\x9c\xc4TMo\xd40\x10=ۿb\xd4S,\x85\xb6\b\xb5\x87E\x15*-=!\x84\xd8H\x88\xa37\x99M\x8c\x9c\x19cOZ\xb6\xbf\x1e\xd9٥\xd9\xf2!q\xe2\xb2ٌg\xde{\x9e7\x93;\xe7\x11:\xae\xb4zp2\xc0\xea\nF\x94\x81\xbb*Ԑ\xd0o\xa1\xf5L\b\t壕\xa1\n\xc6h\xad\x92D\xb4\xe3-&qd\xc51\xc1ju\x05\xe4\xfc\xe1\xe8\x13\xdan\xed\x1e1\xe3]^\\\xbc\xba\xcc\a6ʺ\x9c:\xea\x17DsE\xc3\xd5/\xa8\xc6\x1c\xf0\x1a^\x14tIj\xad\xd4&\x87\xd6\xf8mBjq\x96\xa9\x95․\x95z\x18\x9c\xc7ʥkyG\x1d\x10\x97\n\xb5\x01\x1c\x83\xec\xf2߈\xb6k\xf8\xed\xb4\xddb|\x8f\xd4\xcbPmj8Vorb\x97\x04\x1e\xa2\x13\xac6\xe5\xfd\xcdΡ\xef\xb4RF\xab\x85\xbe\xcfN\x06\x9e\xe4K><\xbe\xdf\u007f\x95[Dj\xd5r\xd85\x9c\x1d\x93\xed\x1btof,\xa3\x95\r\x01\xa9k\xf87({ew\x1c\xafK\x92\xa3>\x93[\xef\x01ﭿ\x8e}\x82-G\xb4\xed\x90\x19\x0f\xd0\xe6\x99B\xb1\xb2\x9c\xf9\xbcY\xc9=\xe2\xeb\xb2dŢ\x88Vp\x91\xe3\xb6U&\xaeg\x91u^\xaf\xb2|\x1b\x9b\xf0\x83\x1d\x97\xa9\x94_S\xf0N\xaa\x93\xd3\x13\x03ɻ\x16\xab\xf3\x1a^\xbc4\xf0\x95\x1d\x95p.\x96\xc1\xa55O\xb1\xc5b\xc1\x13ē#\xe5j#\xa6d{\x04o78ӶLb\x1d9\xeao]\xc4V8\xeery\xb0\x11I\x8eB{ħ\xd8lt\x1e\xbe\xfcs\xc3c`B\x92\x02{v\x06̀\t\xc1F\x84\x11m&\xf0\x98R)\xe2I \xe4&\x9cj\x85ߝ\xac\xc5\xcaT\xcc)\xdf\x1a\xc18\xae]O\xd6\x1fBF\xff\b\x00\x00\xff\xff\xc0\x04\x8b\x00", +} + +var coreFiles = []string{"io/07_File.io"} diff --git a/file_js.go b/coreext/file/file_js.go similarity index 52% rename from file_js.go rename to coreext/file/file_js.go index 40f16d7..3d4c8bb 100644 --- a/file_js.go +++ b/coreext/file/file_js.go @@ -1,25 +1,28 @@ // +build js,wasm -package iolang +package file import ( "os" "syscall" "time" + + "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/coreext/date" ) -// FileGroupID is a File method. +// groupID is a File method. // // groupId returns the group ID owning the file. -func FileGroupID(vm *VM, target, locals *Object, msg *Message) *Object { +func groupID(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { // No such thing on Wasm, or at least not readily available. return vm.NewNumber(-1) } -// FileLastAccessDate is a File method. +// lastAccessDate is a File method. // // lastAccessDate returns the date at which the file was last accessed. -func FileLastAccessDate(vm *VM, target, locals *Object, msg *Message) *Object { +func lastAccessDate(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -28,13 +31,13 @@ func FileLastAccessDate(vm *VM, target, locals *Object, msg *Message) *Object { return vm.IoError(err) } si := fi.Sys().(*syscall.Stat_t) - return vm.NewDate(time.Unix(si.Atime, si.AtimeNsec)) + return date.New(vm, time.Unix(si.Atime, si.AtimeNsec)) } -// FileLastInfoChangeDate is a File method. +// lastInfoChangeDate is a File method. // // lastInfoChangeDate returns the modification time of the file. -func FileLastInfoChangeDate(vm *VM, target, locals *Object, msg *Message) *Object { +func lastInfoChangeDate(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -43,13 +46,13 @@ func FileLastInfoChangeDate(vm *VM, target, locals *Object, msg *Message) *Objec return vm.IoError(err) } si := fi.Sys().(*syscall.Stat_t) - return vm.NewDate(time.Unix(si.Ctime, si.CtimeNsec)) + return date.New(vm, time.Unix(si.Ctime, si.CtimeNsec)) } -// FileUserID is a File method. +// userID is a File method. // // userId returns the user ID owning the file. -func FileUserID(vm *VM, target, locals *Object, msg *Message) *Object { +func userID(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { // No such thing on Wasm, or at least not readily available. return vm.NewNumber(-1) } diff --git a/file_plan9.go b/coreext/file/file_plan9.go similarity index 59% rename from file_plan9.go rename to coreext/file/file_plan9.go index 9c678a0..b2344b6 100644 --- a/file_plan9.go +++ b/coreext/file/file_plan9.go @@ -1,17 +1,20 @@ // +build plan9 -package iolang +package file import ( "os" "syscall" "time" + + "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/coreext/date" ) -// FileGroupID is a File method. +// groupID is a File method. // // groupId returns the group ID owning the file. -func FileGroupID(vm *VM, target, locals *Object, msg *Message) *Object { +func groupID(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -24,10 +27,10 @@ func FileGroupID(vm *VM, target, locals *Object, msg *Message) *Object { return vm.NewString(si.Gid) } -// FileLastAccessDate is a File method. +// lastAccessDate is a File method. // // lastAccessDate returns the date at which the file was last accessed. -func FileLastAccessDate(vm *VM, target, locals *Object, msg *Message) *Object { +func lastAccessDate(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -36,20 +39,20 @@ func FileLastAccessDate(vm *VM, target, locals *Object, msg *Message) *Object { return vm.IoError(err) } si := fi.Sys().(*syscall.Dir) - return vm.NewDate(time.Unix(int64(si.Atime), 0)) + return date.New(vm, time.Unix(int64(si.Atime), 0)) } -// FileLastInfoChangeDate is a File method. +// lastInfoChangeDate is a File method. // // lastInfoChangeDate returns the modification time of the file. -func FileLastInfoChangeDate(vm *VM, target, locals *Object, msg *Message) *Object { +func lastInfoChangeDate(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { return FileLastDataChangeDate(vm, target, locals, msg) } -// FileUserID is a File method. +// userID is a File method. // // userId returns the user ID owning the file. -func FileUserID(vm *VM, target, locals *Object, msg *Message) *Object { +func userID(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() diff --git a/file_unix.go b/coreext/file/file_unix.go similarity index 66% rename from file_unix.go rename to coreext/file/file_unix.go index cc5ce51..c725880 100644 --- a/file_unix.go +++ b/coreext/file/file_unix.go @@ -1,21 +1,24 @@ // +build !windows // +build !plan9 -// +build !wasm +// +build !js // +build !darwin -package iolang +package file import ( "fmt" "os" "syscall" "time" + + "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/coreext/date" ) -// FileGroupID is a File method. +// groupID is a File method. // // groupId returns the group ID owning the file. -func FileGroupID(vm *VM, target, locals *Object, msg *Message) *Object { +func groupID(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -31,10 +34,10 @@ func FileGroupID(vm *VM, target, locals *Object, msg *Message) *Object { return vm.NewNumber(float64(s.Gid)) } -// FileLastAccessDate is a File method. +// lastAccessDate is a File method. // // lastAccessDate returns the date at which the file was last accessed. -func FileLastAccessDate(vm *VM, target, locals *Object, msg *Message) *Object { +func lastAccessDate(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -47,14 +50,14 @@ func FileLastAccessDate(vm *VM, target, locals *Object, msg *Message) *Object { if !ok { panic(fmt.Sprintf("iolang: %T.Sys() returned wrong type %T", fi, si)) } - return vm.NewDate(time.Unix(s.Atim.Unix())) + return date.New(vm, time.Unix(s.Atim.Unix())) } -// FileLastInfoChangeDate is a File method. +// lastInfoChangeDate is a File method. // // lastInfoChangeDate returns the date at which the file's metadata was last // changed. -func FileLastInfoChangeDate(vm *VM, target, locals *Object, msg *Message) *Object { +func lastInfoChangeDate(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -67,13 +70,13 @@ func FileLastInfoChangeDate(vm *VM, target, locals *Object, msg *Message) *Objec if !ok { panic(fmt.Sprintf("iolang: %T.Sys() returned wrong type %T", fi, si)) } - return vm.NewDate(time.Unix(s.Ctim.Unix())) + return date.New(vm, time.Unix(s.Ctim.Unix())) } -// FileUserID is a File method. +// userID is a File method. // // userId returns the user ID owning the file. -func FileUserID(vm *VM, target, locals *Object, msg *Message) *Object { +func userID(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() diff --git a/file_windows.go b/coreext/file/file_windows.go similarity index 72% rename from file_windows.go rename to coreext/file/file_windows.go index a1c0157..941d620 100644 --- a/file_windows.go +++ b/coreext/file/file_windows.go @@ -1,16 +1,19 @@ -package iolang +package file import ( "fmt" "os" "syscall" "time" + + "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/coreext/date" ) // FileGroupID is a File method. // // groupId returns the group ID owning the file. -func FileGroupID(vm *VM, target, locals *Object, msg *Message) *Object { +func groupID(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { // See FileUserID below. return vm.NewNumber(-1) } @@ -18,7 +21,7 @@ func FileGroupID(vm *VM, target, locals *Object, msg *Message) *Object { // FileLastAccessDate is a File method. // // lastAccessDate returns the date at which the file was last accessed. -func FileLastAccessDate(vm *VM, target, locals *Object, msg *Message) *Object { +func lastAccessDate(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -31,13 +34,13 @@ func FileLastAccessDate(vm *VM, target, locals *Object, msg *Message) *Object { if !ok { panic(fmt.Sprintf("iolang: %T.Sys() returned wrong type %T", fi, si)) } - return vm.NewDate(time.Unix(0, s.LastAccessTime.Nanoseconds())) + return date.New(vm, time.Unix(0, s.LastAccessTime.Nanoseconds())) } // FileLastInfoChangeDate is a File method. // // lastInfoChangeDate returns the date at which the file was created. -func FileLastInfoChangeDate(vm *VM, target, locals *Object, msg *Message) *Object { +func lastInfoChangeDate(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { target.Lock() f := target.Value.(File) target.Unlock() @@ -50,13 +53,13 @@ func FileLastInfoChangeDate(vm *VM, target, locals *Object, msg *Message) *Objec if !ok { panic(fmt.Sprintf("iolang: %T.Sys() returned wrong type %T", fi, si)) } - return vm.NewDate(time.Unix(0, s.CreationTime.Nanoseconds())) + return date.New(vm, time.Unix(0, s.CreationTime.Nanoseconds())) } // FileUserID is a File method. // // userId returns the user ID owning the file. -func FileUserID(vm *VM, target, locals *Object, msg *Message) *Object { +func userID(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { // We could encode the file path in UTF-16, use x/sys/windows.CreateFile // to get a handle, use GetSecurityInfo to get a SECURITY_DESCRIPTOR, use // LookupAccountSid to write the account name to a *uint16 backed by a diff --git a/io/07_File.io b/coreext/file/io/07_File.io similarity index 100% rename from io/07_File.io rename to coreext/file/io/07_File.io diff --git a/future.go b/coreext/future/future.go similarity index 67% rename from future.go rename to coreext/future/future.go index fcfd8c0..aeaa56a 100644 --- a/future.go +++ b/coreext/future/future.go @@ -1,9 +1,16 @@ -package iolang +//go:generate go run ../../cmd/gencore future_init.go future ./io +//go:generate gofmt -s -w future_init.go + +package future import ( "fmt" "runtime" "sync/atomic" + + "github.com/zephyrtronium/iolang" + _ "github.com/zephyrtronium/iolang/coreext/coroutine" + "github.com/zephyrtronium/iolang/internal" ) // A Future is a placeholder object that will be filled in by a dedicated @@ -12,15 +19,15 @@ type Future struct { // M is an atomic flag for whether the value has been computed. M uintptr // Value is the computed result, or nil while waiting for it. - Value *Object + Value *iolang.Object // Coro is the coroutine which will fill in the value. - Coro *VM + Coro *iolang.VM } // tagFuture is the Tag type for Future objects. type tagFuture struct{} -func (tagFuture) Activate(vm *VM, self, target, locals, context *Object, msg *Message) *Object { +func (tagFuture) Activate(vm *iolang.VM, self, target, locals, context *iolang.Object, msg *iolang.Message) *iolang.Object { if f := self.Value.(*Future); atomic.LoadUintptr(&f.M) == 1 { return f.Value.Activate(vm, target, locals, context, msg) } @@ -40,23 +47,34 @@ func (tagFuture) String() string { // a new Future with no coroutine. var FutureTag tagFuture -func (vm *VM) initFuture() { - slots := Slots{ - "forward": vm.NewCFunction(FutureForward, FutureTag), - "waitOnResult": vm.NewCFunction(FutureWaitOnResult, FutureTag), +func init() { + internal.Register(initFuture) +} + +func initFuture(vm *iolang.VM) { + slots := iolang.Slots{ + "forward": vm.NewCFunction(forward, FutureTag), + "waitOnResult": vm.NewCFunction(waitOnResult, FutureTag), } // Don't use coreInstall because we want no protos so we forward where // possible. vm.SetSlot(vm.Core, "Future", vm.ObjectWith(slots, nil, &Future{}, FutureTag)) + // Install Object methods that use Futures. + slots = iolang.Slots{ + "asyncSend": vm.NewCFunction(objectAsyncSend, nil), + "futureSend": vm.NewCFunction(objectFutureSend, nil), + } + vm.SetSlots(vm.BaseObject, slots) + internal.Ioz(vm, coreIo, coreFiles) } -// NewFuture creates a new Future object with its own coroutine and runs it. -func (vm *VM) NewFuture(target *Object, msg *Message) *Object { +// New creates a new Future object with its own coroutine and runs it. +func New(vm *iolang.VM, target *iolang.Object, msg *iolang.Message) *iolang.Object { coro := vm.Coro.Clone() m := vm.MessageObject(msg) f := &Future{Coro: vm.VMFor(coro)} - o := vm.ObjectWith(Slots{"runTarget": target, "runMessage": m}, vm.CoreProto("Future"), f, FutureTag) - vm.SetSlots(coro, Slots{ + o := vm.ObjectWith(iolang.Slots{"runTarget": target, "runMessage": m}, vm.CoreProto("Future"), f, FutureTag) + vm.SetSlots(coro, iolang.Slots{ "runTarget": target, "runMessage": m, "runLocals": target, @@ -74,12 +92,12 @@ func (f *Future) run() { defer vm.Sched.Finish(f.Coro) target, _ := vm.GetSlot(vm.Coro, "runTarget") msg, _ := vm.GetSlot(vm.Coro, "runMessage") - m, ok := msg.Value.(*Message) + m, ok := msg.Value.(*iolang.Message) if !ok { panic("Future started without a message to run") } r, stop := m.Send(vm, target, target) - if stop == ExceptionStop { + if stop == iolang.ExceptionStop { // Exception. Send it to the target's handleActorException slot. m := vm.IdentMessage("handleActorException", vm.CachedMessage(r)) r, stop = vm.Perform(target, target, m) @@ -107,24 +125,24 @@ func (f *Future) run() { // // NOTE: If Wait returns a Stop, then that Stop was sent to the waiting // coroutine, not the Future's. -func (f *Future) Wait(vm *VM) (*Object, Stop) { +func (f *Future) Wait(vm *iolang.VM) (*iolang.Object, iolang.Stop) { vm.Sched.Await(vm, f.Coro) for atomic.LoadUintptr(&f.M) == 0 { select { case stop := <-vm.Control: switch stop.Control { - case NoStop, ResumeStop: + case iolang.NoStop, internal.ResumeStop: runtime.Gosched() - case ContinueStop, BreakStop, ReturnStop, ExceptionStop, ExitStop: + case iolang.ContinueStop, iolang.BreakStop, iolang.ReturnStop, iolang.ExceptionStop, iolang.ExitStop: return stop.Result, stop.Control - case PauseStop: - vm.Sched.pause <- vm - for stop.Control != ResumeStop { + case internal.PauseStop: + vm.Sched.Pause(vm) + for stop.Control != internal.ResumeStop { switch stop = <-vm.Control; stop.Control { - case NoStop, PauseStop: // do nothing - case ContinueStop, BreakStop, ReturnStop, ExceptionStop, ExitStop: + case iolang.NoStop, internal.PauseStop: // do nothing + case iolang.ContinueStop, iolang.BreakStop, iolang.ReturnStop, iolang.ExceptionStop, iolang.ExitStop: return stop.Result, stop.Control - case ResumeStop: + case internal.ResumeStop: vm.Sched.Await(vm, f.Coro) default: panic(fmt.Errorf("iolang: invalid Stop: %w, value: %v", stop.Control.Err(), stop.Result)) @@ -136,14 +154,14 @@ func (f *Future) Wait(vm *VM) (*Object, Stop) { default: // do nothing } } - return f.Value, NoStop + return f.Value, iolang.NoStop } // FutureForward is a Future method. // // forward responds to messages to which the Future does not respond by proxying // to the evaluated result. This causes a wait. -func FutureForward(vm *VM, target, locals *Object, msg *Message) *Object { +func forward(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { f := target.Value.(*Future) if atomic.LoadUintptr(&f.M) == 0 { if f.Coro == nil { @@ -156,7 +174,7 @@ func FutureForward(vm *VM, target, locals *Object, msg *Message) *Object { } return t.Activate(vm, target, locals, proto, msg) } - if r, stop := f.Wait(vm); stop != NoStop { + if r, stop := f.Wait(vm); stop != iolang.NoStop { return vm.Stop(r, stop) } } @@ -166,7 +184,7 @@ func FutureForward(vm *VM, target, locals *Object, msg *Message) *Object { // FutureWaitOnResult is a Future method. // // waitOnResult blocks until the result of the Future is computed. Returns nil. -func FutureWaitOnResult(vm *VM, target, locals *Object, msg *Message) *Object { +func waitOnResult(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { f := target.Value.(*Future) if f.Coro == nil { // Either it hasn't been started yet or it's already finished. In the @@ -178,30 +196,30 @@ func FutureWaitOnResult(vm *VM, target, locals *Object, msg *Message) *Object { // atomic check and now, but that's probably always an erroneous race. return vm.RaiseExceptionf("cannot wait on unstarted Future") } - if r, stop := f.Wait(vm); stop != NoStop { + if r, stop := f.Wait(vm); stop != iolang.NoStop { return vm.Stop(r, stop) } return vm.Nil } -// ObjectAsyncSend is an Object method. +// objectAsyncSend is an Object method. // // asyncSend evaluates a message in a new coroutine. -func ObjectAsyncSend(vm *VM, target, locals *Object, msg *Message) *Object { +func objectAsyncSend(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { if msg.ArgCount() == 0 { return vm.RaiseExceptionf("asyncSend requires an argument") } - vm.NewFuture(target, msg.ArgAt(0)) + New(vm, target, msg.ArgAt(0)) return vm.Nil } -// ObjectFutureSend is an Object method. +// objectFutureSend is an Object method. // // futureSend evaluates a message in a new coroutine and returns a Future which // will become the result. -func ObjectFutureSend(vm *VM, target, locals *Object, msg *Message) *Object { +func objectFutureSend(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { if msg.ArgCount() == 0 { return vm.RaiseExceptionf("futureSend requires an argument") } - return vm.NewFuture(target, msg.ArgAt(0)) + return New(vm, target, msg.ArgAt(0)) } diff --git a/coreext/future/future_init.go b/coreext/future/future_init.go new file mode 100644 index 0000000..63077dd --- /dev/null +++ b/coreext/future/future_init.go @@ -0,0 +1,9 @@ +package future + +// Code generated by gencore; DO NOT EDIT + +var coreIo = []string{ + "x\x9c\xf2O\xcaJM.QH\xc9\xd7\xe0\xe2,N-\t\xce\xc9/\xd1PrP\xd2QH\x87q\xd2JKJ\x8bR\x83S\xf3R\x9445\x91\x15\xa1\xa8J,\xae\xccK\x86)\xd2\x04\x04\x00\x00\xff\xff\xa9\x13\x1a\xe5", +} + +var coreFiles = []string{"io/Future.io"} diff --git a/coreext/future/future_test.go b/coreext/future/future_test.go new file mode 100644 index 0000000..6551ecc --- /dev/null +++ b/coreext/future/future_test.go @@ -0,0 +1,43 @@ +package future_test + +import ( + "testing" + + "github.com/zephyrtronium/iolang" + _ "github.com/zephyrtronium/iolang/coreext/future" + "github.com/zephyrtronium/iolang/testutils" +) + +func TestObjectMethods(t *testing.T) { + vm := testutils.VM() + config := iolang.Slots{ + // coroWaitTime is the time in seconds that coros should wait while + // testing methods that spawn new coroutines. The new coroutines may + // take any amount of time to execute, as the VM does not wait for them + // to finish. + "coroWaitTime": vm.NewNumber(0.02), + } + vm.SetSlot(vm.Lobby, "testValues", vm.NewObject(config)) + cases := map[string]map[string]testutils.SourceTestCase{ + "asyncSend": { + "spawns": {Source: `while(Scheduler coroCount > 0, yield); testValues asyncSendSync := true; asyncSend(while(testValues asyncSendSync, yield)); wait(testValues coroWaitTime); testValues asyncSendCoros := Scheduler coroCount; testValues asyncSendSync = false; testValues asyncSendCoros`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "sideEffects": {Source: `testValues asyncSendSideEffect := 0; asyncSend(Lobby testValues asyncSendSideEffect = 1); wait(testValues coroWaitTime); testValues asyncSendSideEffect`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "empty": {Source: `asyncSend`, Pass: testutils.PassFailure()}, + }, + "futureSend": { + "result": {Source: `futureSend(1) isNil not`, Pass: testutils.PassIdentical(vm.True)}, + "evaluates": {Source: `futureSend(1) + 1`, Pass: testutils.PassEqual(vm.NewNumber(2))}, + "spawns": {Source: `while(Scheduler coroCount > 0, yield); testValues futureSendSync := true; futureSend(while(testValues futureSendSync, yield)); wait(testValues coroWaitTime); testValues futureSendCoros := Scheduler coroCount; testValues futureSendSync = false; testValues futureSendCoros`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "sideEffects": {Source: `testValues futureSendSideEffect := 0; futureSend(Lobby testValues futureSendSideEffect = 1); wait(testValues coroWaitTime); testValues futureSendSideEffect`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "empty": {Source: `futureSend`, Pass: testutils.PassFailure()}, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + for name, s := range c { + t.Run(name, s.TestFunc("TestObjectMethods")) + } + }) + } + vm.RemoveSlot(vm.Lobby, "testValues") +} diff --git a/coreext/future/io/Future.io b/coreext/future/io/Future.io new file mode 100644 index 0000000..8226d13 --- /dev/null +++ b/coreext/future/io/Future.io @@ -0,0 +1,4 @@ +Object do( + setSlot("@", getSlot("futureSend")) + setSlot("@@", getSlot("asyncSend")) +) \ No newline at end of file diff --git a/io/09_Path.io b/coreext/path/io/09_Path.io similarity index 100% rename from io/09_Path.io rename to coreext/path/io/09_Path.io diff --git a/coreext/path/path.go b/coreext/path/path.go new file mode 100644 index 0000000..f3c4841 --- /dev/null +++ b/coreext/path/path.go @@ -0,0 +1,55 @@ +//go:generate go run ../../cmd/gencore path_init.go path ./io +//go:generate gofmt -s -w path_init.go + +package path + +import ( + "path/filepath" + "runtime" + + "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/internal" +) + +func init() { + internal.Register(initPath) +} + +func initPath(vm *iolang.VM) { + slots := iolang.Slots{ + "absolute": vm.NewCFunction(absolute, nil), + "hasDriveLetters": vm.IoBool(runtime.GOOS == "windows"), + "isPathAbsolute": vm.NewCFunction(isPathAbsolute, nil), + "listSeparator": vm.NewString(string(filepath.ListSeparator)), + "separator": vm.NewString(string(filepath.Separator)), + } + internal.CoreInstall(vm, "Path", slots, nil, nil) + internal.Ioz(vm, coreIo, coreFiles) +} + +// absolute is a Path method. +// +// absolute returns an absolute version of the argument path. +func absolute(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { + s, exc, stop := msg.StringArgAt(vm, locals, 0) + if stop != iolang.NoStop { + return vm.Stop(exc, stop) + } + abs, err := filepath.Abs(filepath.FromSlash(s)) + if err != nil { + return vm.IoError(err) + } + return vm.NewString(filepath.ToSlash(abs)) +} + +// isPathAbsolute is a Path method. +// +// isPathAbsolute returns whether the argument is an absolute path. The path +// may be operating system- or Io-style. +func isPathAbsolute(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { + s, exc, stop := msg.StringArgAt(vm, locals, 0) + if stop != iolang.NoStop { + return vm.Stop(exc, stop) + } + return vm.IoBool(filepath.IsAbs(filepath.FromSlash(s))) +} diff --git a/coreext/path/path_init.go b/coreext/path/path_init.go new file mode 100644 index 0000000..e6d9f25 --- /dev/null +++ b/coreext/path/path_init.go @@ -0,0 +1,9 @@ +package path + +// Code generated by gencore; DO NOT EDIT + +var coreIo = []string{ + "x\x9cT\x8f1j\x031\x10E\xeb\x99S\f\xae$\xc8\t\x02i\xd3\xc5\x04\xf6\x04\xb2\xf4m\t\xb4\x1ayG\xeb\x90\xdb\a\xad\x8b\xe0b\x8a\xffy\x1f\xde|\x87\x91%\xa9c\x1a\xbf\x1d\xf2\xfe!\xa7Y\x9d\x98駌<\x8b\x15#krLd3.\xb8\xefh\x11\x12\xab60Q\f\xb5\xca\n\xb3p\x83\x84\xed\xb6\xafh\xc3\xe4\xaa\x1bB̮\xbf1\x11=\xe6\xf4 \r-a\x93\xa4_ω\xeb\xfe\t\x94\xebY۹Tg\x12zGKSd\xc1\xdd=\xfc$\xe6\x19\x93g\xa6\x91\x8b-\xbao\x11\x9f\xa5\xe2x\xe1\xdf\xf3\x88\xe1bZ\xf7\x01\xf7\"W\xc3\x05\xd5{\xf6\xfc\x17\x00\x00\xff\xffp$L^", +} + +var coreFiles = []string{"io/09_Path.io"} diff --git a/io/99_UnitTest.io b/coreext/unittest/io/99_UnitTest.io similarity index 100% rename from io/99_UnitTest.io rename to coreext/unittest/io/99_UnitTest.io diff --git a/coreext/unittest/unittest.go b/coreext/unittest/unittest.go new file mode 100644 index 0000000..8a0e740 --- /dev/null +++ b/coreext/unittest/unittest.go @@ -0,0 +1,21 @@ +//go:generate go run ../../cmd/gencore unittest_init.go unittest ./io +//go:generate gofmt -s -w unittest_init.go + +package unittest + +import ( + "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/internal" + + // importing for side effects + _ "github.com/zephyrtronium/iolang/coreext/directory" + _ "github.com/zephyrtronium/iolang/coreext/file" +) + +func init() { + internal.Register(initUnitTest) +} + +func initUnitTest(vm *iolang.VM) { + internal.Ioz(vm, coreIo, coreFiles) +} diff --git a/coreext/unittest/unittest_init.go b/coreext/unittest/unittest_init.go new file mode 100644 index 0000000..f94af59 --- /dev/null +++ b/coreext/unittest/unittest_init.go @@ -0,0 +1,9 @@ +package unittest + +// Code generated by gencore; DO NOT EDIT + +var coreIo = []string{ + "x\x9c\xa4WKo\xdc6\x10>K\xbfb*\xa3\xb0\x88Ȇ}*\xe0bS8\xb1\x03\x18I\x9c\xc0\x9b\xa0\x97\\(iv\x975E*$\xe5\xf5\xc6\xf0\u007f/\x86ԃ\xf2\xabM{Z\x8a\x9c\xc77\xef\xd9/h\xddU\xa7\x14\x1a8Y\xc0\xa7\xf2/\xac\x1cTR+\x84Z\xe7i\xb2\x15\xb5\xdb\xc0\xc9\xc9\x02~;J\xd3D(ሰA\xb7\xd1u\x9e&\x89E\xb9\x82\x8a[\xb4t\xaf\x84\x1c\xee\xf0\xb6\xc2\xd6\t\xad\xfc\x83\x14\xd6\xe5lx3\x9dr\xa2Az8J\x13\x96\xa6\x89C\xeb\xde\xeaN=/\xfd\x86\xcb\x0e-\x18\xac\xbb\nsU\x80\xe2\r\xda\x02\x14\xbc\nG\xb0\xe2\a\x16pĂD\xba\x9b\v\x13\xab<\x92G\xd4\xf0\x1a\x8e\x8b4I\x92\xe5\xce:l@\xf2NU\x9beeD\xeb`%$^\xf2\x06\xd3$\xf14O\xb0/\x16=\u007f\f\xf4\x1aw\x16V\xc2XG/\xe19\xcb\xe8\x87\xec\xef\xd1I\xa1\xb04ȯ\x9f\x86\xf8G\xad\x9d-\xc0\x9f\xe9\b\x8b\xe8\xfc\n\x8e㧓\x05\x1c\xb3\xc0\xeb\xbf\u007f\x85\x10\xb4\xc5\x02\x8e\nȾ\xa9\fZ#\x94\xf3\x9a\x13\xdbU\x15Z\x1b\xa9\xcd\x0e{\x82\xdfaD\xc5\xd2\x04\x8d\xd1&\"#\u007f\x16\x14U\xb2(\n.o[Tu\xee\x03<\xd20\x02\x94\x9d\xf7\x82\xd3d\xb27\x98o:\x15\x89\xa6\xe0\xdb\xe2Q2\xf9\xeb'R\xe6\x8c;\x04\x8b\x95V\xb5\xfd\xa2\xaf:E\x9e\xf3\x19da\xa5\r\xf2j\xd3#\xb1R\a\xc1IBR\x89\xf9\x83.\xcb\x1d\xac\xd1-\xa5\x0e\x80Y\b \x91\x8e\xec\xf4\x15\xf8\x02\xa3E\xf7\xb5\r\xdfx[ypf\x97\xfb\xa7Z/\x9d\x11j\xedyX\x10F\xb1 O\x81w\xa2\xd7\x02\x87\x87\x90AF?^\xb8\xf7R\x01}8X\xa4\xcb!7gz\xab\xfc\x15\x8b\xf2&\xf1\xce<\x1f]?\xdc,\xbb\xa6\xe1f\x17<\xfb\x80f\x9e^S2\xccc8\x98\xddG7ɳE\x06\x06[\xe4\x0e\xebܧ\x13\xf3\x06|S\xefN/>\x9c\x04;\xc8\x13\xdc\xe5GÓ\xbf\xcc\x0e\x1eq\xb2\xa0Rz\x8b\x88\xc9n\xf4v\xe9xu=\xd4\xc3̎\a\x90\x1fˋ\xc4\xe5\xd9\x15W\x01\xcd\xd4A\xbc\xa7\xe9\xd3ߋU>=\xfdB\x05\v\x99\xcd\nȲ\x80\x1bD/`\xc80\xba\xb4\xdfT\x16\xeb1h;\xe9{S\x88\xec\xe08aϛ\xd6\xed@QD3\xf2\xcd\xf9\x19\xe4+.dg\xd0\xc2\xde]DK\x1d㞑>\x87\xa6Ւ;, \xfb\xf4>\xa3\xd0潆\xc3C\xdf̀K\xb1VWb\xbdq\xc1h8\x80\x9e\x82Č\x1e\x8f0\xb2\x94\xa5ih\xe5\x1f\xc5\xed\x85z\xba\x9f\xcf\v/\xea\xfe\x81\xc6t*o\r\xb6\xdc \xf3\x02\xbf*\xe1\x88\xea\x914\xa2BU\u007f6\xda\xe9H\xd3\xc4\b\xecڸ7\xbb\xf7\xb8\x1bz\x05I\x9f\xf5\x05h\xd0Z\xbeF\xdf\x16/\xbb\xa6D\xc3\xd8Pe\xdeG\x11Џ\xbc\x85\xadp\x9bпݮ\xc5\x02fF1\xe2\xa3̈\x98|{(`,W0\\X\xcc)\xc7\xc2K\xff\x93\x11_Ƽ\bn-\x1aw\xfe\xbd\xe32v\x14/\xa0,\xa0\xa1\xf2m@\xac.\x85\xcc\x1bX@ť\x1c\xec\xe8\x9d©\n\xca\x02H\xa6\xef\x9c\xd9\xfe\xde]\x03ܬO\xa9\x98\xef\xe9y\xba8f\xf7\xfbpp\xf0\x1a\xf6\xf7\xee8p\xbb\x14M+1\xb4\xbc\x9e\xb4|x\xbd?Kr*\xf2P\xe5\x01\xfa\xa5\xfe\u007f\xe8\x17/\xa3_\xfc{\xf4\x8b\xff\x80~9_+\x02\xf08&9\x87N\x89\xef\x1d^\xd4\x05\x94\xd1yfL\xec\x8d\x17D\x8e\xbe\xfaY\xa9\xb34\xe3\x0f\x11\x16\xb4\xa8\xbd\x80\xe8\x19\xf6\b͋\x12\xbe\x98\x0e_V\xefL\x87\xcfq\xbf\xe3\xd2\xfe\x03\xfb\x8aH\x9e㿢\x1a\xb2SM͚I\x18\xdbR\x02\xdepy\xda'\r\xebS\x8e\xd2\xc9\xe7\x15唧\x1a\xd3j\x9f\x86V'k\xd8\xf0\x1b\feZOu;˘q˛\x95*5(\xa1\xceP:\xfe(\xd65\xdd\x16!\xbfs\x0e\aP2ॅ\xd7\xd3K\x8fk\xef\x8e\xdf\x03\u07b6X9\xa4D\xe8\x1cl9͙\xf2\x1er.\xa5\xdeb\x1d\x98N`\xef\xce\x1f\xeeY6!\xbaVz\xab\xdet\xeb\b³\x06\v\v\x1c<\x03\x94\xddzn\xa2\x9f\rg\xc2`\xe5\xb4ٽ\xd5R\xfa\x13\x89\xa5i\xb1\xec\x84ß\x1d\x19-\xef\xffiH\xfec\xe7\xbb\xf0l+\xff\xcc݆\xd1\x1f\x12\xa2\x1aѷ}\xe3\x0e\x1a,:\"\xcb[\xc6\xc2Hy'\xe4l\x9c\x8c\x90C\xaf&\x95\xcc/\xfa\xe38\xf1\xd3\x17U=M\x142\xe8Ph\xdf~\x9fh\xfci\x12i\x1a\x16)\x12Y\xf4\xbb\xe6\xb8 \xd2%TZ9T\xb4\xdc\xfbO\x8f\x80\xe2C\xfc\x93\x1f{5\xfdT\x9f\xbf\xfd\xac[\x9fB<\xee\xd84\xb6\xbc\x984I\x02\xdcބq\f\x16\xe1\x9f\xd7\xf0\x17h\x98\x90\x99\xbf\xcd\x18\b{Z9q\xc3\x1d/%\xd2\"\x04\\Ձ\a\x84}/T\xfdi\x95\x0fk\x04\x9b\x96q\v\r\x9a5^\xa8ϒW\x98\a\x86a\xf7\x88\xb7^O\x1c<\xf1w\x00\x00\x00\xff\xff_\xe3\x98\xca", +} + +var coreFiles = []string{"io/99_UnitTest.io"} diff --git a/coroutine.go b/coroutine.go deleted file mode 100644 index 4c66cea..0000000 --- a/coroutine.go +++ /dev/null @@ -1,135 +0,0 @@ -package iolang - -// A Coroutine holds control flow and debugging for a single Io coroutine. -type Coroutine struct { - // Control is the control flow channel for the VM associated with this - // coroutine. - Control chan RemoteStop - // Debug is a pointer to the VM's Debug flag. - Debug *uint32 -} - -// tagCoro is the Tag type for Coroutine objects. -type tagCoro struct{} - -func (tagCoro) Activate(vm *VM, self, target, locals, context *Object, msg *Message) *Object { - return self -} - -func (tagCoro) CloneValue(value interface{}) interface{} { - return Coroutine{Control: make(chan RemoteStop, 1)} -} - -func (tagCoro) String() string { - return "Coroutine" -} - -// CoroutineTag is the Tag for Coroutine objects. Activate returns the -// coroutine. CloneValue creates a new control flow channel and no debugging. -var CoroutineTag tagCoro - -// VMFor creates a VM for a given Coroutine object so that it can run Io code. -// The new VM does not have debugging enabled. Panics if the object is not a -// Coroutine. -func (vm *VM) VMFor(coro *Object) *VM { - coro.Lock() - c := coro.Value.(Coroutine) - r := &VM{ - Lobby: vm.Lobby, - Core: vm.Core, - Addons: vm.Addons, - BaseObject: vm.BaseObject, - True: vm.True, - False: vm.False, - Nil: vm.Nil, - Operators: vm.Operators, - Sched: vm.Sched, - Control: c.Control, - Coro: coro, - addonmaps: vm.addonmaps, - numberCache: vm.numberCache, - StartTime: vm.StartTime, - } - c.Debug = &r.Debug - coro.Value = c - coro.Unlock() - return r -} - -func (vm *VM) initCoroutine() { - slots := Slots{ - "currentCoroutine": vm.NewCFunction(CoroutineCurrentCoroutine, nil), - "implementation": vm.NewString("goroutines"), - "implementationVersion": vm.NewNumber(0), // in case API changes - "isCurrent": vm.NewCFunction(CoroutineIsCurrent, CoroutineTag), - "pause": vm.NewCFunction(CoroutinePause, CoroutineTag), - "resume": vm.NewCFunction(CoroutineResume, CoroutineTag), - "run": vm.NewCFunction(CoroutineRun, CoroutineTag), - "setMessageDebugging": vm.NewCFunction(CoroutineSetMessageDebugging, CoroutineTag), // debugger.go - "type": vm.NewString("Coroutine"), - "yield": vm.NewCFunction(CoroutineYield, CoroutineTag), - } - slots["resumeLater"] = slots["resume"] - value := Coroutine{Control: vm.Control, Debug: &vm.Debug} - vm.Coro = vm.ObjectWith(slots, []*Object{vm.BaseObject}, value, CoroutineTag) - vm.SetSlot(vm.Core, "Coroutine", vm.Coro) -} - -// run starts this inactive coroutine by activating its main slot. It should be -// used in a go statement. -func (vm *VM) run() { - vm.Perform(vm.Coro, vm.Coro, vm.IdentMessage("main")) - vm.Sched.Finish(vm) -} - -// CoroutineCurrentCoroutine is a Coroutine method. -// -// currentCoroutine returns the current coroutine. -func CoroutineCurrentCoroutine(vm *VM, target, locals *Object, msg *Message) *Object { - return vm.Coro -} - -// CoroutineIsCurrent is a Coroutine method. -// -// isCurrent returns whether the receiver is the current coroutine. -func CoroutineIsCurrent(vm *VM, target, locals *Object, msg *Message) *Object { - return vm.IoBool(vm.Coro == target) -} - -// CoroutinePause is a Coroutine method. -// -// pause stops the coroutine's execution until it is sent the resume message. It -// will finish evaluating its current message before pausing. If all coroutines -// are paused, the program ends. -func CoroutinePause(vm *VM, target, locals *Object, msg *Message) *Object { - target.Value.(Coroutine).Control <- RemoteStop{Control: PauseStop} - return target -} - -// CoroutineResume is a Coroutine method. -// -// resume unpauses the coroutine, or starts it if it was not started. -func CoroutineResume(vm *VM, target, locals *Object, msg *Message) *Object { - target.Value.(Coroutine).Control <- RemoteStop{Control: ResumeStop} - return target -} - -// CoroutineRun is a Coroutine method. -// -// run starts this coroutine if it was not already running. The coroutine -// activates its main slot, which by default performs the message in runMessage -// upon runTarget using runLocals. -func CoroutineRun(vm *VM, target, locals *Object, msg *Message) *Object { - coro := vm.VMFor(target) - vm.Sched.Start(coro) - go coro.run() - return target -} - -// CoroutineYield is a Coroutine method. -// -// yield reschedules all goroutines. -func CoroutineYield(vm *VM, target, locals *Object, msg *Message) *Object { - target.Value.(Coroutine).Control <- RemoteStop{} - return target -} diff --git a/debugger.go b/debugger.go deleted file mode 100644 index bf6cb20..0000000 --- a/debugger.go +++ /dev/null @@ -1,119 +0,0 @@ -package iolang - -import ( - "fmt" - "sync/atomic" -) - -// Debugger is a debugger for a single coroutine. -type Debugger struct { - // msgs is the queue of messages to process. - msgs chan debugMessage -} - -// debugMessage holds a context to be debugged and a channel to indicate it has. -type debugMessage struct { - msg *Message - target *Object - locals *Object - dbg chan struct{} -} - -// tagDebugger is the Tag type for Debugger objects. -type tagDebugger struct{} - -func (tagDebugger) Activate(vm *VM, self, target, locals, context *Object, msg *Message) *Object { - return self -} - -func (tagDebugger) CloneValue(value interface{}) interface{} { - return Debugger{msgs: make(chan debugMessage)} -} - -func (tagDebugger) String() string { - return "Debugger" -} - -// DebuggerTag is the tag for Debugger objects. Activate returns self. -// CloneValue creates a new debug channel. -var DebuggerTag tagDebugger - -func (vm *VM) initDebugger() { - slots := Slots{ - "start": vm.NewCFunction(DebuggerStart, DebuggerTag), - } - debugger := Debugger{msgs: make(chan debugMessage)} - vm.coreInstall("Debugger", slots, debugger, DebuggerTag) -} - -// DebugMessage does nothing if debugging is disabled for the VM; otherwise, it -// sends the execution context to the debugger and waits for it to be handled. -func (vm *VM) DebugMessage(target, locals *Object, msg *Message) { - if atomic.LoadUint32(&vm.Debug) != 0 { - debug, ok := vm.GetLocalSlot(vm.Core, "Debugger") - if !ok { - return - } - if debug.Tag() != DebuggerTag { - return - } - dbg := debug.Value.(Debugger) - done := make(chan struct{}) - dbg.msgs <- debugMessage{msg: msg, target: target, locals: locals, dbg: done} - <-done - } -} - -// DebuggerStart is a Debugger method. -// -// start begins processing the debugger's message queue. This behaves like -// loop(self vmWillSendMessage(nextMessageInQueue)). -func DebuggerStart(vm *VM, target, locals *Object, msg *Message) *Object { - dbg := target.Value.(Debugger) - // This method is run in a new coroutine, but we don't want the coroutine - // to show up in scheduler methods. Tell the scheduler we've finished, even - // though we really haven't and probably won't. - vm.Sched.Finish(vm) - pf := vm.IdentMessage("vmWillSendMessage") - for { - select { - case work := <-dbg.msgs: - vm.SetSlots(target, Slots{ - "messageCoroutine": vm.Coro, - "messageSelf": work.target, - "messageLocals": work.locals, - "message": vm.MessageObject(work.msg), - }) - r, stop := vm.Perform(target, locals, pf) - close(work.dbg) - switch stop { - case NoStop, ContinueStop: // do nothing - case BreakStop: - return r - case ExceptionStop, ExitStop: - return vm.Stop(r, stop) - default: - panic(fmt.Errorf("iolang: invalid Stop: %v", stop)) - } - case <-vm.Sched.Alive: - return nil - } - } -} - -// CoroutineSetMessageDebugging is a Coroutine method. -// -// setMessageDebugging activates the debugger for the coroutine. -func CoroutineSetMessageDebugging(vm *VM, target, locals *Object, msg *Message) *Object { - coro := target.Value.(Coroutine) - r, stop := msg.EvalArgAt(vm, locals, 0) - if stop != NoStop { - return vm.Stop(r, stop) - } - if vm.AsBool(r) { - atomic.StoreUint32(coro.Debug, 1) - } else { - atomic.StoreUint32(coro.Debug, 0) - } - return target -} diff --git a/go.mod b/go.mod index 2ccda97..4c2fbcf 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/zephyrtronium/iolang go 1.13 require ( - github.com/zephyrtronium/contains v0.0.0-20190813150354-f7df53f08000 + github.com/zephyrtronium/contains v0.0.0-20191109101209-403ebe7b0b60 gitlab.com/variadico/lctime v0.0.0-20190211022338-49aae8a53d11 - golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe + golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 golang.org/x/text v0.3.2 - golang.org/x/tools v0.0.0-20191101200257-8dbcdeb83d3f - gopkg.in/yaml.v2 v2.2.4 + golang.org/x/tools v0.0.0-20200313205530-4303120df7d8 + gopkg.in/yaml.v2 v2.2.8 ) diff --git a/go.sum b/go.sum index fae1a91..9a07c6d 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,24 @@ +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zephyrtronium/contains v0.0.0-20190813150354-f7df53f08000 h1:rsL4vYsl1EVDklYnVDq2etRQv7jZtFu2gkv4OzKKFbo= github.com/zephyrtronium/contains v0.0.0-20190813150354-f7df53f08000/go.mod h1:9VfULMvEEtBLQ93TyMwEFa2rMZj232ZMs7HA/dx31Eg= +github.com/zephyrtronium/contains v0.0.0-20191109101209-403ebe7b0b60 h1:spBs4SQ3oIUAGBh3fZLGWjQ6iAiEtmxmM5o68l5mTH8= +github.com/zephyrtronium/contains v0.0.0-20191109101209-403ebe7b0b60/go.mod h1:9VfULMvEEtBLQ93TyMwEFa2rMZj232ZMs7HA/dx31Eg= gitlab.com/variadico/lctime v0.0.0-20190211022338-49aae8a53d11 h1:Ly1b+G8N/HhmtygI9E+LkVZ3LitOpHHyVKbBORfOd7o= gitlab.com/variadico/lctime v0.0.0-20190211022338-49aae8a53d11/go.mod h1:oBjcCtUE+aWYiW4NuGN+eK3OK3efpcyKphbDv1Y508M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -15,8 +26,15 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IK golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191101200257-8dbcdeb83d3f h1:+QO45yvqhfD79HVNFPAgvstYLFye8zA+rd0mHFsGV9s= golang.org/x/tools v0.0.0-20191101200257-8dbcdeb83d3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200313205530-4303120df7d8 h1:gkI/wGGwpcG5W4hLCzZNGxA4wzWBGGDStRI1MrjDl2Q= +golang.org/x/tools v0.0.0-20200313205530-4303120df7d8/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/addon.go b/internal/addon.go similarity index 71% rename from addon.go rename to internal/addon.go index a0c4be9..f86c750 100644 --- a/addon.go +++ b/internal/addon.go @@ -1,12 +1,15 @@ -package iolang +package internal import ( "os" "path/filepath" "plugin" - "sync" ) +// This file contains the machinery to implement addons, but the addon loading +// system is not installed by default. To enable it, import the package +// github.com/zephyrtronium/iolang/coreext/addon. + // Addon is an interface via which an addon is loaded into a VM. // // Addons in iolang are separate packages, which may be linked dynamically (on @@ -69,6 +72,41 @@ type addontriple struct { ch chan struct{} } +// InitAddon initializes the addon system on the VM. This is called only by the +// initializer from the addon core extension. +func InitAddon(vm *VM) { + vm.addonmaps = &addonmaps{ + addons: make(chan addontriple), + scan: make(chan *os.File), + protos: make(map[string]Addon), + opened: make(map[*plugin.Plugin]bool), + inited: make(map[string]bool), + known: make(map[string]Addon), + } + go vm.manageAddons() +} + +// Install installs an addon proto by appending it to Lobby's protos and +// setting the corresponding slot in Addons. +func (vm *VM) Install(name string, proto *Object) { + vm.SetSlot(vm.Addons, name, proto) + l := vm.Lobby + l.AppendProto(vm.NewObject(Slots{name: proto})) +} + +// LoadAddon loads an addon. It returns a channel that closes when the addon is +// loaded. +func (vm *VM) LoadAddon(addon Addon) <-chan struct{} { + ch := make(chan struct{}) + vm.addonmaps.addons <- addontriple{vm, addon, ch} + return ch +} + +// ScanForAddons sends a directory that the VM should scan for addons. +func ScanForAddons(vm *VM, file *os.File) { + vm.addonmaps.scan <- file +} + func (vm *VM) manageAddons() { maps := vm.addonmaps for { @@ -108,58 +146,6 @@ func (vm *VM) manageAddons() { } } -func (vm *VM) initAddon() { - addonOnce.Do(initAddonOnce) - vm.addonmaps = &addonmaps{ - addons: make(chan addontriple), - scan: make(chan *os.File), - protos: make(map[string]Addon), - opened: make(map[*plugin.Plugin]bool), - inited: make(map[string]bool), - known: make(map[string]Addon), - } - go vm.manageAddons() - slots := Slots{ - "havePlugins": vm.IoBool(havePlugins), - "open": vm.NewCFunction(AddonOpen, nil), - "scanForNewAddons": vm.NewCFunction(AddonScanForNewAddons, nil), - "type": vm.NewString("Addon"), - } - vm.coreInstall("Addon", slots, nil, nil) -} - -// Install installs an addon proto by appending it to Lobby's protos and -// setting the corresponding slot in Addons. -func (vm *VM) Install(name string, proto *Object) { - vm.SetSlot(vm.Addons, name, proto) - l := vm.Lobby - l.AppendProto(vm.NewObject(Slots{name: proto})) -} - -// LoadAddon loads an addon. It returns a channel that closes when the addon is -// loaded. -func (vm *VM) LoadAddon(addon Addon) <-chan struct{} { - ch := make(chan struct{}) - vm.addonmaps.addons <- addontriple{vm, addon, ch} - return ch -} - -// reallyLoadAddon is the method the addon manager calls to set up an addon. -func (vm *VM) reallyLoadAddon(addon Addon) { - for _, dep := range addon.Depends() { - if vm.addonmaps.inited[dep] { - continue - } - da, ok := vm.addonmaps.known[dep] - if !ok { - vm.RaiseExceptionf("unable to load %s (dependency of %s): not in any scanned directory", dep, addon.Name()) - return - } - <-vm.LoadAddon(da) - } - addon.Init(vm) -} - // findAddons yields addonplugins for Io addons in the given directory. func findAddons(file *os.File) <-chan addonplugin { ch := make(chan addonplugin, 8) @@ -196,71 +182,18 @@ func findAddons(file *os.File) <-chan addonplugin { return ch } -// AddonOpen is an Addon method. -// -// open loads the addon at the receiver's path and returns the addon's object. -func AddonOpen(vm *VM, target, locals *Object, msg *Message) *Object { - p, proto := vm.GetSlot(target, "path") - if proto == nil { - return vm.RaiseExceptionf("addon path unset") - } - p.Lock() - path, ok := p.Value.(Sequence) - if !ok { - p.Unlock() - return vm.RaiseExceptionf("addon path must be Sequence, not %s", vm.TypeName(p)) - } - plug, err := plugin.Open(path.String()) - p.Unlock() - if err != nil { - return vm.IoError(err) - } - open, err := plug.Lookup("IoAddon") - if err != nil { - return vm.IoError(err) - } - f, ok := open.(func() Addon) - if !ok { - return vm.RaiseExceptionf("%s is not an iolang addon", path) - } - <-vm.LoadAddon(f()) - return target -} - -// AddonScanForNewAddons is an Addon method. -// -// scanForNewAddons marks a directory to be scanned for new addons. -func AddonScanForNewAddons(vm *VM, target, locals *Object, msg *Message) *Object { - path, exc, stop := msg.StringArgAt(vm, locals, 0) - if stop != NoStop { - return vm.Stop(exc, stop) - } - file, err := os.Open(path) - if err != nil { - return vm.IoError(err) - } - fi, err := file.Stat() - if err != nil { - return vm.IoError(err) - } - if !fi.IsDir() { - return vm.RaiseExceptionf("%s is not a directory", path) - } - vm.addonmaps.scan <- file - return target -} - -// havePlugins indicates whether Go's plugin system is available on the current -// system. Currently this should become true on Linux or Darwin with cgo -// enabled, but the cgo requirement might drop in the future (unlikely) and more -// platforms might be added (likely). -var havePlugins = false - -func initAddonOnce() { - _, err := plugin.Open("/dev/null") - if err == nil || err.Error() != "plugin: not implemented" { - havePlugins = true +// reallyLoadAddon is the method the addon manager calls to set up an addon. +func (vm *VM) reallyLoadAddon(addon Addon) { + for _, dep := range addon.Depends() { + if vm.addonmaps.inited[dep] { + continue + } + da, ok := vm.addonmaps.known[dep] + if !ok { + vm.RaiseExceptionf("unable to load %s (dependency of %s): not in any scanned directory", dep, addon.Name()) + return + } + <-vm.LoadAddon(da) } + addon.Init(vm) } - -var addonOnce sync.Once diff --git a/block.go b/internal/block.go similarity index 99% rename from block.go rename to internal/block.go index 7890f7f..b206e8f 100644 --- a/block.go +++ b/internal/block.go @@ -1,4 +1,4 @@ -package iolang +package internal import ( "bytes" diff --git a/bool.go b/internal/bool.go similarity index 98% rename from bool.go rename to internal/bool.go index 098e2f4..faa4fb6 100644 --- a/bool.go +++ b/internal/bool.go @@ -1,4 +1,4 @@ -package iolang +package internal func (vm *VM) initTrue() { vm.True.SetProtos(vm.BaseObject) diff --git a/cfunction.go b/internal/cfunction.go similarity index 99% rename from cfunction.go rename to internal/cfunction.go index 2bb7f7a..c9b5b49 100644 --- a/cfunction.go +++ b/internal/cfunction.go @@ -1,4 +1,4 @@ -package iolang +package internal import ( "path" diff --git a/control.go b/internal/control.go similarity index 99% rename from control.go rename to internal/control.go index e83d541..1ef23f6 100644 --- a/control.go +++ b/internal/control.go @@ -1,4 +1,4 @@ -package iolang +package internal import "fmt" diff --git a/internal/coroutine.go b/internal/coroutine.go new file mode 100644 index 0000000..5eff927 --- /dev/null +++ b/internal/coroutine.go @@ -0,0 +1,74 @@ +package internal + +// A Coroutine holds control flow and debugging for a single Io coroutine. +type Coroutine struct { + // Control is the control flow channel for the VM associated with this + // coroutine. + Control chan RemoteStop + // Debug is a pointer to the VM's Debug flag. + Debug *uint32 +} + +// RunCoro starts an inactive coroutine by activating its main slot. It should +// be used in a go statement. +func RunCoro(vm *VM) { + vm.Perform(vm.Coro, vm.Coro, vm.IdentMessage("main")) + vm.Sched.Finish(vm) +} + +// run is a temporary proxy to RunCoro(vm). +func (vm *VM) run() { + RunCoro(vm) +} + +// tagCoro is the Tag type for Coroutine objects. +type tagCoro struct{} + +func (tagCoro) Activate(vm *VM, self, target, locals, context *Object, msg *Message) *Object { + return self +} + +func (tagCoro) CloneValue(value interface{}) interface{} { + return Coroutine{Control: make(chan RemoteStop, 1)} +} + +func (tagCoro) String() string { + return "Coroutine" +} + +// CoroutineTag is the Tag for Coroutine objects. Activate returns the +// coroutine. CloneValue creates a new control flow channel and no debugging. +var CoroutineTag tagCoro + +// VMFor creates a VM for a given Coroutine object so that it can run Io code. +// The new VM does not have debugging enabled. Panics if the object is not a +// Coroutine. +func (vm *VM) VMFor(coro *Object) *VM { + coro.Lock() + c := coro.Value.(Coroutine) + r := &VM{ + Lobby: vm.Lobby, + Core: vm.Core, + Addons: vm.Addons, + BaseObject: vm.BaseObject, + True: vm.True, + False: vm.False, + Nil: vm.Nil, + Operators: vm.Operators, + Sched: vm.Sched, + Control: c.Control, + Coro: coro, + addonmaps: vm.addonmaps, + numberCache: vm.numberCache, + StartTime: vm.StartTime, + } + c.Debug = &r.Debug + coro.Value = c + coro.Unlock() + return r +} + +func (vm *VM) initCoroutine() { + value := Coroutine{Control: vm.Control, Debug: &vm.Debug} + vm.Coro = vm.ObjectWith(nil, []*Object{vm.BaseObject}, value, CoroutineTag) +} diff --git a/internal/debugger.go b/internal/debugger.go new file mode 100644 index 0000000..b22fded --- /dev/null +++ b/internal/debugger.go @@ -0,0 +1,70 @@ +package internal + +import "sync/atomic" + +// Debugger is a debugger for a single coroutine. +type Debugger struct { + // msgs is the queue of messages to process. + msgs chan DebugMessage +} + +// DebuggerWith allows coreext/debugger to create a Debugger object. +func DebuggerWith(msgs chan DebugMessage) Debugger { + return Debugger{msgs: msgs} +} + +// DebuggerChan allows coreext/debugger to retrieve a Debugger's message +// channel. +func DebuggerChan(d Debugger) chan DebugMessage { + return d.msgs +} + +// DebugMessage holds a context to be debugged and a channel to indicate it has. +type DebugMessage struct { + Msg *Message + Target *Object + Locals *Object + Dbg chan struct{} +} + +// tagDebugger is the Tag type for Debugger objects. +type tagDebugger struct{} + +func (tagDebugger) Activate(vm *VM, self, target, locals, context *Object, msg *Message) *Object { + return self +} + +func (tagDebugger) CloneValue(value interface{}) interface{} { + return Debugger{msgs: make(chan DebugMessage)} +} + +func (tagDebugger) String() string { + return "Debugger" +} + +// DebuggerTag is the tag for Debugger objects. Activate returns self. +// CloneValue creates a new debug channel. +var DebuggerTag tagDebugger + +// DebugMessage does nothing if debugging is disabled for the VM; otherwise, it +// sends the execution context to the debugger and waits for it to be handled. +func (vm *VM) DebugMessage(target, locals *Object, msg *Message) { + if atomic.LoadUint32(&vm.Debug) != 0 { + vm.debugMessageSlow(target, locals, msg) + } +} + +// debugMessageSlow is an outlined path of DebugMessage. +func (vm *VM) debugMessageSlow(target, locals *Object, msg *Message) { + debug, ok := vm.GetLocalSlot(vm.Core, "Debugger") + if !ok { + return + } + if debug.Tag() != DebuggerTag { + return + } + dbg := debug.Value.(Debugger) + done := make(chan struct{}) + dbg.msgs <- DebugMessage{Msg: msg, Target: target, Locals: locals, Dbg: done} + <-done +} diff --git a/exception.go b/internal/exception.go similarity index 99% rename from exception.go rename to internal/exception.go index 80f3860..a27639f 100644 --- a/exception.go +++ b/internal/exception.go @@ -1,4 +1,4 @@ -package iolang +package internal import "fmt" diff --git a/io/00_Object.io b/internal/io/00_Object.io similarity index 96% rename from io/00_Object.io rename to internal/io/00_Object.io index a89a5c7..cf728e8 100644 --- a/io/00_Object.io +++ b/internal/io/00_Object.io @@ -1,6 +1,5 @@ Object do( - print := method(File standardOutput write(getSlot("self") asString); getSlot("self")) - println := method(File standardOutput write(getSlot("self") asString, "\n"); getSlot("self")) + println := method(getSlot("self") print; "\n" print) write := method(call evalArgs foreach(print); getSlot("self")) writeln := method(call evalArgs foreach(print); "\n" print; getSlot("self")) // Use setSlot directly to circumvent operator shuffling. @@ -168,9 +167,6 @@ Object do( m setIsActivatable(true) getSlot("m") ) - - yield := method(Coroutine currentCoroutine yield) - pause := method(Coroutine currentCoroutine pause) deprecatedWarning := method(alt, m := call sender call ifNil(Exception raise("deprecatedWarning must be called from within a Block")) message diff --git a/io/01_Call.io b/internal/io/01_Call.io similarity index 100% rename from io/01_Call.io rename to internal/io/01_Call.io diff --git a/io/02_Sequence.io b/internal/io/02_Sequence.io similarity index 100% rename from io/02_Sequence.io rename to internal/io/02_Sequence.io diff --git a/io/04_Exception.io b/internal/io/04_Exception.io similarity index 100% rename from io/04_Exception.io rename to internal/io/04_Exception.io diff --git a/io/05_Number.io b/internal/io/05_Number.io similarity index 100% rename from io/05_Number.io rename to internal/io/05_Number.io diff --git a/io/06_List.io b/internal/io/06_List.io similarity index 100% rename from io/06_List.io rename to internal/io/06_List.io diff --git a/io/10_Map.io b/internal/io/10_Map.io similarity index 100% rename from io/10_Map.io rename to internal/io/10_Map.io diff --git a/io/12_Block.io b/internal/io/12_Block.io similarity index 100% rename from io/12_Block.io rename to internal/io/12_Block.io diff --git a/io/13_Message.io b/internal/io/13_Message.io similarity index 100% rename from io/13_Message.io rename to internal/io/13_Message.io diff --git a/io/14_OperatorTable.io b/internal/io/14_OperatorTable.io similarity index 100% rename from io/14_OperatorTable.io rename to internal/io/14_OperatorTable.io diff --git a/io/16_System.io b/internal/io/16_System.io similarity index 100% rename from io/16_System.io rename to internal/io/16_System.io diff --git a/io/17_Stop.io b/internal/io/17_Stop.io similarity index 100% rename from io/17_Stop.io rename to internal/io/17_Stop.io diff --git a/lex.go b/internal/lex.go similarity index 99% rename from lex.go rename to internal/lex.go index e0f3df1..61fff43 100644 --- a/lex.go +++ b/internal/lex.go @@ -1,4 +1,4 @@ -package iolang +package internal import ( "bytes" diff --git a/lex_test.go b/internal/lex_test.go similarity index 99% rename from lex_test.go rename to internal/lex_test.go index cefd736..a3f3425 100644 --- a/lex_test.go +++ b/internal/lex_test.go @@ -1,4 +1,4 @@ -package iolang +package internal import ( "bufio" diff --git a/list.go b/internal/list.go similarity index 99% rename from list.go rename to internal/list.go index 34350a1..f67df86 100644 --- a/list.go +++ b/internal/list.go @@ -1,4 +1,4 @@ -package iolang +package internal import ( "fmt" diff --git a/map.go b/internal/map.go similarity index 99% rename from map.go rename to internal/map.go index d76e184..cffe383 100644 --- a/map.go +++ b/internal/map.go @@ -1,4 +1,4 @@ -package iolang +package internal import ( "fmt" diff --git a/message.go b/internal/message.go similarity index 99% rename from message.go rename to internal/message.go index 4176e57..7c9d99d 100644 --- a/message.go +++ b/internal/message.go @@ -1,4 +1,4 @@ -package iolang +package internal import ( "bytes" diff --git a/message_test.go b/internal/message_test.go similarity index 74% rename from message_test.go rename to internal/message_test.go index 8f263d3..25c775c 100644 --- a/message_test.go +++ b/internal/message_test.go @@ -1,26 +1,29 @@ -package iolang +package internal_test import ( "strings" "sync/atomic" "testing" + + "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/testutils" ) // TestPerform tests that objects can receive and possibly forward messages to // activate slots and produce appropriate results. func TestPerform(t *testing.T) { - vm := TestingVM() + vm := testutils.VM() pt := &performTester{obj: vm.NewObject(nil)} - res := vm.ObjectWith(nil, []*Object{vm.BaseObject}, pt, performTesterTag{}) - anc := vm.NewObject(Slots{"t": res}) + res := vm.ObjectWith(nil, []*iolang.Object{vm.BaseObject}, pt, performTesterTag{}) + anc := vm.NewObject(iolang.Slots{"t": res}) target := anc.Clone() vm.SetSlot(target, "forward", vm.NewCFunction(performTestForward, nil)) tm := vm.IdentMessage("t") cases := map[string]struct { - o *Object - msg *Message + o *iolang.Object + msg *iolang.Message succeed bool - v *Object + v *iolang.Object }{ "Local": {anc, tm, true, res}, "Ancestor": {target, tm, true, res}, @@ -33,12 +36,12 @@ func TestPerform(t *testing.T) { r, stop := vm.Perform(c.o, c.o, c.msg) var n int32 if c.succeed { - if stop != NoStop { + if stop != iolang.NoStop { t.Errorf("wrong control flow: want %v (NoStop), got %v (%v)", c.v, r, stop) } n = 1 } else { - if stop != ExceptionStop { + if stop != iolang.ExceptionStop { t.Errorf("wrong control flow: want (ExceptionStop), got %v (%v)", r, stop) } } @@ -52,12 +55,12 @@ func TestPerform(t *testing.T) { type performTester struct { act int32 - obj *Object + obj *iolang.Object } type performTesterTag struct{} -func (performTesterTag) Activate(vm *VM, self, target, locals, context *Object, msg *Message) *Object { +func (performTesterTag) Activate(vm *iolang.VM, self, target, locals, context *iolang.Object, msg *iolang.Message) *iolang.Object { v := self.Value.(*performTester) atomic.AddInt32(&v.act, 1) return v.obj.Activate(vm, target, locals, context, msg) @@ -71,7 +74,7 @@ func (performTesterTag) String() string { return "performTester" } -func performTestForward(vm *VM, target, locals *Object, msg *Message) *Object { +func performTestForward(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { nn := strings.ToLower(msg.Name()) if v, proto := vm.GetSlot(target, nn); proto != nil { return v.Activate(vm, target, locals, proto, vm.IdentMessage(nn)) @@ -79,14 +82,16 @@ func performTestForward(vm *VM, target, locals *Object, msg *Message) *Object { return vm.RaiseExceptionf("%s does not respond to %s", vm.TypeName(target), msg.Name()) } +var BenchDummy *iolang.Object + func BenchmarkPerform(b *testing.B) { - vm := TestingVM() + vm := testutils.VM() o := vm.BaseObject.Clone().Clone().Clone().Clone().Clone().Clone().Clone().Clone().Clone().Clone().Clone() p := vm.BaseObject.Clone() nm := vm.IdentMessage("type") cm := vm.IdentMessage("thisContext") am := vm.IdentMessage("clone") - cases := map[string]*Object{ + cases := map[string]*iolang.Object{ "Local": vm.BaseObject, "Proto": p, "Ancestor": o, @@ -117,11 +122,11 @@ func BenchmarkPerform(b *testing.B) { } func BenchmarkPerformParallel(b *testing.B) { - vm := TestingVM() + vm := testutils.VM() o := vm.BaseObject.Clone().Clone().Clone().Clone().Clone().Clone().Clone().Clone().Clone().Clone().Clone() p := vm.BaseObject.Clone() m := vm.IdentMessage("type") - cases := map[string]*Object{ + cases := map[string]*iolang.Object{ "Local": vm.BaseObject, "Proto": p, "Ancestor": o, @@ -140,7 +145,7 @@ func BenchmarkPerformParallel(b *testing.B) { } func BenchmarkEvalParallel(b *testing.B) { - vm := TestingVM() + vm := testutils.VM() m, err := vm.Parse(strings.NewReader(`benchmarkValue := benchmarkValue + 1`), "BenchmarkPerformParallel") if err != nil { panic(err) @@ -157,15 +162,15 @@ func BenchmarkEvalParallel(b *testing.B) { // TestPerformNilResult tests that VM.Perform always converts nil to VM.Nil. func TestPerformNilResult(t *testing.T) { - vm := TestingVM() + vm := testutils.VM() cf := vm.NewCFunction(nilResult, nil) - o := vm.NewObject(Slots{ + o := vm.NewObject(iolang.Slots{ "f": cf, "forward": cf, }) cases := map[string]struct { - o *Object - msg *Message + o *iolang.Object + msg *iolang.Message }{ "HaveSlot": {o, vm.IdentMessage("f")}, "Forward": {o, vm.IdentMessage("g")}, @@ -176,35 +181,13 @@ func TestPerformNilResult(t *testing.T) { if r != vm.Nil { t.Errorf("result not VM.Nil: want %T@%p, got %T@%p", vm.Nil, vm.Nil, r, r) } - if stop != NoStop { + if stop != iolang.NoStop { t.Errorf("wrong control flow: want NoStop, got %v", stop) } }) } } -func nilResult(vm *VM, target, locals *Object, msg *Message) *Object { +func nilResult(vm *iolang.VM, target, locals *iolang.Object, msg *iolang.Message) *iolang.Object { return nil } - -// TestPerformSynchronous tests that multiple coroutines updating the same slot -// see each others' changes. -func TestPerformSynchronous(t *testing.T) { - vm := TestingVM() - vm.SetSlot(vm.Lobby, "testValue", vm.NewNumber(0)) - defer vm.RemoveSlot(vm.Lobby, "testValue") - r, stop := vm.DoString("100 repeat(@1000 repeat(testValue = testValue + 1)); wait(0.2); while(Scheduler coroCount > 0, yield)", "TestPerformSynchronous") - if stop != NoStop { - t.Errorf("%s (%v)", vm.AsString(r), stop) - } - x, ok := vm.GetLocalSlot(vm.Lobby, "testValue") - if !ok { - t.Fatal("no slot testValue on Lobby") - } - if x.Tag() != NumberTag { - t.Fatalf("Lobby testValue has tag %#v, not %#v", x.Tag(), NumberTag) - } - if x.Value.(float64) != 100000 { - t.Errorf("Lobby testValue has wrong value: want 100000, got %g", x.Value.(float64)) - } -} diff --git a/number.go b/internal/number.go similarity index 99% rename from number.go rename to internal/number.go index 6505340..ed8fb26 100644 --- a/number.go +++ b/internal/number.go @@ -1,4 +1,4 @@ -package iolang +package internal import ( "encoding/binary" diff --git a/number_test.go b/internal/number_test.go similarity index 87% rename from number_test.go rename to internal/number_test.go index 702aacd..cb2436f 100644 --- a/number_test.go +++ b/internal/number_test.go @@ -1,12 +1,14 @@ -package iolang +package internal_test import ( "testing" + + "github.com/zephyrtronium/iolang/testutils" ) // TestNumberCache tests that certain numbers always have identical objects. func TestNumberCache(t *testing.T) { - vm := TestingVM() + vm := testutils.VM() // These constants are independent from but (as of writing) equal to those // used to construct the number cache. It is allowable for more numbers to // be cached, but fewer is not allowable, as there is real(ish?) code that diff --git a/object.go b/internal/object.go similarity index 98% rename from object.go rename to internal/object.go index f252e31..ea4e3f7 100644 --- a/object.go +++ b/internal/object.go @@ -1,4 +1,4 @@ -package iolang +package internal import ( "fmt" @@ -174,9 +174,8 @@ func (vm *VM) initObject() { "appendProto": vm.NewCFunction(ObjectAppendProto, nil), "asGoRepr": vm.NewCFunction(ObjectAsGoRepr, nil), "asString": vm.NewCFunction(ObjectAsString, nil), - "asyncSend": vm.NewCFunction(ObjectAsyncSend, nil), // future.go - "block": vm.NewCFunction(ObjectBlock, nil), // block.go - "break": vm.NewCFunction(ObjectBreak, nil), // control.go + "block": vm.NewCFunction(ObjectBlock, nil), // block.go + "break": vm.NewCFunction(ObjectBreak, nil), // control.go "clone": vm.NewCFunction(ObjectClone, nil), "cloneWithoutInit": vm.NewCFunction(ObjectCloneWithoutInit, nil), "compare": vm.NewCFunction(ObjectCompare, nil), @@ -190,7 +189,6 @@ func (vm *VM) initObject() { "evalArgAndReturnSelf": vm.NewCFunction(ObjectEvalArgAndReturnSelf, nil), "for": vm.NewCFunction(ObjectFor, nil), // control.go "foreachSlot": vm.NewCFunction(ObjectForeachSlot, nil), - "futureSend": vm.NewCFunction(ObjectFutureSend, nil), // future.go "getLocalSlot": vm.NewCFunction(ObjectGetLocalSlot, nil), "getSlot": vm.NewCFunction(ObjectGetSlot, nil), "hasLocalSlot": vm.NewCFunction(ObjectHasLocalSlot, nil), @@ -209,6 +207,7 @@ func (vm *VM) initObject() { "perform": vm.NewCFunction(ObjectPerform, nil), "performWithArgList": vm.NewCFunction(ObjectPerformWithArgList, nil), "prependProto": vm.NewCFunction(ObjectPrependProto, nil), + "print": vm.NewCFunction(ObjectPrint, nil), "protos": vm.NewCFunction(ObjectProtos, nil), "removeAllProtos": vm.NewCFunction(ObjectRemoveAllProtos, nil), "removeAllSlots": vm.NewCFunction(ObjectRemoveAllSlots, nil), @@ -926,6 +925,24 @@ func ObjectPrependProto(vm *VM, target, locals *Object, msg *Message) *Object { return target } +// ObjectPrint is an Object method. +// +// print converts the receiver to a string and prints it. +func ObjectPrint(vm *VM, target, locals *Object, msg *Message) *Object { + r, stop := vm.Perform(target, locals, vm.IdentMessage("asString")) + if stop != NoStop { + return vm.Stop(r, stop) + } + r.Lock() + defer r.Unlock() + if r.Tag() == SequenceTag { + fmt.Print(r.Value) + } else { + fmt.Printf("%v_%p", r.Tag(), r.Value) + } + return target +} + // ObjectRemoveAllProtos is an Object method. // // removeAllProtos removes all protos from the object. diff --git a/internal/object_test.go b/internal/object_test.go new file mode 100644 index 0000000..5403d6f --- /dev/null +++ b/internal/object_test.go @@ -0,0 +1,703 @@ +package internal_test + +import ( + "testing" + + "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/testutils" +) + +// TestGetSlot tests that GetSlot can find local and ancestor slots, and that no +// object is checked more than once. +func TestGetSlot(t *testing.T) { + vm := testutils.VM() + cases := map[string]struct { + o, v, p *iolang.Object + slot string + }{ + "Local": {vm.Lobby, vm.Lobby, vm.Lobby, "Lobby"}, + "Ancestor": {vm.Lobby, vm.BaseObject, vm.Core, "Object"}, + "Never": {vm.Lobby, nil, nil, "fail to find"}, + } + // TODO: test case where the lookup chain expands into the general case, + // then returns to the single-proto case + for name, c := range cases { + t.Run(name, func(t *testing.T) { + v, p := vm.GetSlot(c.o, c.slot) + if v != c.v { + t.Errorf("slot %s found wrong object: have %T@%p, want %T@%p", c.slot, v, v, c.v, c.v) + } + if p != c.p { + t.Errorf("slot %s found on wrong proto: have %T@%p, want %T@%p", c.slot, p, p, c.p, c.p) + } + }) + } +} + +// BenchmarkGetSlot benchmarks VM.GetSlot in various depths of search. +func BenchmarkGetSlot(b *testing.B) { + vm := testutils.VM() + o := vm.BaseObject.Clone().Clone().Clone().Clone().Clone().Clone().Clone().Clone().Clone().Clone().Clone() + cases := map[string]struct { + o *iolang.Object + slot string + }{ + "Local": {vm.Lobby, "Lobby"}, + "Proto": {vm.BaseObject, "Lobby"}, + "Ancestor": {o, "Lobby"}, + "Missing": {vm.Lobby, "Lobby fail to find"}, + } + for name, c := range cases { + b.Run(name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + BenchDummy, _ = vm.GetSlot(c.o, c.slot) + } + }) + } +} + +// TestGetLocalSlot tests that GetLocalSlot can find local but not ancestor +// slots. +func TestGetLocalSlot(t *testing.T) { + vm := testutils.VM() + cases := map[string]struct { + o, v *iolang.Object + ok bool + slot string + }{ + "Local": {vm.Lobby, vm.Lobby, true, "Lobby"}, + "Ancestor": {vm.Lobby, nil, false, "Object"}, + "Never": {vm.Lobby, nil, false, "fail to find"}, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + v, ok := vm.GetLocalSlot(c.o, c.slot) + if ok != c.ok { + t.Errorf("slot %s has wrong presence: have %v, want %v", c.slot, ok, c.ok) + } + if v != c.v { + t.Errorf("slot %s found wrong object: have %T@%p, want %T@%p", c.slot, v, v, c.v, c.v) + } + }) + } +} + +// TestObjectGoActivate tests that an Object set to be activatable activates its +// activate slot when activated. +func TestObjectGoActivate(t *testing.T) { + vm := testutils.VM() + o := vm.NewObject(iolang.Slots{}) + vm.SetSlot(vm.Lobby, "TestObjectActivate", o) + cases := map[string]testutils.SourceTestCase{ + "InactiveNoActivate": {Source: `getSlot("TestObjectActivate") removeSlot("activate") setIsActivatable(false)`, Pass: testutils.PassEqual(o)}, + "InactiveActivate": {Source: `getSlot("TestObjectActivate") do(activate := Lobby) setIsActivatable(false)`, Pass: testutils.PassEqual(o)}, + "ActiveNoActivate": {Source: `getSlot("TestObjectActivate") removeSlot("activate") setIsActivatable(true)`, Pass: testutils.PassEqual(o)}, + "ActiveActivate": {Source: `getSlot("TestObjectActivate") do(activate := Lobby) setIsActivatable(true)`, Pass: testutils.PassEqual(vm.Lobby)}, + } + for name, c := range cases { + t.Run(name, c.TestFunc("TestObjectActivate/"+name)) + } + vm.RemoveSlot(vm.Lobby, "TestObjectActivate") +} + +// TestObjectSlots tests that a new VM Object has the slots we expect. +func TestObjectSlots(t *testing.T) { + slots := []string{ + "", + "!=", + "-", + "..", + "<", + "<=", + "==", + ">", + ">=", + "?", + // "@", // TODO: coreext + // "@@", // TODO: coreext + // "actorProcessQueue", + // "actorRun", + "addTrait", + "ancestorWithSlot", + "ancestors", + "and", + "appendProto", + "apropos", + "asBoolean", + "asGoRepr", + "asSimpleString", + "asString", + // "asyncSend", // TODO: coreext + "block", + "break", + "clone", + "cloneWithoutInit", + "compare", + "contextWithSlot", + "continue", + // "coroDo", // TODO: coreext + // "coroDoLater", // TODO: coreext + // "coroFor", // TODO: coreext + // "coroWith", // TODO: coreext + // "currentCoro", // TODO: coreext + "deprecatedWarning", + "do", + "doFile", + "doMessage", + "doRelativeFile", + "doString", + "evalArg", + "evalArgAndReturnNil", + "evalArgAndReturnSelf", + "for", + "foreachSlot", + // "futureSend", // TODO: coreext + "getLocalSlot", + "getSlot", + // "handleActorException", + "hasLocalSlot", + "hasProto", + "hasSlot", + "if", + "ifError", + "ifNil", + "ifNilEval", + "ifNonNil", + "ifNonNilEval", + "in", + "init", + "inlineMethod", + "isActivatable", + "isError", + "isIdenticalTo", + "isKindOf", + "isLaunchScript", + "isNil", + "isTrue", + "justSerialized", + "launchFile", + "lazySlot", + "lexicalDo", + "list", + "loop", + // "memorySize", + "message", + "method", + "newSlot", + "not", + "or", + // "pause", // TODO: coreext + "perform", + "performWithArgList", + "prependProto", + "print", + "println", + "proto", + "protos", + "raiseIfError", + "relativeDoFile", + "removeAllProtos", + "removeAllSlots", + "removeProto", + "removeSlot", + "resend", + "return", + "returnIfError", + "returnIfNonNil", + "serialized", + "serializedSlots", + "serializedSlotsWithNames", + "setIsActivatable", + "setProto", + "setProtos", + "setSlot", + "setSlotWithType", + "shallowCopy", + "slotDescriptionMap", + "slotNames", + "slotSummary", + "slotValues", + "stopStatus", + "super", + "switch", + "thisContext", + "thisLocalContext", + "thisMessage", + "try", + "type", + "uniqueHexId", + "uniqueId", + "updateSlot", + "wait", + "while", + "write", + "writeln", + // "yield", // TODO: coreext + } + testutils.CheckSlots(t, testutils.VM().BaseObject, slots) +} + +// TestObjectScript tests Object methods by executing Io scripts. +func TestObjectMethods(t *testing.T) { + vm := testutils.VM() + list012 := vm.NewList(vm.NewNumber(0), vm.NewNumber(1), vm.NewNumber(2)) + listxyz := vm.NewList(vm.NewString("x"), vm.NewString("y"), vm.NewString("z")) + // If this test runs before TestLobbySlots, any new slots that tests create + // will cause that to fail. To circumvent this, we provide an object to + // carry test values, then remove it once all tests have run. This object + // initially carries default test configuration values. + config := iolang.Slots{ + // coroWaitTime is the time in seconds that coros should wait while + // testing methods that spawn new coroutines. The new coroutines may + // take any amount of time to execute, as the VM does not wait for them + // to finish. + "coroWaitTime": vm.NewNumber(0.02), + // obj is a generic object with some slots to simplify some tests. + "obj": vm.NewObject(iolang.Slots{"x": vm.NewNumber(1), "y": vm.NewNumber(2), "z": vm.NewNumber(0)}), + // manyProtos is an object that has many protos. + "manyProtos": vm.ObjectWith(nil, []*iolang.Object{vm.BaseObject, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil}, nil, nil), + // manyProtosToRemove is an object that has many protos to test that + // removeAllProtos succeeds. + "manyProtosToRemove": vm.ObjectWith(nil, []*iolang.Object{vm.BaseObject, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil}, nil, nil), + } + vm.SetSlot(vm.Lobby, "testValues", vm.NewObject(config)) + cases := map[string]map[string]testutils.SourceTestCase{ + "evalArg": { + "evalArg": {Source: `evalArg(Lobby)`, Pass: testutils.PassEqual(vm.Lobby)}, + "continue": {Source: `evalArg(continue; Lobby)`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `evalArg(Exception raise; Lobby)`, Pass: testutils.PassFailure()}, + }, + "notEqual": { + "0!=1": {Source: `0 !=(1)`, Pass: testutils.PassIdentical(vm.True)}, + "0!=0": {Source: `0 !=(0)`, Pass: testutils.PassIdentical(vm.False)}, + "1!=0": {Source: `1 !=(0)`, Pass: testutils.PassIdentical(vm.True)}, + "incomparable": {Source: `Lobby !=(Core)`, Pass: testutils.PassIdentical(vm.True)}, + "identical": {Source: `Lobby !=(Lobby)`, Pass: testutils.PassIdentical(vm.False)}, + "continue": {Source: `Lobby !=(continue); Lobby`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `Lobby !=(Exception raise); Lobby`, Pass: testutils.PassFailure()}, + }, + "minus": { + "-1": {Source: `-(1)`, Pass: testutils.PassEqual(vm.NewNumber(-1))}, + "-seq": {Source: `-("abc")`, Pass: testutils.PassFailure()}, + }, + "dotdot": { + "1..2": {Source: `1 ..(2)`, Pass: testutils.PassEqual(vm.NewString("12"))}, + }, + "less": { + "0<1": {Source: `0 <(1)`, Pass: testutils.PassIdentical(vm.True)}, + "0<0": {Source: `0 <(0)`, Pass: testutils.PassIdentical(vm.False)}, + "1<0": {Source: `1 <(0)`, Pass: testutils.PassIdentical(vm.False)}, + "incomparable": {Source: `Lobby <(Core)`, Pass: testutils.PassSuccess()}, + "identical": {Source: `Lobby <(Lobby)`, Pass: testutils.PassIdentical(vm.False)}, + "continue": {Source: `0 <(continue); Lobby`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `0 <(Exception raise); Lobby`, Pass: testutils.PassFailure()}, + }, + "lessEqual": { + "0<=1": {Source: `0 <=(1)`, Pass: testutils.PassIdentical(vm.True)}, + "0<=0": {Source: `0 <=(0)`, Pass: testutils.PassIdentical(vm.True)}, + "1<=0": {Source: `1 <=(0)`, Pass: testutils.PassIdentical(vm.False)}, + "incomparable": {Source: `Lobby <=(Core)`, Pass: testutils.PassSuccess()}, + "identical": {Source: `Lobby <=(Lobby)`, Pass: testutils.PassIdentical(vm.True)}, + "continue": {Source: `0 <=(continue); Lobby`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `0 <=(Exception raise); Lobby`, Pass: testutils.PassFailure()}, + }, + "equal": { + "0==1": {Source: `0 ==(1)`, Pass: testutils.PassIdentical(vm.False)}, + "0==0": {Source: `0 ==(0)`, Pass: testutils.PassIdentical(vm.True)}, + "1==0": {Source: `1 ==(0)`, Pass: testutils.PassIdentical(vm.False)}, + "incomparable": {Source: `Lobby ==(Core)`, Pass: testutils.PassIdentical(vm.False)}, + "identical": {Source: `Lobby ==(Lobby)`, Pass: testutils.PassIdentical(vm.True)}, + "continue": {Source: `Lobby ==(continue); Lobby`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `Lobby ==(Exception raise); Lobby`, Pass: testutils.PassFailure()}, + }, + "greater": { + "0>1": {Source: `0 >(1)`, Pass: testutils.PassIdentical(vm.False)}, + "0>0": {Source: `0 >(0)`, Pass: testutils.PassIdentical(vm.False)}, + "1>0": {Source: `1 >(0)`, Pass: testutils.PassIdentical(vm.True)}, + "incomparable": {Source: `Lobby >(Core)`, Pass: testutils.PassSuccess()}, + "identical": {Source: `Lobby >(Lobby)`, Pass: testutils.PassIdentical(vm.False)}, + "continue": {Source: `0 >(continue); Lobby`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `0 >(Exception raise); Lobby`, Pass: testutils.PassFailure()}, + }, + "greaterEqual": { + "0>=1": {Source: `0 >=(1)`, Pass: testutils.PassIdentical(vm.False)}, + "0>=0": {Source: `0 >=(0)`, Pass: testutils.PassIdentical(vm.True)}, + "1>=0": {Source: `1 >=(0)`, Pass: testutils.PassIdentical(vm.True)}, + "incomparable": {Source: `Lobby >=(Core)`, Pass: testutils.PassSuccess()}, + "identical": {Source: `Lobby >=(Lobby)`, Pass: testutils.PassIdentical(vm.True)}, + "continue": {Source: `0 >=(continue); Lobby`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `0 >=(Exception raise); Lobby`, Pass: testutils.PassFailure()}, + }, + "question": { + "have": {Source: `?Lobby`, Pass: testutils.PassIdentical(vm.Lobby)}, + "not": {Source: `?nothing`, Pass: testutils.PassIdentical(vm.Nil)}, + "effect": {Source: `testValues questionEffect := 0; ?testValues questionEffect := 1; testValues questionEffect`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "continue": {Source: `?continue`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `?Exception raise`, Pass: testutils.PassFailure()}, + "parens": {Source: `testValues questionEffect := 0; ?(testValues questionEffect := 1) ?(nothing); testValues questionEffect`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + }, + "addTrait": { + "all": {Source: `Object clone addTrait(testValues obj)`, Pass: testutils.PassEqualSlots(iolang.Slots{"x": vm.NewNumber(1), "y": vm.NewNumber(2), "z": vm.NewNumber(0)})}, + "res": {Source: `Object clone do(x := 4) addTrait(testValues obj, Map clone do(atPut("x", "w")))`, Pass: testutils.PassEqualSlots(iolang.Slots{"w": vm.NewNumber(1), "x": vm.NewNumber(4), "y": vm.NewNumber(2), "z": vm.NewNumber(0)})}, + "unres": {Source: `Object clone do(x := 4) addTrait(testValues obj, Map clone do(atPut("w", "x")))`, Pass: testutils.PassFailure()}, + "badres": {Source: `Object clone do(x := 4) addTrait(testValues obj, Map clone do(atPut("x", 1)))`, Pass: testutils.PassFailure()}, + "short": {Source: `Object clone addTrait`, Pass: testutils.PassFailure()}, + }, + "ancestorWithSlot": { + "local": {Source: `Number ancestorWithSlot("abs")`, Pass: testutils.PassIdentical(vm.Nil)}, + "proto": {Source: `Lobby clone ancestorWithSlot("Lobby")`, Pass: testutils.PassIdentical(vm.Lobby)}, + "localInProtos": {Source: `Lobby ancestorWithSlot("Lobby")`, Pass: testutils.PassIdentical(vm.Lobby)}, + "nowhere": {Source: `Lobby ancestorWithSlot("this slot doesn't exist")`, Pass: testutils.PassIdentical(vm.Nil)}, + "bad": {Source: `Lobby ancestorWithSlot(0)`, Pass: testutils.PassFailure()}, + "continue": {Source: `Lobby ancestorWithSlot(continue); Lobby`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `Lobby ancestorWithSlot(Exception raise); Lobby`, Pass: testutils.PassFailure()}, + }, + "ancestors": { + "ancestors": {Source: `testValues anc := Object clone ancestors; testValues anc containsIdenticalTo(Object) and testValues anc containsIdenticalTo(Core)`, Pass: testutils.PassIdentical(vm.True)}, + }, + "and": { + "true": {Source: `and(Object)`, Pass: testutils.PassIdentical(vm.True)}, + "false": {Source: `and(Object clone do(isTrue := false))`, Pass: testutils.PassIdentical(vm.False)}, + }, + // TODO: apropos needs special tests, since it prints + "appendProto": { + "appendProto": {Source: `Object clone do(appendProto(Lobby)) protos containsIdenticalTo(Lobby)`, Pass: testutils.PassIdentical(vm.True)}, + "continue": {Source: `Object clone appendProto(continue); Lobby`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `Object clone appendProto(Exception raise); Lobby`, Pass: testutils.PassFailure()}, + }, + "asBoolean": { + "asBoolean": {Source: `Object asBoolean`, Pass: testutils.PassIdentical(vm.True)}, + }, + // asGoRepr gets no tests // + "asSimpleString": { + "isSequence": {Source: `Object asSimpleString`, Pass: testutils.PassTag(iolang.SequenceTag)}, + }, + "asString": { + "isSequence": {Source: `Object asString`, Pass: testutils.PassTag(iolang.SequenceTag)}, + }, + "block": { + "noMessage": {Source: `block`, Pass: testutils.PassTag(iolang.BlockTag)}, + "exception": {Source: `block(Exception raise)`, Pass: testutils.PassSuccess()}, + }, + "break": { + "break": {Source: `break`, Pass: testutils.PassControl(vm.Nil, iolang.BreakStop)}, + "value": {Source: `break(Lobby)`, Pass: testutils.PassControl(vm.Lobby, iolang.BreakStop)}, + "continue": {Source: `break(continue)`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `break(Exception raise)`, Pass: testutils.PassFailure()}, + }, + "clone": { + "new": {Source: `Object clone != Object`, Pass: testutils.PassIdentical(vm.True)}, + "proto": {Source: `Object clone protos containsIdenticalTo(Object)`, Pass: testutils.PassIdentical(vm.True)}, + "init": {Source: `testValues initValue := 0; Object clone do(init := method(Lobby testValues initValue = 1)) clone; testValues initValue`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + }, + "cloneWithoutInit": { + "new": {Source: `Object cloneWithoutInit != Object`, Pass: testutils.PassIdentical(vm.True)}, + "proto": {Source: `Object cloneWithoutInit protos containsIdenticalTo(Object)`, Pass: testutils.PassIdentical(vm.True)}, + "init": {Source: `testValues noInitValue := 0; Object clone do(init := method(Lobby testValues noInitValue = 1)) cloneWithoutInit; testValues noInitValue`, Pass: testutils.PassEqual(vm.NewNumber(0))}, + }, + "compare": { + "incomparable": {Source: `Object compare("string")`, Pass: testutils.PassTag(iolang.NumberTag)}, + "continue": {Source: `Object compare(continue)`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `Object compare(Exception raise)`, Pass: testutils.PassFailure()}, + }, + "contextWithSlot": { + "local": {Source: `Number contextWithSlot("abs")`, Pass: testutils.PassEqual(vm.NewNumber(0))}, + "proto": {Source: `Lobby clone contextWithSlot("Lobby")`, Pass: testutils.PassIdentical(vm.Lobby)}, + "localInProtos": {Source: `Lobby contextWithSlot("Lobby")`, Pass: testutils.PassIdentical(vm.Lobby)}, + "nowhere": {Source: `Lobby contextWithSlot("this slot doesn't exist")`, Pass: testutils.PassIdentical(vm.Nil)}, + "bad": {Source: `Lobby contextWithSlot(0)`, Pass: testutils.PassFailure()}, + "continue": {Source: `Lobby contextWithSlot(continue); Lobby`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `Lobby contextWithSlot(Exception raise); Lobby`, Pass: testutils.PassFailure()}, + }, + "continue": { + "continue": {Source: `continue`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "value": {Source: `continue(Lobby)`, Pass: testutils.PassControl(vm.Lobby, iolang.ContinueStop)}, + "exception": {Source: `continue(Exception raise)`, Pass: testutils.PassFailure()}, + }, + "deprecatedWarning": { + "context": {Source: `deprecatedWarning`, Pass: testutils.PassFailure()}, + // TODO: deprecatedWarning needs special tests, since it prints + }, + "do": { + "result": {Source: `Object do(Lobby)`, Pass: testutils.PassIdentical(vm.BaseObject)}, + "context": {Source: `testValues doValue := 0; testValues do(doValue := 1); testValues doValue`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "continue": {Source: `do(continue)`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `do(Exception raise)`, Pass: testutils.PassFailure()}, + }, + // TODO: doFile needs special testing + "doMessage": { + "doMessage": {Source: `testValues doMessageValue := 0; testValues doMessage(message(doMessageValue = 1)); testValues doMessageValue`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "context": {Source: `testValues doMessageValue := 2; doMessage(message(testValues doMessageValue = doMessageValue + 1), testValues); testValues doMessageValue`, Pass: testutils.PassEqual(vm.NewNumber(3))}, + "bad": {Source: `testValues doMessage("doMessageValue := 4")`, Pass: testutils.PassFailure()}, + }, + // TODO: doRelativeFile needs special testing + "doString": { + "doString": {Source: `testValues doStringValue := 0; testValues doString("doStringValue = 1"); testValues doStringValue`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "label": {Source: `testValues doStringLabel := "foo"; testValues doString("doStringLabel = thisMessage label", "bar"); testValues doStringLabel`, Pass: testutils.PassEqual(vm.NewString("bar"))}, + "bad": {Source: `testValues doString(message(doStringValue := 4))`, Pass: testutils.PassFailure()}, + }, + "evalArgAndReturnNil": { + "result": {Source: `evalArgAndReturnNil(Lobby)`, Pass: testutils.PassIdentical(vm.Nil)}, + "eval": {Source: `testValues evalNil := 0; evalArgAndReturnNil(testValues evalNil := 1); testValues evalNil`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "continue": {Source: `evalArgAndReturnNil(continue)`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `evalArgAndReturnNil(Exception raise)`, Pass: testutils.PassFailure()}, + }, + "evalArgAndReturnSelf": { + "result": {Source: `evalArgAndReturnSelf(nil)`, Pass: testutils.PassIdentical(vm.Lobby)}, + "eval": {Source: `testValues evalSelf := 0; evalArgAndReturnSelf(testValues evalSelf := 1); testValues evalSelf`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "continue": {Source: `evalArgAndReturnSelf(continue)`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `evalArgAndReturnSelf(Exception raise)`, Pass: testutils.PassFailure()}, + }, + "for": { + "result": {Source: `testValues do(forResult := for(forCtr, 0, 2, forCtr * 2)) forResult`, Pass: testutils.PassEqual(vm.NewNumber(4))}, + "nothing": {Source: `testValues do(forResult := for(forCtr, 2, 0, Exception raise)) forResult`, Pass: testutils.PassIdentical(vm.Nil)}, + "order": {Source: `testValues do(forList := list; for(forCtr, 0, 2, forList append(forCtr))) forList`, Pass: testutils.PassEqual(list012)}, + "step": {Source: `testValues do(forList := list; for(forCtr, 2, 0, -1, forList append(forCtr))) forList reverse`, Pass: testutils.PassEqual(list012)}, + "continue": {Source: `testValues do(forList := list; for(forCtr, 0, 2, forList append(forCtr); continue; forList append(forCtr))) forList`, Pass: testutils.PassEqual(list012)}, + "continueResult": {Source: `testValues do(forResult := for(forCtr, 0, 2, continue(forCtr * 2))) forResult`, Pass: testutils.PassEqual(vm.NewNumber(4))}, + "break": {Source: `testValues do(for(forCtr, 0, 2, break)) forCtr`, Pass: testutils.PassEqual(vm.NewNumber(0))}, + "breakResult": {Source: `testValues do(forResult := for(forCtr, 0, 2, break(4))) forResult`, Pass: testutils.PassEqual(vm.NewNumber(4))}, + "return": {Source: `testValues do(for(forCtr, 0, 2, return))`, Pass: testutils.PassControl(vm.Nil, iolang.ReturnStop)}, + "exception": {Source: `testValues do(for(forCtr, 0, 2, Exception raise))`, Pass: testutils.PassFailure()}, + "name": {Source: `testValues do(for(forCtrNew no responderino, 0, 2, nil))`, Pass: testutils.PassLocalSlots([]string{"forCtrNew"}, []string{"no", "responderino"})}, + "short": {Source: `testValues do(for(forCtr))`, Pass: testutils.PassFailure()}, + "long": {Source: `testValues do(for(forCtr, 0, 1, 2, 3, 4, 5))`, Pass: testutils.PassFailure()}, + }, + "foreachSlot": { + "result": {Source: `testValues do(foreachSlotResult := obj foreachSlot(value, 4)) foreachSlotResult`, Pass: testutils.PassEqual(vm.NewNumber(4))}, + "nothing": {Source: `testValues do(foreachSlotResult := Object clone foreachSlot(value, Exception raise)) foreachSlotResult`, Pass: testutils.PassIdentical(vm.Nil)}, + "key": {Source: `testValues do(forList := list; obj foreachSlot(slot, value, forList append(slot))) forList sort`, Pass: testutils.PassEqual(listxyz)}, + "value": {Source: `testValues do(forList := list; obj foreachSlot(slot, value, forList append(value))) forList sort`, Pass: testutils.PassEqual(list012)}, + "continue": {Source: `testValues do(forList := list; obj foreachSlot(slot, value, forList append(slot); continue; forList append(slot))) forList sort`, Pass: testutils.PassEqual(listxyz)}, + "continueResult": {Source: `testValues do(foreachSlotResult := obj foreachSlot(slot, value, continue(Lobby))) foreachSlotResult`, Pass: testutils.PassIdentical(vm.Lobby)}, + "break": {Source: `testValues do(foreachSlotIters := 0; obj foreachSlot(slot, value, foreachSlotIters = foreachSlotIters + 1; break)) foreachSlotIters`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "breakResult": {Source: `testValues do(foreachSlotResult := obj foreachSlot(slot, value, break(Lobby))) foreachSlotResult`, Pass: testutils.PassIdentical(vm.Lobby)}, + "return": {Source: `testValues do(obj foreachSlot(slot, value, return))`, Pass: testutils.PassControl(vm.Nil, iolang.ReturnStop)}, + "exception": {Source: `testValues do(obj foreachSlot(slot, value, Exception raise))`, Pass: testutils.PassFailure()}, + "name": {Source: `testValues do(obj foreachSlot(slotNew no responderino, valueNew no responderino, nil))`, Pass: testutils.PassLocalSlots([]string{"slotNew", "valueNew"}, []string{"no", "responderino"})}, + "short": {Source: `testValues do(obj foreachSlot(nil))`, Pass: testutils.PassFailure()}, + "long": {Source: `testValues do(obj foreachSlot(slot, value, 1, 2, 3))`, Pass: testutils.PassFailure()}, + }, + "getLocalSlot": { + "local": {Source: `getLocalSlot("Lobby")`, Pass: testutils.PassIdentical(vm.Lobby)}, + "ancestor": {Source: `Lobby clone getLocalSlot("Lobby")`, Pass: testutils.PassIdentical(vm.Nil)}, + "never": {Source: `getLocalSlot("this slot does not exist")`, Pass: testutils.PassIdentical(vm.Nil)}, + "bad": {Source: `getLocalSlot(Lobby)`, Pass: testutils.PassFailure()}, + }, + "getSlot": { + "local": {Source: `getSlot("Lobby")`, Pass: testutils.PassIdentical(vm.Lobby)}, + "ancestor": {Source: `Lobby clone getSlot("Lobby")`, Pass: testutils.PassIdentical(vm.Lobby)}, + "never": {Source: `getSlot("this slot does not exist")`, Pass: testutils.PassIdentical(vm.Nil)}, + "bad": {Source: `getSlot(Lobby)`, Pass: testutils.PassFailure()}, + }, + "hasLocalSlot": { + "local": {Source: `hasLocalSlot("Lobby")`, Pass: testutils.PassIdentical(vm.True)}, + "ancestor": {Source: `Lobby clone hasLocalSlot("Lobby")`, Pass: testutils.PassIdentical(vm.False)}, + "never": {Source: `hasLocalSlot("this slot does not exist")`, Pass: testutils.PassIdentical(vm.False)}, + "bad": {Source: `hasLocalSlot(Lobby)`, Pass: testutils.PassFailure()}, + }, + "hasSlot": { + "local": {Source: `hasSlot("Lobby")`, Pass: testutils.PassIdentical(vm.True)}, + "ancestor": {Source: `Lobby clone hasSlot("Lobby")`, Pass: testutils.PassIdentical(vm.True)}, + "never": {Source: `hasSlot("this slot does not exist")`, Pass: testutils.PassIdentical(vm.False)}, + "bad": {Source: `hasSlot(Lobby)`, Pass: testutils.PassFailure()}, + }, + "if": { + "evalTrue": {Source: `testValues ifResult := nil; if(true, testValues ifResult := true, testValues ifResult := false); testValues ifResult`, Pass: testutils.PassIdentical(vm.True)}, + "evalFalse": {Source: `testValues ifResult := nil; if(false, testValues ifResult := true, testValues ifResult := false); testValues ifResult`, Pass: testutils.PassIdentical(vm.False)}, + "resultTrue": {Source: `if(true, 1, 0)`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "resultFalse": {Source: `if(false, 1, 0)`, Pass: testutils.PassEqual(vm.NewNumber(0))}, + "resultTrueDiadic": {Source: `if(true, 1)`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "resultFalseDiadic": {Source: `if(false, 1)`, Pass: testutils.PassIdentical(vm.False)}, + "resultTrueMonadic": {Source: `if(true)`, Pass: testutils.PassIdentical(vm.True)}, + "resultFalseMonadic": {Source: `if(false)`, Pass: testutils.PassIdentical(vm.False)}, + "continue1": {Source: `if(continue, nil, nil)`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "continue2": {Source: `if(true, continue, nil)`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "continue3": {Source: `if(false, nil, continue)`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception1": {Source: `if(Exception raise, nil, nil)`, Pass: testutils.PassFailure()}, + "exception2": {Source: `if(true, Exception raise, nil)`, Pass: testutils.PassFailure()}, + "exception3": {Source: `if(false, nil, Exception raise)`, Pass: testutils.PassFailure()}, + }, + "ifError": { + "noEval": {Source: `testValues ifErrorResult := nil; ifError(testValues ifErrorResult := true); testValues ifErrorResult`, Pass: testutils.PassIdentical(vm.Nil)}, + "self": {Source: `ifError(nil)`, Pass: testutils.PassIdentical(vm.Lobby)}, + }, + "ifNil": { + "noEval": {Source: `testValues ifNilResult := false; ifNil(testValues ifNilResult := true); testValues ifNilResult`, Pass: testutils.PassIdentical(vm.False)}, + "self": {Source: `ifNil(nil)`, Pass: testutils.PassIdentical(vm.Lobby)}, + }, + "ifNilEval": { + "noEval": {Source: `testValues ifNilEvalResult := false; ifNilEval(testValues ifNilEvalResult := true); testValues ifNilEvalResult`, Pass: testutils.PassIdentical(vm.False)}, + "self": {Source: `ifNilEval(nil)`, Pass: testutils.PassIdentical(vm.Lobby)}, + }, + "ifNonNil": { + "result": {Source: `ifNonNil(nil)`, Pass: testutils.PassIdentical(vm.Lobby)}, + "eval": {Source: `testValues ifNonNilResult := 0; ifNonNil(testValues ifNonNilResult := 1); testValues ifNonNilResult`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "continue": {Source: `ifNonNil(continue)`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `ifNonNil(Exception raise)`, Pass: testutils.PassFailure()}, + }, + "ifNonNilEval": { + "evalArg": {Source: `ifNonNilEval(Lobby)`, Pass: testutils.PassEqual(vm.Lobby)}, + "continue": {Source: `ifNonNilEval(continue; Lobby)`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `ifNonNilEval(Exception raise; Lobby)`, Pass: testutils.PassFailure()}, + }, + "in": { + "contains": {Source: `testValues contains := method(self inResult := true); Object in(testValues); testValues inResult`, Pass: testutils.PassIdentical(vm.True)}, + }, + "init": { + "self": {Source: `init`, Pass: testutils.PassIdentical(vm.Lobby)}, + }, + "inlineMethod": { + "type": {Source: `inlineMethod(nil)`, Pass: testutils.PassTag(iolang.MessageTag)}, + "text": {Source: `inlineMethod(nil) name`, Pass: testutils.PassEqual(vm.NewString("nil"))}, + "next": {Source: `inlineMethod(true nil) next name`, Pass: testutils.PassEqual(vm.NewString("nil"))}, + "prev": {Source: `inlineMethod(true) previous`, Pass: testutils.PassIdentical(vm.Nil)}, + }, + "isActivatable": { + "false": {Source: `Object isActivatable`, Pass: testutils.PassIdentical(vm.False)}, + }, + "isError": { + "false": {Source: `Object isError`, Pass: testutils.PassIdentical(vm.False)}, + }, + "isIdenticalTo": { + "0===0": {Source: `123456789 isIdenticalTo(123456789)`, Pass: testutils.PassIdentical(vm.False)}, + "unidentical": {Source: `Lobby isIdenticalTo(Core)`, Pass: testutils.PassIdentical(vm.False)}, + "identical": {Source: `Lobby isIdenticalTo(Lobby)`, Pass: testutils.PassIdentical(vm.True)}, + "continue": {Source: `Lobby isIdenticalTo(continue); Lobby`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `Lobby isIdenticalTo(Exception raise); Lobby`, Pass: testutils.PassFailure()}, + }, + "isKindOf": { + "self": {Source: `Object isKindOf(Object)`, Pass: testutils.PassIdentical(vm.True)}, + "proto": {Source: `Object clone isKindOf(Object)`, Pass: testutils.PassIdentical(vm.True)}, + "ancestor": {Source: `0 isKindOf(Lobby)`, Pass: testutils.PassIdentical(vm.True)}, + "not": {Source: `Object isKindOf(Exception)`, Pass: testutils.PassIdentical(vm.False)}, + "continue": {Source: `isKindOf(continue; Lobby)`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `isKindOf(Exception raise; Lobby)`, Pass: testutils.PassFailure()}, + }, + // isLaunchScript needs special testing + "isNil": { + "false": {Source: `Object isNil`, Pass: testutils.PassIdentical(vm.False)}, + }, + "isTrue": { + "true": {Source: `Object isTrue`, Pass: testutils.PassIdentical(vm.True)}, + }, + "justSerialized": { + "same": {Source: `testValues justSerializedStream := SerializationStream clone; testValues obj justSerialized(testValues justSerializedStream); doString(testValues justSerializedStream output)`, Pass: testutils.PassEqualSlots(vm.GetAllSlots(config["obj"]))}, + }, + // launchFile needs special testing + "lazySlot": { + "initial1": {Source: `lazySlot(1)`, Pass: testutils.PassUnequal(vm.NewNumber(1))}, + "initial2": {Source: `testValues lazySlot("lazySlotValue", 1); testValues getSlot("lazySlotValue")`, Pass: testutils.PassUnequal(vm.NewNumber(1))}, + "eval1": {Source: `testValues lazySlotValue := lazySlot(1); testValues lazySlotValue`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "eval2": {Source: `testValues lazySlot("lazySlotValue", 1); testValues lazySlotValue`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "replace1": {Source: `testValues lazySlotValue := lazySlot(1); testValues lazySlotValue; testValues getSlot("lazySlotValue")`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "replace2": {Source: `testValues lazySlot("lazySlotValue", 1); testValues lazySlotValue; testValues getSlot("lazySlotValue")`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "once1": {Source: `testValues lazySlotCount := 0; testValues lazySlotValue := lazySlot(testValues lazySlotCount = testValues lazySlotCount + 1; 1); testValues lazySlotValue; testValues lazySlotValue; testValues lazySlotValue; testValues lazySlotCount`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "once2": {Source: `testValues lazySlotCount := 0; testValues lazySlot("lazySlotValue", testValues lazySlotCount = testValues lazySlotCount + 1; 1); testValues lazySlotValue; testValues lazySlotValue; testValues lazySlotValue; testValues lazySlotCount`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + }, + "lexicalDo": { + // These tests have to be careful not to call lexicalDo on an + // object that already has the lexical context as a proto. + "result": {Source: `Lobby lexicalDo(Object)`, Pass: testutils.PassIdentical(vm.Lobby)}, + "context": {Source: `testValues lexicalDoValue := 0; testValues lexicalDo(lexicalDoValue := 1); testValues lexicalDoValue`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "lexical": {Source: `testValues lexicalDo(lexicalDoHasProto := thisContext protos containsIdenticalTo(Lobby)); testValues lexicalDoHasProto`, Pass: testutils.PassIdentical(vm.True)}, + "continue": {Source: `Object clone lexicalDo(continue)`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `Object clone lexicalDo(Exception raise)`, Pass: testutils.PassFailure()}, + }, + "list": { + // Object list is List with, but still test it in both places. + "zero": {Source: `Object list`, Pass: testutils.PassEqual(vm.NewList())}, + "one": {Source: `Object list(nil)`, Pass: testutils.PassEqual(vm.NewList(vm.Nil))}, + "five": {Source: `Object list(nil, nil, nil, nil, nil)`, Pass: testutils.PassEqual(vm.NewList(vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil))}, + "continue": {Source: `Object list(nil, nil, nil, nil, continue)`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `Object list(nil, nil, nil, nil, Exception raise)`, Pass: testutils.PassFailure()}, + }, + "loop": { + "loop": {Source: `testValues loopCount := 0; loop(testValues loopCount = testValues loopCount + 1; if(testValues loopCount >= 5, break)); testValues loopCount`, Pass: testutils.PassEqual(vm.NewNumber(5))}, + "continue": {Source: `testValues loopCount := 0; loop(testValues loopCount = testValues loopCount + 1; if(testValues loopCount < 5, continue); break); testValues loopCount`, Pass: testutils.PassEqual(vm.NewNumber(5))}, + "break": {Source: `testValues loopCount := 0; loop(break; testValues loopCount = 1); testValues loopCount`, Pass: testutils.PassEqual(vm.NewNumber(0))}, + "return": {Source: `testValues loopCount := 0; loop(return nil; testValues loopCount = 1); testValues loopCount`, Pass: testutils.PassControl(vm.Nil, iolang.ReturnStop)}, + "exception": {Source: `testValues loopCount := 0; loop(Exception raise; testValues loopCount = 1); testValues loopCount`, Pass: testutils.PassFailure()}, + }, + "message": { + "nothing": {Source: `message`, Pass: testutils.PassIdentical(vm.Nil)}, + "message": {Source: `message(message)`, Pass: testutils.PassTag(iolang.MessageTag)}, + "continue": {Source: `message(continue)`, Pass: testutils.PassTag(iolang.MessageTag)}, + "exception": {Source: `message(Exception raise)`, Pass: testutils.PassTag(iolang.MessageTag)}, + }, + "method": { + "noMessage": {Source: `method`, Pass: testutils.PassTag(iolang.BlockTag)}, + "exception": {Source: `method(Exception raise)`, Pass: testutils.PassSuccess()}, + }, + "newSlot": { + "makes": {Source: `testValues newSlot("newSlotValue"); testValues`, Pass: testutils.PassLocalSlots([]string{"newSlotValue", "setNewSlotValue"}, nil)}, + "value": {Source: `testValues newSlot("newSlotValue", 1); testValues newSlotValue`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "setter": {Source: `testValues newSlot("newSlotValue", 1); testValues setNewSlotValue(2); testValues newSlotValue`, Pass: testutils.PassEqual(vm.NewNumber(2))}, + "result": {Source: `testValues newSlot("newSlotValue", 1)`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + }, + "not": { + "nil": {Source: `Object not`, Pass: testutils.PassIdentical(vm.Nil)}, + }, + "or": { + // It might be nice to change or to be a coalescing operator like + // Python's or, by changing it to thisContext. + "true": {Source: `Object or`, Pass: testutils.PassIdentical(vm.True)}, + }, + "perform": { + "string": {Source: `testValues performValue := 0; testValues perform("setSlot", "performValue", 1); testValues performValue`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "message": {Source: `testValues performValue := 0; testValues perform(message(setSlot("performValue", 1))); testValues performValue`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "single": {Source: `testValues performValue := 0; testValues perform(message(nil; setSlot("performValue", 1))); testValues performValue`, Pass: testutils.PassEqual(vm.NewNumber(0))}, + "several": {Source: `testValues perform(message(nil), message(nil))`, Pass: testutils.PassFailure()}, + "wrong": {Source: `testValues perform(nil)`, Pass: testutils.PassFailure()}, + "continue": {Source: `testValues perform(continue)`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `testValues perform(Exception raise)`, Pass: testutils.PassFailure()}, + }, + "performWithArgList": { + "perform": {Source: `testValues performWithValue := 0; testValues performWithArgList("setSlot", list("performWithValue", 1)); testValues performWithValue`, Pass: testutils.PassEqual(vm.NewNumber(1))}, + "wrong": {Source: `testValues performWithArgList("nil", "nil")`, Pass: testutils.PassFailure()}, + "continue": {Source: `testValues performWithArgList(continue, list)`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `testValues performWithArgList(Exception raise, list)`, Pass: testutils.PassFailure()}, + }, + "prependProto": { + "none": {Source: `testValues prependProtoObj := Object clone removeAllProtos; Object getSlot("prependProto") performOn(testValues prependProtoObj, thisLocalContext, message(prependProto(Lobby))); Object getSlot("protos") performOn(testValues prependProtoObj)`, Pass: testutils.PassEqual(vm.NewList(vm.Lobby))}, + "one": {Source: `testValues prependProtoObj := Object clone; testValues prependProtoObj prependProto(Lobby); testValues prependProtoObj protos`, Pass: testutils.PassEqual(vm.NewList(vm.Lobby, vm.BaseObject))}, + "ten": {Source: `testValues prependProtoObj := Object clone; testValues prependProtoObj prependProto(Lobby) prependProto(Lobby) prependProto(Lobby) prependProto(Lobby) prependProto(Lobby) prependProto(Lobby) prependProto(Lobby) prependProto(Lobby) prependProto(Lobby) prependProto(Lobby); testValues prependProtoObj protos`, Pass: testutils.PassEqual(vm.NewList(vm.Lobby, vm.Lobby, vm.Lobby, vm.Lobby, vm.Lobby, vm.Lobby, vm.Lobby, vm.Lobby, vm.Lobby, vm.Lobby, vm.BaseObject))}, + "continue": {Source: `Object clone prependProto(continue)`, Pass: testutils.PassControl(vm.Nil, iolang.ContinueStop)}, + "exception": {Source: `Object clone prependProto(Exception raise)`, Pass: testutils.PassFailure()}, + }, + // print and println need special tests + "proto": { + "proto": {Source: `testValues proto`, Pass: testutils.PassIdentical(vm.BaseObject)}, + }, + "protos": { + "none": {Source: `Object getSlot("protos") performOn(Object clone removeAllProtos)`, Pass: testutils.PassEqual(vm.NewList())}, + "one": {Source: `Object clone protos`, Pass: testutils.PassEqual(vm.NewList(vm.BaseObject))}, + "ten": {Source: `testValues manyProtos protos`, Pass: testutils.PassEqual(vm.NewList(vm.BaseObject, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil))}, + }, + "raiseIfError": { + "nothing": {Source: `Object raiseIfError`, Pass: testutils.PassSuccess()}, + }, + // relativeDoFile needs special tests + "removeAllProtos": { + // There is no way to test from an Io script that both protos and + // removeAllProtos work. We only test that removeAllProtos does not + // completely fail here and save tests that it actually works for + // another test function. + "none": {Source: `Object getSlot("removeAllProtos") performOn(Object clone removeAllProtos)`, Pass: testutils.PassSuccess()}, + "one": {Source: `Object clone removeAllProtos`, Pass: testutils.PassSuccess()}, + "ten": {Source: `testValues manyProtosToRemove removeAllProtos`, Pass: testutils.PassSuccess()}, + }, + "removeAllSlots": { + "none": {Source: `Object clone removeAllSlots`, Pass: testutils.PassSuccess()}, + "one": {Source: `testValues slotsObj := Object clone do(x := 0); testValues slotsObj clone do(x := 1) removeAllSlots x`, Pass: testutils.PassEqual(vm.NewNumber(0))}, + }, + } + for name, c := range cases { + t.Run(name, func(t *testing.T) { + for name, s := range c { + t.Run(name, s.TestFunc("TestObjectMethods")) + } + }) + } + vm.RemoveSlot(vm.Lobby, "testValues") +} diff --git a/optable.go b/internal/optable.go similarity index 99% rename from optable.go rename to internal/optable.go index b2697da..be1c933 100644 --- a/optable.go +++ b/internal/optable.go @@ -1,4 +1,4 @@ -package iolang +package internal import "fmt" diff --git a/optable_test.go b/internal/optable_test.go similarity index 87% rename from optable_test.go rename to internal/optable_test.go index d155407..163db4c 100644 --- a/optable_test.go +++ b/internal/optable_test.go @@ -1,14 +1,17 @@ -package iolang +package internal_test import ( "strings" "testing" + + "github.com/zephyrtronium/iolang" + "github.com/zephyrtronium/iolang/testutils" ) // TestLazyOptable tests that a new OperatorTable is created whenever // one is needed but does not exist. func TestLazyOptable(t *testing.T) { - vm := TestingVM() + vm := testutils.VM() cases := []string{"operators", "assignOperators"} for _, c := range cases { t.Run(c, func(t *testing.T) { @@ -19,7 +22,7 @@ func TestLazyOptable(t *testing.T) { if proto == nil { t.Fatalf("OperatorTable missing %s after parsing", c) } - if _, ok := r.Value.(map[string]*Object); !ok { + if _, ok := r.Value.(map[string]*iolang.Object); !ok { t.Fatalf("OperatorTable %s has wrong type; want Map, have %v", c, vm.TypeName(r)) } }) @@ -30,7 +33,7 @@ func TestLazyOptable(t *testing.T) { if proto == nil { t.Fatalf("OperatorTable missing %s after parsing", c) } - if _, ok := r.Value.(map[string]*Object); !ok { + if _, ok := r.Value.(map[string]*iolang.Object); !ok { t.Fatalf("OperatorTable %s has wrong type; want Map, have %v", c, vm.TypeName(r)) } }) @@ -43,7 +46,7 @@ func TestLazyOptable(t *testing.T) { // recursively equal, and their Next messages are recursively equal. Otherwise, // the first message belonging to other that differs from m is returned. Panics // if other is nil. -func (m *Message) Diff(other *Message) *Message { +func Diff(m *iolang.Message, other *iolang.Message) *iolang.Message { if m == nil && other != nil { return other } @@ -57,7 +60,7 @@ func (m *Message) Diff(other *Message) *Message { return other } for i, arg := range m.Args { - r := arg.Diff(other.Args[i]) + r := Diff(arg, other.Args[i]) if r != nil { return r } @@ -68,13 +71,13 @@ func (m *Message) Diff(other *Message) *Message { } return nil } - return m.Next.Diff(other.Next) + return Diff(m.Next, other.Next) } // TestOptableShuffle tests that operator precedence shuffling produces the // correct message chains using the default OperatorTable. func TestOptableShuffle(t *testing.T) { - vm := TestingVM() + vm := testutils.VM() cases := map[string]string{ "x+y": "x +(y)", "x+y+z": "x +(y) +(z)", @@ -112,7 +115,7 @@ func TestOptableShuffle(t *testing.T) { if err != nil { t.Fatalf("error parsing unshuffled %q: %v", s, err) } - if d := b.Diff(a); d != nil { + if d := Diff(b, a); d != nil { t.Errorf("parses of %q and unshuffled %q differ with %#v", c, s, d) } }) @@ -122,7 +125,7 @@ func TestOptableShuffle(t *testing.T) { // TestOptableErrors tests that invalid operator expressions produce errors when // shuffled. func TestOptableErrors(t *testing.T) { - vm := TestingVM() + vm := testutils.VM() cases := map[string]string{ "AssignStart": ":= x", "AssignOnly": ":=", @@ -134,8 +137,8 @@ func TestOptableErrors(t *testing.T) { } ops, _ := vm.GetSlot(vm.Operators, "operators") asgn, _ := vm.GetSlot(vm.Operators, "assignOperators") - ops.Value.(map[string]*Object)["$"] = vm.Nil - asgn.Value.(map[string]*Object)["<><"] = vm.Nil + ops.Value.(map[string]*iolang.Object)["$"] = vm.Nil + asgn.Value.(map[string]*iolang.Object)["<><"] = vm.Nil for name, c := range cases { t.Run(name, func(t *testing.T) { _, err := vm.Parse(strings.NewReader(c), "TestOptableErrors") @@ -144,7 +147,7 @@ func TestOptableErrors(t *testing.T) { } }) } - vm.initOpTable() + testutils.ResetVM() } // TODO: tests on changing the operator table diff --git a/parse.go b/internal/parse.go similarity index 99% rename from parse.go rename to internal/parse.go index 5c0a9b0..d1fb669 100644 --- a/parse.go +++ b/internal/parse.go @@ -1,4 +1,4 @@ -package iolang +package internal /* This file is for converting lexer tokens into messages. If you're looking diff --git a/parse_test.go b/internal/parse_test.go similarity index 96% rename from parse_test.go rename to internal/parse_test.go index df19f12..9af24ff 100644 --- a/parse_test.go +++ b/internal/parse_test.go @@ -1,14 +1,16 @@ -package iolang +package internal_test import ( "strings" "testing" + + "github.com/zephyrtronium/iolang/testutils" ) // TestParseArgs tests that messages are parsed with the correct number of // arguments. func TestParseArgs(t *testing.T) { - vm := TestingVM() + vm := testutils.VM() cases := map[string]struct { text string n int @@ -47,7 +49,7 @@ func TestParseArgs(t *testing.T) { // TestParseErrors tests that certain illegal phrasings result in errors. func TestParseErrors(t *testing.T) { - vm := TestingVM() + vm := testutils.VM() cases := map[string]string{ "BareComma": "a, b", "UnclosedBracket": "abc(def", @@ -72,7 +74,7 @@ func TestParseErrors(t *testing.T) { // TestParseComments tests that the parser ignores comments. func TestParseComments(t *testing.T) { - vm := TestingVM() + vm := testutils.VM() cases := map[string]struct { text string msgs []string diff --git a/scheduler.go b/internal/scheduler.go similarity index 92% rename from scheduler.go rename to internal/scheduler.go index 5048b5d..537e89e 100644 --- a/scheduler.go +++ b/internal/scheduler.go @@ -1,4 +1,4 @@ -package iolang +package internal import ( "fmt" @@ -86,6 +86,14 @@ func (s *Scheduler) Await(a, b *VM) { } } +// Pause tells the scheduler that a coroutine is pausing. +func (s *Scheduler) Pause(coro *VM) { + select { + case s.pause <- coro: // do nothing + case <-s.Alive: // do nothing + } +} + // Finish tells the scheduler that a coroutine has finished execution. func (s *Scheduler) Finish(coro *VM) { select { @@ -163,7 +171,7 @@ func (s *Scheduler) schedule(ready chan struct{}) { s.reallyExit() return } - s.Main.NewFuture(sys, s.Main.IdentMessage("userInterruptHandler")) + go s.userInterrupt(sys) case r := <-s.exit: s.reallyExit() s.Main.ExitStatus = r @@ -183,6 +191,14 @@ func (s *Scheduler) checkCycle(w waitpair) bool { return false } +// userInterrupt activates the System userInterruptHandler in a new coroutine. +func (s *Scheduler) userInterrupt(locals *Object) { + coro := s.Main.VMFor(s.Main.Coro.Clone()) + s.Start(coro) + coro.Perform(locals, locals, s.Main.IdentMessage("userInterruptHandler")) + s.Finish(coro) +} + // SchedulerAwaitingCoros is a Scheduler method. // // awaitingCoros returns a list of all coroutines which are waiting on another diff --git a/sequence.go b/internal/sequence.go similarity index 99% rename from sequence.go rename to internal/sequence.go index e703aa6..7f20975 100644 --- a/sequence.go +++ b/internal/sequence.go @@ -1,4 +1,4 @@ -package iolang +package internal import ( "bytes" diff --git a/sequence_immutable.go b/internal/sequence_immutable.go similarity index 99% rename from sequence_immutable.go rename to internal/sequence_immutable.go index 1f89f26..87f8a31 100644 --- a/sequence_immutable.go +++ b/internal/sequence_immutable.go @@ -1,4 +1,4 @@ -package iolang +package internal import ( "bytes" diff --git a/sequence_math.go b/internal/sequence_math.go similarity index 99% rename from sequence_math.go rename to internal/sequence_math.go index 95dff33..94eb45c 100644 --- a/sequence_math.go +++ b/internal/sequence_math.go @@ -1,4 +1,4 @@ -package iolang +package internal import ( "bytes" diff --git a/sequence_mutable.go b/internal/sequence_mutable.go similarity index 99% rename from sequence_mutable.go rename to internal/sequence_mutable.go index 2179411..23af1ac 100644 --- a/sequence_mutable.go +++ b/internal/sequence_mutable.go @@ -1,4 +1,4 @@ -package iolang +package internal import ( "fmt" diff --git a/sequence_string.go b/internal/sequence_string.go similarity index 99% rename from sequence_string.go rename to internal/sequence_string.go index f9eed49..4f5511d 100644 --- a/sequence_string.go +++ b/internal/sequence_string.go @@ -1,4 +1,4 @@ -package iolang +package internal import ( "bytes" diff --git a/slots.go b/internal/slots.go similarity index 99% rename from slots.go rename to internal/slots.go index 3e8c370..96596e9 100644 --- a/slots.go +++ b/internal/slots.go @@ -1,4 +1,4 @@ -package iolang +package internal /* This file contains the implementation of slot lookups. Executing Io code diff --git a/system.go b/internal/system.go similarity index 99% rename from system.go rename to internal/system.go index 55eafe1..dae2a45 100644 --- a/system.go +++ b/internal/system.go @@ -1,4 +1,4 @@ -package iolang +package internal import ( "os" diff --git a/system_js.go b/internal/system_js.go similarity index 83% rename from system_js.go rename to internal/system_js.go index 1cb72b9..35e32a8 100644 --- a/system_js.go +++ b/internal/system_js.go @@ -1,6 +1,6 @@ // +build js,wasm -package iolang +package internal // platformVersion is 1. var platformVersion = "1" diff --git a/system_plan9.go b/internal/system_plan9.go similarity index 80% rename from system_plan9.go rename to internal/system_plan9.go index e376c7f..70103f1 100644 --- a/system_plan9.go +++ b/internal/system_plan9.go @@ -1,4 +1,4 @@ -package iolang +package internal // platformVersion is 4. var platformVersion = "4" diff --git a/system_unix.go b/internal/system_unix.go similarity index 97% rename from system_unix.go rename to internal/system_unix.go index e317927..935d494 100644 --- a/system_unix.go +++ b/internal/system_unix.go @@ -1,6 +1,6 @@ // +build aix darwin dragonfly freebsd linux netbsd openbsd solaris -package iolang +package internal import ( "bytes" diff --git a/system_windows.go b/internal/system_windows.go similarity index 98% rename from system_windows.go rename to internal/system_windows.go index 1071cde..0388ec7 100644 --- a/system_windows.go +++ b/internal/system_windows.go @@ -1,4 +1,4 @@ -package iolang +package internal import ( "fmt" diff --git a/vm.go b/internal/vm.go similarity index 81% rename from vm.go rename to internal/vm.go index 0512010..1f908ab 100644 --- a/vm.go +++ b/internal/vm.go @@ -1,7 +1,7 @@ -//go:generate go run ./cmd/gencore vm_init.go ./io +//go:generate go run ../cmd/gencore vm_init.go internal ./io //go:generate gofmt -s -w vm_init.go -package iolang +package internal import ( "compress/zlib" @@ -13,6 +13,14 @@ import ( "github.com/zephyrtronium/contains" ) +// IoVersion is the interpreter version, used for the System version slot. It +// bears no relation to versions of the original implementation. +const IoVersion = "1" + +// IoSpecVer is the Io language version, used for the System iospecVersion +// slot. +const IoSpecVer = "0.0.0" + // VM is an object for processing Io programs. type VM struct { // Lobby is the default target of messages. @@ -66,6 +74,8 @@ type VM struct { // NewVM prepares a new VM to interpret Io code. String arguments may be passed // to occupy the System args slot, typically os.Args[1:]. func NewVM(args ...string) *VM { + haveVM = true // TODO: atomic? + vm := VM{ Lobby: &Object{id: nextObject()}, @@ -105,19 +115,10 @@ func NewVM(args ...string) *VM { vm.initNil() vm.initLocals() vm.initList() - vm.initFile() - vm.initDirectory() - vm.initDate() - vm.initDuration() vm.initSystem() vm.initArgs(args) - vm.initCollector() - vm.initScheduler() vm.initCoroutine() - vm.initFuture() - vm.initAddon() - vm.initPath() - vm.initDebugger() + vm.initScheduler() vm.finalInit() @@ -127,6 +128,11 @@ func NewVM(args ...string) *VM { // coreInstall is a convenience method to install a new Core proto that has // BaseObject as its proto. Returns the new proto. func (vm *VM) coreInstall(proto string, slots Slots, value interface{}, tag Tag) *Object { + return CoreInstall(vm, proto, slots, value, tag) +} + +// CoreInstall is a transitional proxy to vm.coreInstall for core extensions. +func CoreInstall(vm *VM, proto string, slots Slots, value interface{}, tag Tag) *Object { r := vm.ObjectWith(slots, []*Object{vm.BaseObject}, value, tag) vm.SetSlot(vm.Core, proto, r) return r @@ -201,8 +207,17 @@ func (vm *VM) initCore() { // finalInit runs Core initialization scripts once the VM can execute code. func (vm *VM) finalInit() { - for i, data := range coreIo { - name := coreFiles[i] + Ioz(vm, coreIo, coreFiles) + for _, ext := range coreExt { + ext(vm) + } +} + +// Ioz executes zlib-compressed Io scripts generated by gencore. Panics on any +// error. +func Ioz(vm *VM, io, names []string) { + for i, data := range io { + name := names[i] r, err := zlib.NewReader(strings.NewReader(data)) if err != nil { panic(fmt.Errorf("iolang: error decompressing initialization code from %s: %w", name, err)) @@ -229,3 +244,20 @@ func (vm *VM) IsAlive() bool { return true } } + +// Register registers a core extension. Each function is called in the order it +// is registered; extensions that depend on other extensions need only import +// them. Register should be called from within init funcs. Panics if NewVM has +// been called. +func Register(f func(*VM)) { + if haveVM { + panic("iolang/internal: Register must be called before any VM is created") + } + coreExt = append(coreExt, f) +} + +// coreExt is a list of core extensions that have been registered. +var coreExt = make([]func(*VM), 0, 10) + +// haveVM becomes true once NewVM has been called. +var haveVM = false diff --git a/vm_init.go b/internal/vm_init.go similarity index 53% rename from vm_init.go rename to internal/vm_init.go index a6498a2..856e5f7 100644 --- a/vm_init.go +++ b/internal/vm_init.go @@ -1,28 +1,20 @@ -package iolang +package internal // Code generated by gencore; DO NOT EDIT var coreIo = []string{ - "x\x9c\xec9Ko\xdcF\xd2gί(0\x87\x90\xf8\x18\xd9\xca\xe1;Ș\x15l\xd9\x01\x8c\xf8!d\xbc닁E\x8b\xac\x19v\xa6\xd9Mw7G\x96\x83\xfc\xf7EU79\xe4\x90\xe3\xc8\xc8e\x0f{\xb1\x87d\xbd\xdfUz\u007f\xf7;\x96\x1e*\x93\xad\x92\xd6J\xed\xe1j\r\r\xfa\xdaT\xd9/R!8/t%l\xf5\xbe\xf3m\xe7\xe1\xdeJ\x8f\xd9\x0e\xfdF\x19\x9f\xa5\x0e\xd56\xcdA\xb8\x8d\xb7R\xef\xf2gp\xf2)\x8fd\x95\xfe[\x84\vH?\xe9t\x91<#\x8e\x88\x97B)\xc0\x83P\xcf\xed\xce\xc1\xd6X\x14e\x9d\xb1\x10\xe7\xf1'\xe2}\x9b\x02\t\x02\xfc\xb0D\xed\xc9\x13\xf8\xa7Cp\xe1\x03T\xd2b\xe9\xd5\x03x\x03\xa5\xb4e\xd7\x1cP{0-Z\xe1\x8d\x05Wwۭ\x92zw\xb1J\\OL\xe8*-za\x0e\x05\x1c@\xba\x0f\xb6C\xa2?\x00\xfdt\x02\xa2q'\xfc\x14\xe4\xe2b\x02sζpq\x01\x87\xa3\v\xf3\xd5*\x91Zr \f(\xbe\x96\xee\xc6h\x8f_|J\x00B\x97輱nd6Q\xac\x92Dn\xc3\xff\xfc\x03J\xa3\xbd\x90ڽ\xaeP{Y\n\xf5\xc1\x9c\xba8/\xc0\xa2\xef\xac\x06\x91\xaf\x92\x84q\x05\xac\xe1\x8dt\x1eJe4\xae\x92\x84\xbe\b\x10m\x8b\xba\x9a\x11X%ɩj\xad5\xde\x1c]7H\x9b\t\x06\x17\xab\x84\x94`\xa8\x91\x02\xe7\xa8H\xeb|\xbeJj\xe1n{\x8c\x01T\xba_\xa5\xae\xdeo\xd9*\xb5p\xec\xf5#I\xa7\x8c\x9f[\xbe\x16\xee\x8d)\x85\xe2\x97\x04\x92\x83\xb1s\xffD\xa9?J_\x8f \xa5{'\x15h㏮&\x88\x0f\x0f-\xce\x18\x1f\x84\xea\xb0X0P\x1f##02\f\xff\x00\x1fI\xd1G\xb2T\xa2\xf1~Q\xafG\x92\x1f\xbe2\xfc\xb2\xc7\xdc\xf1٧\x14\x91\x84\n\xc2݈Vz\xa1\xe4W\xac84*\x13\xc24K\xa3$G\xe0\x8b\vHa\r\xe3\xec}\uece7\xf93 \x1e\xf9\t\xdf(\v\xeb\xa7\xc4ׇ\x13\x05C(3-aw7\xa6\xd3\x1e\xd6k\xb8d!\x9a\xb1%Pm\a\xe9\x19\xbeA\xe7\xc4\x0eA\x8b\x06\v\xd0R1\xe3DP1\x19\x87N\x93\xe6GX\xfc\xe2\x89Qנ\xf6n\x00\x17\xfe\xb6\xf3\xd9e\x01\xbd \xacPH\v&z\x9e\x98C\xff\xbc\xa7\x97\x11\xb19|\x9f]\xac\x13IK\xe2\xcd\xcc7(\x9cNT\xfd\x94\xfe\xf0\a\xe1\xfc\xf9)-\xe0\x87?F\x02^\x1eK˟y\nR{\xb4\xadQ\xc2#\b\xf76\xc8HD'䂱\xa2M9\x1c\"d\xd6\x04\xf3i\xa9B!\xe0\xfa䞗^\x1e\x84\x17w\x8a\xa5\xde\n\xe5\x90\xf3\xe1\xf5\xe9\xa7\xf3\x05pF\xe5\x10C\x85\n\x9c{a\x8cB\xc1}\xc1\xdb\x0e\xe9]U}\xb0B\xfa)U\x8b\xae8\x13-O\vx\xf5\xa5\xc4\xd6K\xa3\xc1\n\xe90Kc\xc3\x1dhY\xfc\xdcI\x8b\x0e\x8cF\xaa\x02\xfe\xde\x1c\x03!\xc4,}\xbdZ\x13#\x90\xdbwR\xbd:\b\x95\xbd\x15\xed1\x0e\x8eQ\x9d\xe6}\xd5\xe3\x17\xfb\x02\x0e}9\xfef\x15\xda\xe7\fFp{\x12=\xa5\"\x90\x16\\\xc1\xa5\x0e\xd5!H\"\b8\b\x92ʹ\xe3,\xe2\x94܇|\x14ʢ\xa8\x1e\x00\xbfH\xd7\xebs>\xfb\a\xfa\xe3\xa2\x11\x91\x8aob\xee\x170\xf2\xd89NpB\bQ\xc9x\x89\xae\xb4\x92\x15 sN\xb2\x9f\xbeO\xf3\xb5稌\u007f'\x1at\xe0\x8c\xf5\xaf\xf5\xad\x12%\xc5s\x85\xaet}\xd9tЈ\xf6L\xf5ߍ\x8a#\xa7\x8alZ\x85\xb1\xfb\xae\x92d\xf0,\x05ɯ\xf8\xe0\x9e\xeb\xea_T\xad\x1c\xa3\xb8\x02\x98U(]\xa2\xb5\xa65\xe36\xbc\xbf'K\xdd\x18\x8b\x93H\b\xb2\xb4\xa1\x88\xf6\xa2\x0e\xa2\xb5Q\xb1\x13\x8b\\;TX\xc60\x82=\xe5o\x17\xb2E\x99{\xb4\xa5p8\xf4\xf8\r~\xce\xf6\xf7\xc1\xf2r\x9b\x05\x1eBWAZp\xf2+\xc2?\xe0ip#s\xdf\xe0\xe7\x0eu\x89C!\xea%\xdb\xe3\xc3ĺC#\xdf\a\xec\xc4\xc5Y\x80x\xa6\x00)˦\xe4N\xbf\xc1\xad\xcf.\xff?/\xb8\x15\xa4\xd1T}L\xf1\f\xc9\x14¿\xdc6\xe2x\x1a\xc5\x1a=\xf5\xe1Õ'\x0f\x11\xb3\xe9\x9aF؇\x99\xb9\x17ҫ\x9f\f\xb2\x17ʔ\xfb㘳0\x86\x9d\x04\xc0\x82q&\xfa\xa6\xc5\tR\x01\xe9UTm\x12\x85So\xae\x92d\u007fO\x89k4\xe5n\x80\\\xf7\xee\xf9~G\x1f\xd9\xfd\x85\xbf\xfe\xa6\xb7\xd8$!\xd8'Z\u007fcr\xe3\t\x86\xeaϿ\xb9\x16\x9d~\xee\xb4\xfc\xdc\xe1\xeb\x8a\xeb@\xff\xed\xfa8,\x0f-o\xd2v\xe9\xed\x93'\xf0\xea\x80\x1a|m\xba]\r\xd7 \x1d\xf8\x1a\xa11\xceÝ\xd4\x15\xc9\xd5O\xf7\xc5\u009c\x1f\x89\x94\xb5\xd0;tp\x9d59uH\x03\xd7\x19u:\xda\x13\xbc\xe5u\xa1\xb5\xe8\xd0\x1e\x90~\x94Xq,\x98m\xc4o\x85E\xedkt\xe8.\xe0\xa6\xc6rO6\a\x1aӁ\x9dE\x897Z8\xa4\x87L\xa8(\xb5\xaf1\x921V\xee\xa4\x16\n*\x83N\xff\xe8\U000cb63d\rO/ ݫ\xa6\xf5\x0fL\xad\tsE\x18?Ë\xf9d\x944\xb0\x0e\x1f\x06\x93\xe5C\xaf\xef\xdd\x10\x88\xe7C(r?k\xa02\xafu\xdc1x\xaa\x8aS\x8fC]\xa1\xe5h\xe3r\u007f+\x9c\xdbxӺ\x8c\xdar_ͻ\x16\xedI\x01/\a\x0f\x06\x12\xf1\xb72>r9\xd7\xc4b\x8b\x0e4\t\t+0\x9dw\xb2B\x10pG\xe9\x1c:\xcc\x10쇰`!^z(\x95\xcb\xf9.0X0\x9a\xef\x1c\f\xe5\xd5\xd6\xd8{a\xab\xc0\x82\xf729\xd9\xc8\\\x99/\f*AV74t\xa6\xc6=]\x1b\x0f[\xd3\xe9H\xf1\x8ed\x14\x83wY\xb2\x93\x92x7\x9b\xb6\xd6qf+\xe0n\xd4!\t\xaeE\xbb5\xb6y\xaf\xb3\x99P;\xf4\x93\xb8(\xa0)\x80\xb7\xba|E\xe3\t\xea\xea\u007fa\xf0\xdf\x14\x06\xd3\x18\x18g\xfc#\xbc~R\xb8\x8b\x05?\u03a2`\x95\xc8\xf11G\x15\xa0\x86\xd6\xc5u\x85/\x1c\xee^\xfa\xb2\x9e\x86\n\xfbqklF\x15\xb4\xa0\xe9}:\xd1\xff\x04?\x17\xf0s\x11k\xe7G\x84R\xe8\x1f}\xdf\xd7k\xb4\bwX\x8a\xcea(ā2\xb4\xc29\x9a\x16\xa9f\xf5\xf5\xf5d\xcb\"\x869\xe5D\xa8{w\x16\xc5>\x1bE\x14\x97\xf4\xff\x83\xcb\xd0t\xa3\xff\x1a\xf2@1&8H*\xdd\xfb*\xec\xcbɌ\xd3T\xa3\xcb\xd1l\x1dV\xacd8\xc3T渃\xc5\xddk\xa9\x00\xd3:\xf6Ft\xba\xac7\x1c\xcc\xe4\x1eN\n\xbd\xb7\x16y\x0e\xf8#\xe6z\x8cU\xc4\xd1\xe1x\xf8\xe6\xf8\xe3\xf9{\xa2\xd6'\x9f\x1616S\xbe\xecMo\x83'w\x8a0c\xb8\xa5\xd88\xa5;,l\xf9*_\xad\xa8|\x84\xbf,\xc9\xed\a\xfa}\xb5\x868\x01\r\xbc\xfaΤ\xab߸\x8dn\xe2\x89\xc6ר\x1f\x03\xffN\xaa\xf4\x11\x11\tSAI2J\xa8|\xb5\xe2)\xb4\x97\xf2\x17~\xf8\x0e1\xf1\x91\xf0QL\x02\x97\xdb%\x04\xc9\xe4\x86e\xd4ش\x803\u007f\x02\x9a\x9c&\xe3\xe1\xf3;\xf5g\xach\x00-U\xaf>mvߡ\xfc\x90\x88\xdf@\x8ap\x9c\xd9\x04\xc63\xc68Շ\x97\x8fҽ\x14qjc\x14\x9a\xac\x86\x87\x89Y\xf8\xcdw\x1aE\x93\x8f\xd8$K\x85\xf2\xa8\xe2P\\\x8e\u007f'\x1b\xad\x1b\xd4\x1eB\xa9\\:q=\xfa\uf547\x02Ƥ\x8e\a\x94C\xce2\xfe'\x00\x00\xff\xffjG}\xda", + "x\x9c\xec\x19\xcbn\xdcF\xf2\xcc\xf9\x8a\x02s\b\x89e\xc6V\x0e{\xb01+ز\x03\x18\xf1C\xc8x\xd7\x17\x03\x8b\x16Y3\xecL\xb3\x9b\xeen\x8e,\a\xf9\xf7EU79\xe4\x90Rd\xece\x0f{\xb1\x87d\xbd\xdfU\xfap\xf3;\x96\x1e*\x93\xad\x92\xd6J핆g\x1bh\xd0צ\xca\xf6\xe8\xb7\xca\xf8,u\xa8vi\x0e\f\xf1\x1c\xd2\xcf:\r\xbf\xf3Urk\xa5\xc7\x11N)\x94\x02<\n\xf5\xc2\xee\x1d\xec\x8cEQ\xd6Y\x80~\x0eg\x14{\xfc\tׇ)\x9c\x98/Q{\xf2\x04\xfe\xe9\x10\\\xf8\x00\x95\xb4Xzu\a\xde@)m\xd95G\xd4\x1eL\x8bVxc\xc1\xd5\xddn\xa7\xa4ޯW\x89\xeb\x89\t]\xa5E/̱\x80#H\xf7\xd1vH\xf4\a\xa0\x9f\xce@4\ue15f\x82\xac\xd7\x13\x98sc\n\xb7\xf5V\xea=\xac\xd7p\x1c\x9e\xf2|\xb5J\xa4\x96\x9e\f2\xa0\xf8Z\xba+\xa3=~\xf5)\x01\b]\xa2\xf3ƺ\x91\xd9D\xb1J\x12\xb9\v\xff\xf3\x0f(\x8d\xf6Bj\xf7\xa6B\xede)\xd4Gs\xeeӼ\x00\x8b\xbe\xb3\x1aD\xbeJ\x12\xc6\x15\xb0\x81\xb7\xd2y(\x95ѸJ\x12\xfa\"@\xb4-\xeaYPзy\x9c\x18oN\xae\x1b\xa4\xcd\x04\x83\x8bUBJ0ԃ\xd1\x16\xa8H\xeb(\xd0j\xe1\xae{\x8c\x01T\xba_\xa5\xae>\xec\xd8*\xb5p\xec\xf5\x13I\xa7\x8c\x9f[\xbe\x16\xee\xad)\x85\xe2\x97\x04\x92\x83\xb1s\xffD\xa9?I_\x8f \xa5{/\x15h\xe3O\xae&\x88\x8fw-\xce\x18\x1f\x85\xea\xb0X0P\x1f##02\f\xff\x00\x1fI\xd1G\xb2T\xa2\xf1vQ\xafG\x92\x1f\xbe2\xfc\xb2\xc7\xdc\xe9٧\x14\x91\x84\n\xc2]\x89Vz\xa1\xe47\xac84*\x13\xc24K\xa3$'\xe0\xf5\x1aR\xd8\xc08{_\xf8\xeci\xfe\x1c\x88G~\xc67\xca\xc2\xfa)\xf1\xed\xeeL\xc1\x10\xcaLK\xd8\xfd\x95鴇\xcd\x06.X\x88fl\tT\xbbAz\x86o\xd09\xb1GТ\xc1\x02\xb4T\xcc8\x11TLơӤ\xf9\t\x16\xbfzb\xd45\xa8\xbd\x1b\xc0\x85\xbf\xee|vQ@/\b+\x14҂\x89\xdeO̡\u007f\xd1\xd3ˈ\xd8\x1c\xbe\xcf.։\xa4%\xf1f\xe6\x1b\x14N'\xaa~N\u007f\xf8\x83p\xfe\xfc\x9c\x16\xf0\xc3\x1f#\x01/N\xa5\xe5\xcf<\x05\xa9=\xda\xd6(\xe1\x11\x84{\x17d$\xa2\x13r\xc1XѦ\x1c\x0e\x112k\x82\xf9\xb4T\xa1\x10p}r/J/\x8f\u008b\x1b\xc5R\xef\x84r\xc8\xf9\xf0\xe6\xfc\xd3\xfd\x05pF\xe5\x18C\x85\n\x9c{i\x8cB\xc1}\xc1\xdb\x0e\xe9]U}\xb4B\xfa)U\x8b\xae\xb8'Z\x9e\x16\xf0\xfak\x89\xad\x97F\x83\x15\xd2a\x96Ɔ7в\xf8\xa5\x93\x16\x1d\x18\x8dT\x05\xfc\xad9\x05B\x88Y\xfa\xfalC\x8c@\xee\xdeK\xf5\xfa(T\xf6N\xb4\xa788Eu\x9a\xf7U\x8f_\x1c\n8\xf6\xe5\xf8\xc1*t\xc8\x19\x8c\xe0\x0e$zJE -\xb8\x82K\x1d\xaaC\x90D\x10p\x10$\x9bi\xc7Y\xc4)y\b\xf9(\x94EQ\xdd\x01~\x95\xae\xd7\xe7\xfe\xec\x1f菋FD*\x1e\xc4<,`\xe4\xb1s\x9c\xe1\x84\x10\xa2\x92\xf1\n]i%+@\xe6\x9cd?}\x9f\xe6k\xcfQ\x19\xff^4\xe8\xc0\x19\xeb\xdf\xe8k%J\x8a\xe7\n]\xe9\xfa\xb2\xe9\xa0\x11\xed=\xd5\u007f?*\x8e\x9c*\xb2i\x15\xc6\xee\xbbJ\x92\xc1\xb3\x14$\xbf\xe2\x9d{\xa1\xab\u007fQ\xb5r\x8c\xe2\n`V\xa1t\x89֚\u058c\xdb\xf0\xe1\x96,ue,N\"!\xc8҆\"ڋ:\x88\xd6F\xc5\xce,r\xe9Pa\x19\xc3\b\x0e\x94\xbf]\xc8\x16enі\xc2\xe1\xd0\xe3\xb7\xf8%;\xdc\x06\xcb\xcb]\x16x\b]\x05i\xc1\xc9o\b\xff\x80\xa7\xc1\x8d\xcc}\x8b_:\xd4%\x0e\x85\xa8\x97\xec\x80w\x13\xeb\x0e\x8d\xfc\x10\xb0\x13\x17g\x01\xe2\x99\x02\xa4,\x9b\x92{\xfd\x16w>\xbb\xf8{^p+H\xa3\xa9\xfa\x98\xa2\xd1-\x04`\xf8\x97\xdbF\x9c:\xa3X\xa3\xa7>|\xb8\xf2\xe4!b\xb6]\xd3\b{73\xf7Bz\xf5\x93A\xf6R\x99\xf2p\x1as\x16ư\xb3\x00X0\xceDߴ8C* }\x16U\x9bD\xe1ԛ\xab$9\xdcR\xe2\x1aM\xb9\x1b 7\xbd{\xbe\xdf\xd1'v\u007f\xe1\xaf\xff\xd2[l\x92\x10\xec\x13\xad\x1f\x98\xdcx\x82\xa1\xfa\xf3o\xaeE\xe7\x9f;-\xbft\xf8\xa6\xe2:\xd0\u007f\xbb<\r\xcbC˛\xb4]z\xfb\xe4\t\xbc>\xa2\x06_\x9bn_\xc3%H\a\xbeFh\x8c\xf3p#uEr\xf5\xd3}\xb10\xe7G\"e-\xf4\x1e\x1d\\fMN\x1d\xd2\xc0eF\x9d\x8e\xf6\x04oy]h-:\xb4G\xa4\x1f%V\x1c\vf\x17\xf1[aQ\xfb\x1a\x1d\xba5\\\xd5X\x1e\xc8\xe6@c:\xb0\xb3(\xf1F\v\x87\xf4\x90\t\x15\xa5\xf65F2\xc6ʽ\xd4BAe\xd0\xe9\x1f}\xbe\x8e\xd9\xdb\xf0\xf4\x02ҽnZ\u007f\xc7Ԛ0W\x84\xf13\xbc\x98OFI\x03\x9b\xf0a0Y>\xf4\xfa\xde\r\x81x>\x84\"\xf7\xb3\x06*\xf3F\xc7\x1d\x83\xa7\xaa8\xf58\xd4\x15Z\x8e6.\xf7\xd7¹\xad7\xad˨-\xf7ռkў\x15\xf0r\xf0` \x11\u007f+\xe3#\x97\xfb\x9aXlс&!a\x05\xa6\xf3NV\b\x02n(\x9dC\x87\x99\xc7ȣH\x0e-_\xe8\xa1\xd5\a\x82\xfd\x10\x16,\xc4K\x0f\xa5r9\xdf\x05\x06\vF\xf3\xdd\aCy\xb53\xf6V\xd8*\xb0\xe0\xbdLN62W\xe6\v\x83J\x90\xd5\r\r\x9d\xa9qO\xd7\xc6\xc3\xcet:R\xbc!\x19\xc5\xe0]\x96\xec\xac$\xde̦\xadM\x9c\xd9\n\xb8\x19uH\x82k\xd1\xee\x8cm>\xe8l\xe6;/\xec\x1e\xfd$.\nh\n\xe0\xad._\xd1x\x82\xba\xfa\u007f\x18\xfc/\x85\xc14\x06\xc6\x19\xff\b\xaf\x9f\x15\xeeb\xc1\x8f\xb3(X%r|\xccQ\x05\xa8\xa1uq]\xe1\v\x87\xbb\x95\xbe\xac\xa7\xa1\xc2~\xdc\x19\x9bQ\x05-hz\x9fN\xf4?\xc1\xcf\x05\xfc\\\xc4\xda\xf9\t\xa1\x14\xfaG\xdf\xf7\xf5\x1a-\xc2\r\x96\xa2s\x18\nq\xa0\f\xadp\x8e\xa6E\xaaY}}=۲\x88aN9\x11\xeaލEq\xc8F\x11\xc5%\xfdop\x11\x9an\xf4_C\x1e(\xc6\x04\aI\xa5\xfbP\x85}9\x99q\x9ajt1\x9a\xadÊ\x95\fg\x98ʜv\xb0\xb8{-\x15`Z\xc7ފN\x97\xf5\x96\xa7\x8d\xf3KZ\xbf\x96*q\x83\x8a\x94\xdc\xde9\x8f\r\xa8\x11\x0eQ\xb1\xa8\x84\x97G|e~\x91a\x1f\xab\xcco\xf1]\xff&\xd2\xe51\x96{J\xc5\xc0ٵ\xf05\xdcJ_/\xb1l\x85\xaf\xafL\xd3\x1a\x8d\x9af\xe0\xbc\xdf\xfa\x89\xff9e\xe1\xeb\x82\f\xc4K\x1d/\xe1!\x97\xf9\xe7\x06\x94\xe4[P\x12u\xa8L\x16Ȱ\x00\xcf6\xcck\xca\xf0\xf9D\xd1\x1e\x86h\xbc\xe2㠱wdի\xceRK\xffd\xecA\xea\xfd\xf0)\x9b\x18\xeb:b\x8eU\x8f\xd4B\xe0+\xa9\xf1]\b\xbbyhOL3=&0\xc8\xf9\xf6\xdc7\xd8\xc9\xd5 p\xaa\x90\xa6\x12\xe1\xb1\xfa$\xac\x9e\x0ecBqT6\x8bu\xf7\x9e\xc28\xa7\xd7t4Qa_xw\xd64\xec`\xa9A\xc0\xcbX|{mz\x93\xc4[n\x96\xf6T\xa4\xa6\xb1\xb2\t\x81@crx\f\xd7\x06\xaa\x81\xca\xd3\xf0)\x1d\x9c$X\xf3\x05\x97\x8b\x9dP\xf1\xac$\xb5\xf3(\xaauZ@:\x05N\xfb\xaa\xe3\xd0\xcap\xa8\x1a_\x87\xbcE\xd1\xc4\r!<\x84\t\xaa\x80\xf8ģ~\xc0\x14d\x91mx=\xac\xf6\xbfw\xceo\aґ\x06\xc7@\x00\xbc4\x9do\xbbp\xa7\x9b\xc2.\x8a\x11\xb1\xd8P\xb3\xb1y\x8f\xfet\x11\b\x17\x80\xfbj\xf6\xf8D\xca\xe3\xf6\xb8\v-\x8d\xe3<\xe4\x87]\xa62Y\x1c\xed\xe7\xdb|/<\xbdvsm\x83\xdci\x1e\xf0\xf3\xb1շ\xfd\xc2>\xd3\xf9/\xb8P\xa3\xe4\x85~&\xf7\xb0\xea\xf7\xdeZ\xe49\xe0\x8f\x98\xeb1V\x11[\xfa\xe9 \xcd\xf1\xc7s\xf1D\xad\xcf>-bl\xa6|q\x9b\xde\xec\xce\xee\a\xa1\xf7\xbb\xa5\xd88\xa7;,R\xf9*_\xad(\xad\xc3_\\\xe4\xee#\xfd~\xb6\x818\x99\f\xbc\xfa\x8e\xa1\xab߸\xbdm\xe3\xe9\xc4ר\x1f\x03\xff^\xaa\xf4\x11\x11\tSAI2J\xa8|\xb5\xe2鰗\xf2\x17~\xf8\x0e1\xf1\x91\xf0QL\x02\x97\xbb%\x04\xc9\xe4\x86%\xd1ش\x80{\xfe439\x19ƃ\xe4w\xea\xcfX\xd1\x00Z\xaa^}ڸ\xbeC\xf9!\x11\x1f@\x8ap\x9c\xd9\x04ƽ\u007f\x9c\xea\xc3\xcbG\xe9^\x8a8M1\nM<\xc3\xc3\xc4,\xfc\xe6;\x8d\xa2\xc9Gl\x92\xa5ByRq(.\xa7\xbf_\x8d\xd6\x00j\x0f\xa1T.\x9d\x9e\x1e\xfdw\xc4c\x01cR\xa7\xc3\xc61g\x19\xff\x13\x00\x00\xff\xff1\x8e,t", "x\x9c\x94\x93ߪ\xdb0\fƯ\xed\xa7\x10\xb9\xb2\xa1\x1c\x0e\fvq\xe0l\x84\xb2\x8b\xc1N(t/\xa06j\x1a\xf0\x9f`)e}\xfb\xe1\xa4k\x9d\xae\x1d\xecʵ\xfa\xe9\xd3/\x92\xbcF砍F+L\xdd:\x8eA\xe0\xed\x1d<\xc91\xb6\x86\xc9\x1d\xc0\x133v\x04\u007f\xfe\xb7\x93\xb4.ua\x05\xf7\xd2ZL\xb0V+:\xa1\xab\x9fșBK\t\xda\xf81\xe7\xfdUo6\xb1Z\x1d\x91\xeb\xd4q\xe1q\xa5\xfd\x02\xaf\xb72\xfc\x0fx\xfevB7\xa2P\xfb=\x98\xa2\xbe\xb5\xc0$\x1bd\xdeJ\x1c\xd8H\x1a\xc9j\xadZ\xe2}\xea\a\xe9c(\\\xb5R>_Kw\xad\x14_c\x82\xa9#\x019\x0f\x04//PA\x95\x0f\x0f\x01\xfd,D\xd7w\xe1\a\x1d\xc4|\xfal\x17\x12\x87;r\xf0\xd58d٠\x1c\xd7\xd1\x0f1P\x90;Y\x1f\xa8\x19\xfd\x8e\x92V3\xa8\xa3\x0e\x85~Ƃs\xc6XA\xe0\x95V\xea\x02\xf5\xa4\xd3{\x17\x03\xe5&4\xf4kJ\x81\xfe\xd0\xf4.\xf7k\xd9(\xad\x1e\xf6\xaa \xf8\x98\xaa?\xe2@O\xffI2\x9d\xe8\xc9\xe4\\\xbbؙg$Z%b\x92\x1c\xda\nʸ܇\"n\x9a\x98<\xba\xfcI\x89\x1c\x9e\x1ff\xe4qI\x1c\xa6\xd1\xde2S\xbe\xef\xf3\xb3\xb9\xee\xb6y\xcdFj\n^\x96\xfa\xf2\xbb,\x99=\xb2\xac#ٺ(\xa6J\x95\x9d'8-\xcb\xdb;T\xf95V\xda\xea\xdf\x01\x00\x00\xff\xff\xa3\x11,u", "x\x9c\x8cX[w۸\x11~&\u007fŔOd\xcc$\x92\x9cx\xbdݣ\x9e\xe38N\u05ed\xe3uW\xde\xeb\xc9\v$\x0e-\xd8 @\x03\xa0.~\xe8o\xef\x19\x80\x84HI\xd9\xe6\x85\x04\x81\x99\x0fs\x9f\x91f\xf8ܠ\\ \x14*\x8d#ff\xbc\xaa\x05ά\xe6\xf2\x01\xfe>\x85\n\xedR\x15i\xf2%I\xe0\xcd\x1b0(J`\xa6=g\xe6sc\xd9\\ \xa0Y\xb0\x1a\x89\x82(\xb38\x8e\fڙP6M\u07bcI\xf2\x0ef\x95\xef!,\x84\x92xQ\xd7(\x8b\x19>\xa7\xabp\x92eY\x0f\xe3\xd5\x00\"\xc8\xec\xb8a\xa1\xeamJ\xb0\x19\xbc\x9a\xc2j\xc8\xf8͜\xaf\xa6\xe9jx\xe9\xc97\xb2\x9e\xec_\xfa\xfa\x1b\x19_\xef3\xbe\xfdFƷ-\xa3V\xca~F&g\xcf\r\xd3\xd8sW\xb5\xdb4\xcfڒ?\xb8\xb9\xaaj\xbb\xed\x119O\x18\xfe\x820\x9d\xc2(#\x92ٶ\x9a+\xb1OÃ\x9b\xa5\xf2X\x16\xabKU\xf7\xc1\xbe*,\xd13\xc1\x1f\xe4\r\x96\xf6Z\xde\t\xb6\xe8K\xbaΡfE\x1eG\x912\xb4M\xf2\xc4Q\xc4˴f\x05ps\xcb\x05(M4;Q\x1d\vL!\x81$\x8b\xa3(M\xd7\xf0ڝf\xf06Pf\xb0@.@c\x8d̦,\x84X\xcd\n2\x9d3:\u007f\xc1t\r\x15ۤ\xca\xd0f\xd6\x13\xf5P\xc6^\xb8\xef+Ԓd\x1d\xc0\xcf\xfcay\fa\xcfJ\x870\xad\"\x1e\rvR\xb7\xa6\xf4\xe8\x97(-\xeac\x02\x86\xbb\xd34u\xe6:\x81u\xf6v\x92A)\x94\xd2\x1d\xeaW\x85\xa7\x02p\xc9jn\x99\xe0/X\xf4n\xe0e\x1a\xa2 \x87E\xa0\xe9\x1be\xb7KY\xec\"ɉln\xd4\x1a\xf5\x82\x99\xbe\xdfwl\"\x9c\x06.b\xfa\xa5\xae\xff\x82\xa9\t\xa7;\xa68Z(i\x19\x97\xe6Bn/\x99\xc1\x19>\xf7C9Ԟ\x9d<\x1d\x833q\xff$s\xe9p\xf5\xdc0т\r\x91x9L\x1fӺ\xedP\x82\xd4dβ\x82K\xbc\xd3\\\xf6\xc3\xe2\x13\x17\b\xc62Y0]\xfc\xd4غ\xb1\xb0\xd6ܢ\x03\xcf!\xf9\"\x93\xec\a'\xb6w\x8ec\xd8\xe3_s\xbb\f\xf1Qr\x81\xb7\xac\xea\x13Q6\xb5\xb9\xcf\xcb{\xdd`\xaa\xd16Z\xb6\xb0QT\x13\xb1`\xc6\xde1\xbb\xbcTU\xad$J\v\xa6\x16\x9cʷ\xcb1\xcaG\xaf\xec?`\x9cC\r\x1a+\xb5\xc2\x1bf\xac\x87xT\\\xb6\xc4T\xd1\\\r\xff\xb0\xbd\xda\xd4L\x16\\>\xdcsQ\fL\xe8\xc1\xff\x9bd\x9e\xd3ҹ\xa76\xf7\xca\x19\x8cS\x90\xd7J0\x8b\x87e\xc3\x19?\x14\x19\xe8ђ\x15*\xf6\x84\x9f\xb86\xf6r\xc94[X\xd4\xc7B\x90|\xe85\x1a\xe5\xc0\xec]cS\xb7HG\xd90\x14\x8e\"\x1e\x8bϿF\f\x1c\x0e\xb1\xa6\xca\xe4\x92{_-f\xaf\xa5Am)xF\x94lB\x00\xae\x98\xb8\xd0\x0f\xc6Y+\x84D\xa4\xb1&\xc3|fu\xbf\xf8\xe7PA\xa94\xb2\xc52}ʡk\xba-1\xc1\xd2n\xd7\xec\xae-V\xe6^ݨA\xc3ߴL;\x82\x8f\xaa\x99\vL7\xa0U#\x8b\x8fj-ۊ\xf1#n\x86\xe1\xe6\n\xf9\xb0Թ\x82+\xca \x96\xce\xc1\xf4ʛ\x06\a\xd3\xd6\xe0\x92\xcb\xe2\xd6.\x0f\xd2WR\x9bx\xa2=\xa2\xf0\xb9\xe5vxy\xcbE\x17֒\x8b6b%e\xe68\x87\xf6\xe0\xc9\x13\x9fx\xc5p3\x13|\x81)m\x8c;e]\xdf\xd8]\xef.\x85\xd70\xf6r)]\xa0\xc6bF\xb1\xbb\xa71\xd6N遯\xbc\x10\xee\xa8\u05faZa\x0476dmĉy\x14GѦ[\xb8\xf2\ue232\x0e\xbf\xb3\x9d\xc1\x9a\f\x11=\x0e,\x81u\x0e\xfd\xf3\xc7\xeb\u007f\xfd\xfb\xe6\xf3\xedOw\xff\xf9yv\xff˯\xbf\xfd\xfeǟG\x10\n\xfe\xc0m\x9f{4\x9e\x9c\xbe{\u007f\xf6\xdd\xf9\xf7=j2\xe7\x92[\x9c\xd5\xd4\t\\\x976!\n\x93/\xe4\x18\x1a:\xe8\xb9r\xcf\xd2=5=\xc1-7\xe7\xef\xfd\x9b\x8d\x12\n\xc4\xe4K3>;\x1f\xb9\xbdf2\x1a\xedV㰚\x84\xd5iX\xbd\v\xab\xf7au\xd6!NF\xa3\xef\xc2\xeeyX}\x1fV\xac[M\xc2\xe9$\x9cN\xcan\xf5\xbe]\x9d\x92\\N\xfb\x15\x13\xbc\xb8\x92\vUt\xaa'\x8d-ϡ\xb1\xe5\xf8\x8c\x9e\xa7\x13\x10\xccr9\x06\xd9Ts\xa4δ\xe0<\xf1\xe3P\xcbO\x1d\xf1~[c\xcbϥ=\az\x12\x02\x97\xf6t\xe2^g\xef\xc0\x9d\xf8\x03\xbf\xef\xb7K\xa1\x18}\xb9\xf7ٻ\x0e<\x8e\xfa\x8dԍ\rY\x1cm\x87{c\xda{\x19\xeeM\xdaV\xfe{o{՛@V\xbb\x89\xc1\xa0\xfd\xe3\x18\xd5x\x9f\xea\xcfcT\x93}\xaa\x1e\xd1pN\xe9\x9a\x15w\xf3\x87\xe7\xe6\xed\xd4\x11G\x8f\x8d\xb13\xd4\xfc\xe0w\x86\xb1\x1aY\x95\x83\u007f\xf7\x86a\x18\xfe5\x91eq\x16ǿ\xe2\xc2*}\xa4\x18\xb4S\v\xf9(MZk'Tdl\xe7\xfa4\xf1\xeeM\xb2x\x15PB\x85Y\xd1W\x8b\xdev\x93\xe3\xeamrXu\xc5ԍ/\xab8\x8b\xff\x17\x00\x00\xff\xff\xbb\xe1i\xb6", - "x\x9c\x9cTKk\x1b1\x10>K\xbfb\xd0i\x05K\xc89\xe0Ғb($\x04\x1aC\xe9\xa9(\xd2xWEָ\xd2(n\xfa\xeb\xcb\xeez\xbd\x0f\f}\x9cl\x8f\xbe\xc7蛱\x9em\x8b\xae\x04L\u0a12\u0096\x940\xf2=%*\xec#\xc2\xdd\x06\xa6\x1f\r\xf2s \xae\xd4\x1a\xa6\xb4\x14'\xe3yK\xa9\xab\xe5\x1d\xdd\xd3\xe1\x18\x90{\x81\x03rK\xae:\xb5>`\xf5\xe618\x1f\x9b\x1e\a\xd9\xffBx\a\xb75\xf4u\xad\xa5\x96r2\xec{:\x9aUKw\x1b\x88>H\x91J|ĜM\xb3\xac\xedLj\x90\x17\xa5\a\xb2&䩄?-\x1e\xd9S\x9c\xa10\x970\xb1\xa4X6z\xb7\x81)\xaaK\x0e\v\x8c\xd2R\x8a`^0\xcc.\x9d1\xec\xa1D\xff\xa3\xe0'\xa7\xa5\xc8\xc8\x0fkH\r=j\xa0n \xc3\xcd\r\xa8o\xaa\xfbXѥ\xc8-\x9d\xbe\x9e]\x17\x1aR\x88\xad\x0f\b\x99Mt&\xb9\xa7\xc2\xc7\xc2pJ\x9e1\xc4J\x01\x80\xaa\a\x8f\x1aT\xf7=k)V\xb7\xdcSBc\xdb굆?\x89u\n\xaf\x83\x9e\xd6Rt\xbd\xf9|\xa5\xb3\xa5\x81\xa5\xc8\xc6\xc7\xdc\xe7\xa2;\xd2\xc1\xf8\xb8ȋ?\xf7\x93\x18\x92\xbb$}\x99\xab\xd2\xe0\xe8<\xf6jڀs\x86s\xfc0t\xa5\xf5\xb0UO/\xdf\xd1\xf2\xb0Ry\x84\xbdW\xf5\xc4\xd9\x17.\t\x9f1:\xa5\xf5\x1c\xb4@\x99\xfc\x16\xed\b\x92\xc2R\xa2\x8f4\xbb\x815!\x80À\x8da\xdc\xd1\xe3\xb4\a5\xa8\x0e\xbd\xa5\xa44\xa4\x12\xf5\xc8~0\x8c\xe9_$\xbexn\x17\x1a[\x9a\xf3\xa7\xff\x8a\r\x14\x11\xbaP\xc7\xfc\x06\xf1\x8c\xd1a\xd2\xe7\x93!\xa9k'c\xd0\xfd\x91I\xcd\a\xaen\xbb<\xc5\xd8\xc5ߺ\xf6\xf3\xfe\u007f\xbb\xc5\xcbt\xdds\xf5$u3\xff\x1d\x00\x00\xff\xff\xebP\xa1q", "x\x9c\xa4S\xc1n\xdb0\f=K_A\xf8d\x01F\xb1\xec\x98!\x03\x8a\xa2\x05\x86a\xcd\xd0\\wQm\xda\xd6&K\x1eI7\xfb\xfcA\xb2\xe3\xa4M/\xc3\x0eA \x8a||z\xef\xf9\xfeO\x8d\xa3\xb8\x18\xa0\x89\xa5V\xb5\x9d\xba^\xbe!\xb3\xed\x10\xb6\xdb\x1d\x04絪#\xc5I\\\xb8(\x05d\xc1\xe6<\xbe^Dr\x9d\v\xd6\xdfY\xefתV\xdc\xc7\xe3Al\xfd\v\xb6;\x18P\xfaؔZ)F\xdf\x02\xe7:\xe1\v\x12\xe3C$\xb4u_\x0e\x95VJ\r0\x12\xbe\xb881\xb8\xf6\xd1\xf94\xa3ԃ\xf3\x98\xa6Bc\xa9\xd9O2N\x02Gr\x82e\xf1C\x8a\n\x06\bv\xc0\nN'o\x9f\xd1WPl\xe7\x93\v\xf88\r\xcfH`\xf9 \xe4B\x97ZCa\x12\xb8I\x8bb\xf8\x87]+\xc3e)\xfc/\x03\x9d\u007fF'?\xa4\xee/\x14\x1b)J\xac\xc0\xb5e\x16\xce\xf1W\x17\x9a};\xd7M\x05u\x12\x1d_\xac\xbf\xa5\xeeVʍ\xf9\x94\xe4\xaf u\x1b\xa3\x8d\xd6\xf7D\x91\x12\xe2\xfe\xf9'\xd6\x02\xb5\x8f\x01g\xf3]\xbb^\x9e\rrm\x99A-uwq\n\x02\xbb\x1dl\xb25ov}ȴ\x01=\xe3\xd5\xcc\xe7\xcb\x11\xc6\xd0 \x01\xa3\x1c|\x94\xb9sX\x12g\x17\xa8E\xbdL\xfb\x9d]\x9b\xf3\xael\xd29\x86d\x1dcY\x9c\x9eB\xf8{r\x84\f\x1b\x88\x04\x1f\x13\xfe4`\x10.\x16\x91s\x00\xb52\x89\xcew\xcb|\x908r)4a\x92\x9fP&\n_\xaet\xb9|\xc7\xdc3S}\x0fEeJ\xd7\x18o9/\x12\x98\xb4\xf7\xe8\xe4\xd2\xf5\x81\xbb$\x1fR\x9e\xcf\xcegۖ\xdaI\xbc4\xc0\xddR\xf4\xb1\xb6\U000c7e43W\x12\xe7$\xc2\xcdM\xcab\xfa{}\xb9\x06s\x86I)\xccl\x0e}<^3*\x8b\xfb\xa7\xa7\xfd\xd3\x162\xd2\xc0\x9d\x81\x91\\\x10\x1f\xb4\xcac\xa9q\x89\xb2\xe3U\x81$\x8c6\xfao\x00\x00\x00\xff\xff[3c\xce", "x\x9c|\x90?O\xe40\x10\xc5k\xfbS\xbcҾ\xf3\xea.W\x9c\x10\xd2\x16l\x05\r\x14)\xa8\xbd\xd9\t;$\xb6\x17{\xb2\xb0|z\x94\x84?\x11B[Y\x9a\xf9\xf9\xfd4\xefv\b[\xca\xd8%\xa3U\x93\u0096\xa3\x17N\xb1\xe0r\x8d@\xb2O;\xd39\xadT\x1c\a\x85\xfa\x16\xbfQi\xa5:\xac\xd1!p4\xd3p\x85\xcej\xa5\x8e#5\xae۔\r;T\x0e\x9d\xc3\x11k\x1c\xf1\v&b\x05\xb6\xf8\x03\x9e`\xad\xacV\a\xcaa\x90\x9f\xa5\xdf\xe3\xfe:t\xabj\x11\xf8\xeef\xfb\x99\xa7\x95/\xd7\xf4\xb2H\x92\xb4\xf1\x85\xee\xf7\xa9\xa7\xcdI\xa8\x98\xea\xff\x88\xfb\xb2\xe1\xe8\xf3\xe9\x1c\xf9o\x06\xef\x1a\xf1\xfd9\xee\u008eb.7\U0006a9a7\x81bC5\xc9\xe2\x87V\xeac\x81\xf2E\x14\xb4)\x93o\xf6\xa6\x908pk\xa6>\xc5:d\x92!GH\x1eh:\xae\xf5}\xa1\xf9\xc0ǡHM\x99}ϯ\xb4[X\x8ad\xf2\xc1a~\xf1\x9cYh\xaeȗ\x9aá\xa7Z2\xc7\ak\xb5\xd5o\x01\x00\x00\xff\xff)\x82\x96\xc4", "x\x9c\xecZ\xeb\x8f۸\x11\xff,\xff\x15\x03\xdd\x17\tQ\xb6\u07bd\x00\x05\xd2\xdd\x00\x9b\xe0\x0e\xb8^\x92\v\xceE\xfb\xa1\xe8\a\xae4\xb6y\xd6kIڑs\xe8\xff^\f\x1f\x12)\xcb\xf6\xe6\x89$(\x90\xacmr8\x9c\xd7o8|\xbc\xe4RA\xd1$\xb3hɅT\xf0\xf4\x06*T\xeb\xa6H\x98J\xe6i:\x8b$\xe6M]\x84\x1d\x97ԡ\xd6\\\x8cگ\xa8\xbddc>\x92\xbfCx\fz\x94\xc0\xa0W\x96\x8b\"\xb2D/\xc9\xf5\r\\\xe9QL\xac$Qi_\u05fc\xd4#+\xdf2\xb74\x1ejV\xa1\x99\xc5EW\x97\x01\xcdp\xa3\xff\xb6(\x96\x8d\xa8\xfe\xc5\xd5\xfaV\xac\xc8JI\x95\x81f\xceԛ\xadJ\xe6\x19t\xe9 ,\xcb\xf3\u05ec\xc2\xe3\xf3t\x13ݗC\xf7X\xc2+-w\xae:\x1d\x89\xbf\xdd\xfdA1i\x00\xd5\n\xa4\xf8x#\x1a\xd5$\x9e]\xf5\b\xbe\xf4\x9b`\xcd\xe4\xcb&g\xa5\x81+\xf9$N\xb5\xbc\x86\xb5t@\xd6=Y\xe0%\x1dnD\x9a\x8e\xcdt8\xdej\xaf\r\x98\x1evw\xa6\xb33]\xc6Ț`\x88\x84\xaa\x9f\x8a\xfe\xb3<נ\xb6\t\xe3\xf7O\x1e\xef>ۯ7\xec\xe7\x19<\x0eB\x9f\xb8S\xfc\xeb\xcf~\x9d\xfa\x84X\xb0\x96\xf9\xf9\xff\x90x\b$\x0e\xac\xf5ŐA\x85\x0e\xad/v)\r\xa1\xf1\xde&z\xa8\x85\x1el\xa0\xf4#\x01\x1a*'\xf0~\xcb\x05J\x83\xca\x1fǨ\xd4U\xdd<\xe4|\x99\x82Zc\r\xc9t\xc4S\xab\x86\x0f\x19\x85\xa9dc\x8b\"\x93\xeaS\a\xc5\xef\x13\x83\xe7\xc07*\xd6IEV\x96M\xce\x14\xfe\xa3Y\xf0w\xa8w=\xe9\xfb\xa3\x93V\xb8\xa1\xeew\xfe\xebw\x1e\xbbx\n\xa1֓n\x93\x10\x90\xa7\x9f\x19\x95\x13\x02\x1f\x04\xe2\xa1\xbd\xaa\x14mf\xed+P}\x87\x80\xb3Z\x9d\x01\xdc\x17\x84\x91@\xb5\x15u\xe0\xc9o\rC\x0fT\xe1\x9b\a\xd0y=gQTs\x8b\x9f\x8a\xb5\xdfi\xe1\xe8i\xf6\xf9p48\xd3\xec{6\xa1\xb3&\x00\xf5e\x10s$Ȧ\x83\xa9\x17\xfd \xa4\xbeIl\xa5\xfeϾ>\xfd\x9f\x9d\xd2\u007f\x161i9L\x98\xc1\xf5L܇V\x94goݺ\x9cT\xc3\x1a\xed\x821\x18L\xd4/X\xbe\xc6\xe2w\x94\xdbR\x8d\xf3G\xea\x84Y\xf0\xaa-\xd1d\xe0P\"\x11\xdcO\xfc8O\xbd\xa2;\t\a\x0eIܠF\x80\xbd\x02\u007f2'S`\xb7\xe8\xd9\xfc5\x85\x8b\v\x88/..\xe2\fD/\xc6\xdfepK\x1a\xff;&2\x9d\x00+\xd6&\x86 \x05\xcayI\x9cņ\xc9\u007fbk\xd0q\x8dW\x8d\v\x16gȖqA\xb64U$\xfd2\xb7\x8c\x19\xb8\uf5fdmh5z\xb1\x15\xb2\x11\aE\x9e~\x99\x10\xe9|\x01O\xed\xa1tޔ%\xe6\xbaĢ&\xbd\a\x8bj\xecF\xc7\x17v\x94\xcd6\xee\xc0Ѣ\xc2\xe3\xe1\xe7fZN4\xf53\xa8X\x97\x81cQ\xb1\xeeo\xb0d\xa5\xc4\f\x94آ\x8b\xb4V\xe0\x8e7[yr\xe61\xefk*\x13\x1d\xc9|\x92\uf395\xc1\x1d\xbe'.S\x86\x8d\xd9r\xe9\x9c\x17&À\xd6&E=bT4h\xa4\xe8%oz\xa2~-\xed\xa7KgQ\u07bb\xc9{\x96`}7\xc0\xa1g\xe2\x9eR\x98\x87\x11\xbf⨮\x97\xf9\xfa퐔r\xc5wLa\x01\x8b|\xfd\x96\t\xf5\x8e\xb3:x\xb4\xe0\xaazW{\xeb\xaaX盻\xa6\xd8OV\xc4=\xaeYi\x12\x93^\u007fv\xac\xf4\xf2\v\r\x9eH1Z8V\x14o\x18\x17zUae\x9fe4\xaf\x1d+O\xdf\xc6\x1dL\x1e\xee,l1g\xb8\f\xfc\x8d\x88\x9a\x14w\xac\xbc\xf5*\xe3\x932\xa5֠\xb3(\xb5\xb0\x19\xdbq\x1a\\\x11\x01RzP\x8ax\xcd\xfd\x982\xfd\xf6\xceS;3`M\xb2\x1cc\x1dm\xc8\xe5\x03g\x17\xd8^\xcb\xd1\xc4\x18'?\xfcI\xc3C\x82\xff>\x85\x1f\xfe4\\F\x1d1\xe8\xa7&mS2\x85\xd6*\xf4ך\xcb\xe3\xac+/0jٔ>֧\x8f\xe5_q\x9flR\xfa\xf6O\x9a\xd4<\xbe\xdf\u007f\x95[Dj\xd5r\xd85\x9c\x1d\x93\xed\x1btof,\xa3\x95\r\x01\xa9k\xf87({ew\x1c\xafK\x92\xa3>\x93[\xef\x01ﭿ\x8e}\x82-G\xb4\xed\x90\x19\x0f\xd0\xe6\x99B\xb1\xb2\x9c\xf9\xbcY\xc9=\xe2\xeb\xb2dŢ\x88Vp\x91\xe3\xb6U&\xaeg\x91u^\xaf\xb2|\x1b\x9b\xf0\x83\x1d\x97\xa9\x94_S\xf0N\xaa\x93\xd3\x13\x03ɻ\x16\xab\xf3\x1a^\xbc4\xf0\x95\x1d\x95p.\x96\xc1\xa55O\xb1\xc5b\xc1\x13ē#\xe5j#\xa6d{\x04o78ӶLb\x1d9\xeao]\xc4V8\xeery\xb0\x11I\x8eB{ħ\xd8lt\x1e\xbe\xfcs\xc3c`B\x92\x02{v\x06̀\t\xc1F\x84\x11m&\xf0\x98R)\xe2I \xe4&\x9cj\x85ߝ\xac\xc5\xcaT\xcc)\xdf\x1a\xc18\xae]O\xd6\x1fBF\xff\b\x00\x00\xff\xff\xc0\x04\x8b\x00", - "x\x9c\xa4TMk\x1c1\f={~\x85\x9a\xd3\x18\x86\xd0sa[B\x9bB(M\x02\r\xf4\xec\x8c5]S\x8f=\xb5\xb5\xc9n\u007f}\x91<\xbb\U000d1924\xf4\xb2\xec\xd8ғ\xf4\xfc\xf4>\xb9\x84-\xc5t\x00\x1b\xebJ=:\xda»\r\xf4H\xdbh\xeb\xc1ж\x81\x8c\xbe\x83\xd6ǀ\x90\x91n\rm\xe5B\xeb\xaaR\xd9\xfd\xc6Y\x82\x84:\xc2>\x03\xdfpD\xe7<^\x9b\x1e\xed,,\x98\x1e\x9bJ\xa9\xcf\xce#p͚Q\xcb?\x81(\x859L\xebJ\xe9J\xd9c\x9f'\xa8.z\x8b\xe9%\xe4i\xae\xd7\xc0Wj0\t\x03MY\x13b\xa5\x94\xebd`\xd8l\xe0\xec\xfc\xac\x81\x84\xb4K\x01\x82\xf3\xbaRj\xe0`\xb9矏\xb1\x1fb\xc0@c\x1e\xb8|\xd9\x0fth`\x00I\xe7\x94\x11`\xd5\xe4PF5\xa1\xc5L1\x1do\x1d\xe6R\x81;\xcc\xcb\xce<\u007fz\x97\xb9\x9a\xd0\xc0\xe3UJ=n\x9d\xc7\xda\xc2\x06,\xacFk\xc0\x83\x19\x06\f\xb6\xb6<\xbd\xf2\x90\xf0\x01Sƫp\xebM\x8b\xa5\x89\xb6ŜݽNj\xb1\x1d\xa9<\x1d\xdf>\xd7\xces\xadg\xf4\xd8҈]\xbb|q\x82\x18\xa9\xf7\xb15\xfeJ4\xb3d\xbd\xc8h\x99N\xd87\"0y=x#\x94\x82\tv}(Dτ3\xb2XT\xf3o\x95\\\xfe₽\xe9\xea\x13\x87\xfa\xb9\x8a\u007fm\x83\xb7\xe05U\x8f\xa5x3\xf4\x94\xca2_\xa5\x17\xc4\xde\f\xc7\\\x91\xf3T컣\xed\xe5\x9e0d\x17\xc3,\x15\xf7\xd4\x14m➠\x8d\x81\x8c\v\xf9\x1b\xfe\xaaY\x9c\x10\"5\xc07e\xa4\xf3s\xfeЧz\xcbveN\fV\x8a1\x04ǎ\xcf\xda&4\x84W\xdd\xc5}\xc6@O\x16J\xf6\x10\xf7.S\x96\x9a\x95RO\x96\xf0\xc3\x12\x83C\x8a\x17\xc9q\xa5xVU\x14\xaf+\x95\xb0\x8f\x0f\xb8ڏI[]Lh\xdam]\xc2\xf4\xc2}N\xa6\xa0\xa1\\\xcfq\xf9\xfb.\xae]\xf1\xa5\xfc\x12]\frDY\xbafa\xe7\xd1\xf8\x9f\xcbV[\xe3=X\xf4\xf8\xc3\x10\xdeů\xe5\u2fd7\xa0\x81\xb3\xde\f\xe2:\xd6\x15\xe1\xcfV\xa2\xbc\x85\\\x88\x93\xbf\x87\xb7\r\xc8\xe7\x91.\xebR\x03\xab\xde\xf8P\x8bw\x04\xe7G\xee\xdb]\xca\xee\x01\x99\x93|\xd3\xdd\x1d\x86\x85^\xf3\xae\xeb\xdc\x1es\xb3\xf2,\xa6\xa1\xccÏ\xeb\xbaz\xb9p\xb2\x052\xd8\x11\x00,\x12\xb6T\xe79\x01G\x05\xd6Yk\x01R'\x8b\xe3 nT\xa4\"nW\xf8\x9f\xfb\xd0\xf2\x1d(\x1d\n\xeb\x1a\\w\xed|=\x9a5\xa5\x9d\xa8\xa63>\x8bG\xea\xeaO\x00\x00\x00\xff\xff\xb2\xdaF\x12", - "x\x9cT\x8f1j\x031\x10E\xeb\x99S\f\xae$\xc8\t\x02i\xd3\xc5\x04\xf6\x04\xb2\xf4m\t\xb4\x1ayG\xeb\x90\xdb\a\xad\x8b\xe0b\x8a\xffy\x1f\xde|\x87\x91%\xa9c\x1a\xbf\x1d\xf2\xfe!\xa7Y\x9d\x98駌<\x8b\x15#krLd3.\xb8\xefh\x11\x12\xab60Q\f\xb5\xca\n\xb3p\x83\x84\xed\xb6\xafh\xc3\xe4\xaa\x1bB̮\xbf1\x11=\xe6\xf4 \r-a\x93\xa4_ω\xeb\xfe\t\x94\xebY۹Tg\x12zGKSd\xc1\xdd=\xfc$\xe6\x19\x93g\xa6\x91\x8b-\xbao\x11\x9f\xa5\xe2x\xe1\xdf\xf3\x88\xe1bZ\xf7\x01\xf7\"W\xc3\x05\xd5{\xf6\xfc\x17\x00\x00\xff\xffp$L^", "x\x9c\xecUMo\xdb8\x10=S\xbfbV'\n\xcbu\xe2\xec-\x8b,`\x14)\x90\xb6n\x03\x18\bze\xad\xb1͈\"\r\x92V\xec\x14\xfd\xef\x05G\xa2-KI\x83\xde{\xb0-j\x86o\u07bc\xf9\xf0\\n\xa1\xb4\fTL\xa8Z\xf9\x10\u007f\t%ʓ\xb1Zn\xcf5\xd1\xf1H\x18I\x94\x8a\b'Af\x81_\x16`d\x1dM\xcd\xd04=\x9a\xea\x81\xe5*\x15\xe3X\xc2J@C\x92\x92\x97GS\xa2\x03\x8fa\xa1m\xe0\x95\x11P\x15\xafY\x1b#`\xdd=\xe7MN2\xb3\x8b\x8b\xf7w_\xe7\xb7\xd7\xc9\vP\x86\xb6\xe5\x9cհ\xd2\xf6\t6\xe8\"7\xe6\x8e\xe4:\xe0\xd2\xce\xd1{\xb9F^\x13\x96\x06\xb9ݢ)\xb9K\x15Ա\x821\x01\\\x867\xba\xe8\xacQ+JQ\xad\xf8yG\xfd\x0fS2\xbc\x98\xdeX\xeb$\xc6\x18\xe8\xe6\x06\xfem\x91ނ\x9a&\xa8S'\xb5\x98\xf4\xfd\xbb\x126G\tO\x1d?\x1a\x9a\xa2˽_+\x01i\x84\xaa3*g\xa3\xc2J\f#\xa1\xff\xe8\xfak]\x1d\x86\x9d3\xc7)\x1fkK\x03\x8fn\xdd\xdf\xd26lЉ4\x9b\xd4\xc2@>w\xe6^\xcb%\xb6\x0et\x99\xf5߿\x04Qŗ\xb1JY\x97\xc7q\xf9\xec\x05\x90W\xe4\xb3':\xc3\xc5O\xeb\xa0\xe3\xe8\xb0A\xe7q>\xdcMc\xfc\x04\x9erm\xb1\x8f\xc3\b\xa3(M[\xf1v\x19\u007f\xf9\xf68j\xb2}\x11a\xab\x01\xad\x9d\xe3\x02%x\x82\x9b\xc7'P3\xe6qW\x91u\xe2`;x&\xc0\xb7\xc156\xeb\xe7M\xbcsg3~\u0602\xfer\v-Z\v\xf4\x82\xf6\xda\x1f\xae\xb5\xda֩\xf2\xc4\xd2\x18\xd7\xd2C\xacK\x8b~D\x91F\xf9(\x95\xfa@q\x13(\xd7\xcac\"}\xd7\xca\xf4-\xbb\x17\xf2\xba\xe7\xa7\xfd\r\xa04\xea\x8d;T\x8b\xe5\x97\xf5r\\/\xbb\xfd\xf2\xeejy\u007f\xb5l\xbe.\xe2\xbdA\xf9W\xf8\xbc\xe1W\x04\xe4L\x16{\b\xe33\xf9S\xe2\xbc6'\xe0\x1dd\xcd\xc1\xfdG\xbdV\u007f\xfd\xbd\x82\xc56iD\x17\x1b\xc5\xf1\xf8\x89\xe0\xa3+\xf0Q\xa8.벼\r>\xbf\xc2\xdf>lց>=\xed\x14\x8c\xdao\x88\xf5\x9f\x10\xebwD]\xfe\b\x00\x00\xff\xff5Vh\xa9", "x\x9c\x8cVKo\xdc6\x10>K\xbfb\xa2\x1eJ\xa2\xca\xda\xe9q\r\xb5p\x8d\x04\b\xea\xd8E\xd7@/\xbdp\xa5\x91\x966\x1f\nI\xd5v~}\xc1\x87v)\xd9q{\xb1Lj\xe6\xfbf\xbeyh\u007f\x13\xba}\x80N\x93\xb2`v\xc7\xe5(p\xe7\fW\x03l\x1b\x90\xe8\x0e\xba#eQ\xf0\x9e\xd8V\x8fXC\xb5\xf7\x1eU\rU|[Q\xd8l\xa0\"\x95\u007f03L\x12\x95\xbba\x12-\xb0qDՑj\xb3\xd9T\x14\xee5W\xc4\xfbE\aZ\x95\x05\r\xa4k\xbaO\xdaH\xe6\x1c\x1ah\x85V\b}8\x87@ɀn'\xb4#\x95E\xd1W\x94\xc2~\xeaiY\x16-\x13\xe2/\xee\x0e\x97f\xb8\xe6\xd6eh\xcc\f\xb6.\x8bb\xe5\t\x9d\xfe\x82ֲ\x01\x83\x050\x9b\x8e`1\x84O*\x8fYQ\xea\xc3,\x8bSP\xdb\x06n\xf7\xf7غ\x14^Ю\x10\\!\x00\xc0v\xdb\xc0\xb9\x17\xcc~\x94\xa3{\x0egg&,\x8b\xa2\xc3\xd1\x1d2\x93\xfd\xd4\xc3좸(\x8bB\x8fvu\xc3\xec\xa0\xf2\x1b\x8f\xac\xb8[\x16' 5\xb0ï\x13\xaa\x16c\\\xfe\xde\xe35p;\xa2aN\x9b;\xb6\x17\b:\x9d\xac7\b\xf0k\v\x83\xff\xa0\xb1xi-\x1f\xd4\xed\vs\xe6\xfe\x98\x82\x8cAO/\xfa\xdd\U000c8fb0ۦ\xa2e\x11\xe4*b\xedw\xf85\v\xd5\v\n\x1d\n\x1c\x98\xc3;M|\xed.`V\xaa\x81\x9e\t\x8b\xc1[\xe1\xe3\xb5\x17\xf4e\x9aG\\R\xfd\xad\x02]\x94\xbe\x81\xf0\xf8\t>\xf8\xab\x13fҞ\x9e@\xed\x9d\xcep\xa5\x1d|w\x14\xfe\x9f\x00q3\xc9=\x1ax\x1f\x0e\x14$W_\xd8\x139\xaf\xe1g\n\x06Gd\x8e$\xa0\x9c|\xe9=\x8b\xc0U\x87*/Vl\x81\x04\x93\xa5\xe2K\\Q\x1a\x9c\xb2v\xcf<\xf7\xe2!\xc4鉶\r\x1c\xbby/\x1e*\n2\xf6\xaew/\xce\xce\xe0\xee\xc0-\xc1k\u007f\x1fX\xb3P\u007f\x800\xe9'??\xcc\xdb\xd3\xddQ\x84\x1aB\x85\xe0\xec\fB^9HJ*֏\xf7d\x95\xcer\x8dX\xfe\r\xe1\x178\x0f\x91d(o:\x9d\xb6M\rU\x1d\x89\u009fT\xbc\xb7j\x97J\xf09T\x0f\xbbyOH;\xac\x11\xb2\x94h\xe4\xf0\xdbe.\xff\xab0\xaf\xf4^\xec\x8a\x06\xe23\xb5pt^s/M\xdf\aӌ\xeb\xfb\x1c2\xdc\xd9\xc1\xff\xffx\xe0\x02\x89\x8cz\xfa\x1e\xf03\xa8\xba\xdbާ\x15o\xfdu\x94\xa7\x01\t\n\x9f\x1c\xfc\x9a\x956\xdad\xe9KPLƉH:{\xca\xd97\x9e[\xad\x1cWa\x1cg\x9b\xd3H\x12I\xe7p\xd2\xf0\xd6\x10ǧ\x0em\xea\xdb\xd3\xe7\x92/\x8082\xc9+\xec\xa6\x03\xb3\xbf\xe3\xf3\x1c\r\x05w@E\"{\xd8\xf2^\x83c\xa3\xd8c\xa6\xe1]ύu\x1eኵ\a\xec\xfeD;\t\xf72\xd3̶\xcd\f\xc3\x18\xc4\xd1H[r\x8e\"\xbdI\xa2\x1c\xe9\x98#\x1fh\r\xcb2\x9fޤĊ\xff\x90:\x96\x8f\x99\xe1JOʅ)I\x90\x97s\x96D\xd2E](\xa0\xb0\xc8{\xe2\xbf\x0eo\t\xb6b\xac\xb3,f\xd6\xc8\x10\xe5X\xe7\xf2\u0080\xe6\xfc\xdf!Y\xe1\xff\x8f\xac\xe2\xdfe\xaf\xd1\xe5T\x1c\x9d^\x99\x8b\xac\x9d\xd2:\xb2\x8e\x99\xb0\xb3E\x1ar\xbf\x1f\xb2T\xb4A\xd6\x1e\b\xaf\xfd\xe5q\x86\xf8iC\xe5\xb3\x13\xc0\x16M\x1bWRj\xfc\x8b\xd4\xe3\x17\v\x8b\x8a^@\xfe\x8dI9\xbe\xbe\x95\x98\x19N\xcbmf~wd^\x11\xd1W\xb7V\xfa\x8ds?Y\xb7CÙ\xe0߰\xcbIJ\xce \x935\xc4'<\x1a\ue434\xbaCJKZ\x96Wڠ\xff\"]떉\xb8\x92\xaf>M\xaau\\\xfb\x0f\x00\xefo\xb8 \x1f\x9fZ\x1c\xfd\r\x18\xc6-\xd2\xf8\xbb\xc9=\x8fa[e\x0e%-\xff\r\x00\x00\xff\xff}\xa2\x0f\x18", "x\x9cT\x8eAj\xc30\x10E\xd7\xd2)>ZI`\x8c\xa1\x85BC\v9@V\xdev3\xb5ǎ`,\xb9\x1a\x19JO_\x14\x92Evz\x9a\x0f\xef]X\x95VƜ\xbd5\xa4c\xdcv᱖\x98V\xbc\u007f`\xe3zͳ\xb7\xc6hCeY@z\xbf\x93^\x8eJ\xdf\xc2(\xbc\vM<\xf2\x8fw8}%\xd7\xc1\x9d\xe0\x825&.^\xa1\xf1\x8f\xf1\x89ס\x83\x82\u007fG\x89\x13\xfb\xa1\xc3\xcb[@\xdf\xc3\xf5}\xef:h\xb0&Xk\x8e\x14sz\xb6o\r\x1f\xad\x93\xe4\xc4\xd6\x18i\x9f\x12\xb5\xfa\xd6\xd5d\x13\x89`\xbbϨ\xac\xe7\xea\x87\xd0\x1e\xc7Ʃ*\x96\\\x98\xa6\xab\xa7\xb2v\x10оs\x9a\x1b\x85p\x93(\xd7\xf3c\xec\xe5\x96\x13\xec\u007f\x00\x00\x00\xff\xffd\xa3U\xf4", "x\x9ct\x91\xbf\x8e\xf20\x10\xc4k\xfb)V\xa9l)\xc5W|ʼnS\n\x8a\xbb\xea\xfeI\xe1:\x9a%ـ\x0f\xc7\x0e\xb6\x13\x89\xb7?\xd9\x01\x029(\x13\xef\xccog\xf6\xb3#\x87\xc1\xba\x15n4Am\x05gX\xd7翰(\xa0\xa5\xb0\xb3\xb50\xd8R\x0e\x9d\xa3*\a{z\xf6\x80\xe1\xab\x0fWo\xa0\x9a\x0f\xa5_\x06\xd4⟔\xcf\xe0I72Y.\xbdW[\xf3иB\xad}\x0ex3u\xeb\x9fF\x00}yl7V\x03\xfa\xef\xd5\xebӅ\xc1\x99\xa3\x81\x9c\xa7\xe5\xccb\"\xcd\xcdO\x82w\xec\xa2\x1c}\x19\x9c2\xdb+\x01gl\x13?K:\xf4d*\x82J[C\x80]G\xa6.\xe9 \":n\xa4\xdaN\xd3(\xcf![\xac'H&9cil*m@ݓ\x87ިCO\xe0\xad\v\xd0XGX\xedDꗳȝ(\xd9\xda\x00d\xa7\x82/k\xa2V[\xf3FM\x10\xffe\x0e3\x84'MU\x10\xfb\x1c\x86\x1c\x06(\x8a$\x96\xb0\xa7\xa3\x1f\x89?V\x19\x91A&を\xff!\xae\xcdX$܉2/rr=\xe7H\x17\x8b9ƣ-\n\xb8+\xc4\xf1\xb6\xf2Q\xe2\xf88\v\x9a\f/;s&\xb9\xe4\xbf\x01\x00\x00\xff\xff֯\xebP", - "x\x9c\xa4RM\x8b\xdb0\x10=[\xbfb\xd0ɂ\xb0\xbe\xa7\xe4\xb0P\x97.\xb4I m\xef\x13i\x1cO+KF\x1a\xecl\u007f}\x91\x93.\r\x84v\xe9\x1eg\u07bc\x0f[O\xd8;j\xcf#\x06\x97\xbfDXo\x80\xbb\xfa\xf0\x9c\x85\x06\x18=J\x17\xd3\x00\x9b\r虃\x8bs\xd6+U\r$}t\xbf\xafN$m\x988\xc50P\x90o\x98\x18\x8f\x9ej\xfd5Sڧر'm\x8cz5\xef\xe3\xees[\bF\xa9G\xe7b\x00\x17kU\x8d(=\xac\xd7\x1b\xd0\xfa\x05\xf9\x14\xd1Q*\x99w\xc7\xefd\x05\xac\x8f\x81.\xf7\x1cX8\x9c\xee\x81<\x96\xed5\x05\x87,\xe8\xfd>Q\xc7\xe7w T\xb0۟bT\x95\t\x93\xed\xf7(}.\xb8\xe7,\xb5\xaa^Lx\x04\xee\xb61lٷ\x13\xfa\xfa\x8f\xfd\xe2\xfa8\x8e\x14\\\xa1\u05fa!JL\xcd\x1135X\xbe\"k\x03\x98w\x87\x02\x9b\xd5\xdbT\xd1\nOԆ\xe9\x1f\xd2B\xf7\xa5\x85\xeeH?\xbc:\xf1\xff\xc8\xfe-\xb2\xaa\fd\xf2d\xe5)\xec=Z\xaaK\rV\xb0\x94\xe1\xc6\xe9='\xb2\x12\xd33\xcc,\xfdrf\x80Μ%\x1b\xa3\xaaDC\x9c\xe8\xe0\xa3\xd4\xfa\x1aJ\x1bU5\r<\x95\t=\xff$X\xfc\xe1G\x88\xb3'w\xa2\x87\xdbg\xefb\"\xb4\xfd5\xc1\xa5\x99\xd9b\xf8\x10Ӗ\xe6e\xce\x17\xdfR\xdd_\x01\x00\x00\xff\xff`8#d", "x\x9c\x8c\x92Ao\xd40\x10\x85\xef\xf9\x15#\x9fl\x91J\xc0\x91\xaa'\x04\x82C)b\x0f\\\xb8x\x93Iv\x8836\xf6\xa4l\xfe=\xb2\xd3d\x1b\xaa\x95\xeaK\x94y\xef}/\x8a\xe70'\xc1\x11Z\xaf+\x00\x00\x8c\xd1\xc7o\xd3x\xc4\b\x1f\xee\xe0mU\xa6\xa3=\xff\xc0fn\x1c\xb6\x0f\xc7\xdf\xd8HZĬŝ\xf0\xd1O,\x171\xa1\xdc_\xc9.\xfa<\x1e\xbd+\x13GI\x96a\x8f\xf2\x10\x84<\x97\xf9\x88r\U000addb1Ou\x91\xf3\xf1a\xc1\xdc\xdb\x00\x8d\xf3\x8c\x9b2\xf1\x803\xb6\x1bq\x9d\xe7\xa2mN\x9a\xea\xfc~\xc1=\x19\xe0\x88=q\xfaIr:\xe0\x1f\xadnn\x94\x01\xea>[\x97P\xaf`\x1b\x02r\xf9\x1es\v\x8dg!\x9e\xd0\xecPCnϼ\x8e\xb8-\xa4;\xb5wP\xa7\x87}}>lG\\\xa3x>8jP\xbf\xafa0/\x9c\x8f\xd6\xfdo\x1c\xe0\r\xbc\xdb;_\xd9p\x15\xaf\xd4N\xd9\xfb\xca\x15X\xf9>\x89\xce\xd4:\x87.\x0e\xf3\xe2B(}\x1a\x83\xcc\xdb\xff|\x96W\xaa^m\xc6\xec\xee\xb8ZX\xe51%\x8c_Y0\xc6)\xc8\x17˭[v\xf4iA\xb6\x9c\xfa\xc5\x11\x1b\xa4\xc7\\\xba\xfao\x01\xcf$Ľ\x82\x10\x89\xc5\xf1\xe6O躢>/K\x0e1d\xfa\xb2\xb3y'\x0f\u038bV\u007f-\x892\x95\xa9\xfe\x05\x00\x00\xff\xff:\x06ܿ", "x\x9c\x94\x90A\xaf\x820\x10\x84\xef\xfd\x15\x1bNp\xe2\xfe\x92wy/܌\x1a\xffA\x85\x95T\xebn\xd3n\x0f\xfe{CQ\x100F\xaeۙo\xa6\xb3e\u007f\xd5\x16~~aw\x85\x95*n\xf6\x00_\xfa\x15\xdbRQP\abB\xa4\xc4F\x8e\x97\xf3#Њ\x86v\xf4ff\xe5\xcbV+\x1b\x16M\x10\x9e\xe3\xdcz?\xb1\x94#ϙ+\xe3\xdf?\x0e\xf6\xbb\x96\x04!\xa4\xf8\xe5?2E\\\x16\x9c\xdco8\xf6\xae\xe4\xc1\xef0R$|X\x13\xef\x02\x81 \x94}堦\x9b7\xf9\xdc\\\xd5t\xd54=\x9b\x13\x10\xbc\x02\x00\x00\xff\xffO\xdc1\xc7", - "x\x9c\xa4WKo\xdc6\x10>K\xbfb*\xa3\xb0\x88Ȇ}*\xe0bS8\xb1\x03\x18I\x9c\xc0\x9b\xa0\x97\\(iv\x975E*$\xe5\xf5\xc6\xf0\u007f/\x86ԃ\xf2\xabM{Z\x8a\x9c\xc77\xef\xd9/h\xddU\xa7\x14\x1a8Y\xc0\xa7\xf2/\xac\x1cTR+\x84Z\xe7i\xb2\x15\xb5\xdb\xc0\xc9\xc9\x02~;J\xd3D(ሰA\xb7\xd1u\x9e&\x89E\xb9\x82\x8a[\xb4t\xaf\x84\x1c\xee\xf0\xb6\xc2\xd6\t\xad\xfc\x83\x14\xd6\xe5lx3\x9dr\xa2Az8J\x13\x96\xa6\x89C\xeb\xde\xeaN=/\xfd\x86\xcb\x0e-\x18\xac\xbb\nsU\x80\xe2\r\xda\x02\x14\xbc\nG\xb0\xe2\a\x16pĂD\xba\x9b\v\x13\xab<\x92G\xd4\xf0\x1a\x8e\x8b4I\x92\xe5\xce:l@\xf2NU\x9beeD\xeb`%$^\xf2\x06\xd3$\xf14O\xb0/\x16=\u007f\f\xf4\x1aw\x16V\xc2XG/\xe19\xcb\xe8\x87\xec\xef\xd1I\xa1\xb04ȯ\x9f\x86\xf8G\xad\x9d-\xc0\x9f\xe9\b\x8b\xe8\xfc\n\x8e㧓\x05\x1c\xb3\xc0\xeb\xbf\u007f\x85\x10\xb4\xc5\x02\x8e\nȾ\xa9\fZ#\x94\xf3\x9a\x13\xdbU\x15Z\x1b\xa9\xcd\x0e{\x82\xdfaD\xc5\xd2\x04\x8d\xd1&\"#\u007f\x16\x14U\xb2(\n.o[Tu\xee\x03<\xd20\x02\x94\x9d\xf7\x82\xd3d\xb27\x98o:\x15\x89\xa6\xe0\xdb\xe2Q2\xf9\xeb'R\xe6\x8c;\x04\x8b\x95V\xb5\xfd\xa2\xaf:E\x9e\xf3\x19da\xa5\r\xf2j\xd3#\xb1R\a\xc1IBR\x89\xf9\x83.\xcb\x1d\xac\xd1-\xa5\x0e\x80Y\b \x91\x8e\xec\xf4\x15\xf8\x02\xa3E\xf7\xb5\r\xdfx[ypf\x97\xfb\xa7Z/\x9d\x11j\xedyX\x10F\xb1 O\x81w\xa2\xd7\x02\x87\x87\x90AF?^\xb8\xf7R\x01}8X\xa4\xcb!7gz\xab\xfc\x15\x8b\xf2&\xf1\xce<\x1f]?\xdc,\xbb\xa6\xe1f\x17<\xfb\x80f\x9e^S2\xccc8\x98\xddG7ɳE\x06\x06[\xe4\x0e\xebܧ\x13\xf3\x06|S\xefN/>\x9c\x04;\xc8\x13\xdc\xe5GÓ\xbf\xcc\x0e\x1eq\xb2\xa0Rz\x8b\x88\xc9n\xf4v\xe9xu=\xd4\xc3̎\a\x90\x1fˋ\xc4\xe5\xd9\x15W\x01\xcd\xd4A\xbc\xa7\xe9\xd3ߋU>=\xfdB\x05\v\x99\xcd\nȲ\x80\x1bD/`\xc80\xba\xb4\xdfT\x16\xeb1h;\xe9{S\x88\xec\xe08aϛ\xd6\xed@QD3\xf2\xcd\xf9\x19\xe4+.dg\xd0\xc2\xde]DK\x1d㞑>\x87\xa6Ւ;, \xfb\xf4>\xa3\xd0潆\xc3C\xdf̀K\xb1VWb\xbdq\xc1h8\x80\x9e\x82Č\x1e\x8f0\xb2\x94\xa5ih\xe5\x1f\xc5\xed\x85z\xba\x9f\xcf\v/\xea\xfe\x81\xc6t*o\r\xb6\xdc \xf3\x02\xbf*\xe1\x88\xea\x914\xa2BU\u007f6\xda\xe9H\xd3\xc4\b\xecڸ7\xbb\xf7\xb8\x1bz\x05I\x9f\xf5\x05h\xd0Z\xbeF\xdf\x16/\xbb\xa6D\xc3\xd8Pe\xdeG\x11Џ\xbc\x85\xadp\x9bпݮ\xc5\x02fF1\xe2\xa3̈\x98|{(`,W0\\X\xcc)\xc7\xc2K\xff\x93\x11_Ƽ\bn-\x1aw\xfe\xbd\xe32v\x14/\xa0,\xa0\xa1\xf2m@\xac.\x85\xcc\x1bX@ť\x1c\xec\xe8\x9d©\n\xca\x02H\xa6\xef\x9c\xd9\xfe\xde]\x03ܬO\xa9\x98\xef\xe9y\xba8f\xf7\xfbpp\xf0\x1a\xf6\xf7\xee8p\xbb\x14M+1\xb4\xbc\x9e\xb4|x\xbd?Kr*\xf2P\xe5\x01\xfa\xa5\xfe\u007f\xe8\x17/\xa3_\xfc{\xf4\x8b\xff\x80~9_+\x02\xf08&9\x87N\x89\xef\x1d^\xd4\x05\x94\xd1yfL\xec\x8d\x17D\x8e\xbe\xfaY\xa9\xb34\xe3\x0f\x11\x16\xb4\xa8\xbd\x80\xe8\x19\xf6\b͋\x12\xbe\x98\x0e_V\xefL\x87\xcfq\xbf\xe3\xd2\xfe\x03\xfb\x8aH\x9e㿢\x1a\xb2SM͚I\x18\xdbR\x02\xdepy\xda'\r\xebS\x8e\xd2\xc9\xe7\x15唧\x1a\xd3j\x9f\x86V'k\xd8\xf0\x1b\feZOu;˘q˛\x95*5(\xa1\xceP:\xfe(\xd65\xdd\x16!\xbfs\x0e\aP2ॅ\xd7\xd3K\x8fk\xef\x8e\xdf\x03\u07b6X9\xa4D\xe8\x1cl9͙\xf2\x1er.\xa5\xdeb\x1d\x98N`\xef\xce\x1f\xeeY6!\xbaVz\xab\xdet\xeb\b³\x06\v\v\x1c<\x03\x94\xddzn\xa2\x9f\rg\xc2`\xe5\xb4ٽ\xd5R\xfa\x13\x89\xa5i\xb1\xec\x84ß\x1d\x19-\xef\xffiH\xfec\xe7\xbb\xf0l+\xff\xcc݆\xd1\x1f\x12\xa2\x1aѷ}\xe3\x0e\x1a,:\"\xcb[\xc6\xc2Hy'\xe4l\x9c\x8c\x90C\xaf&\x95\xcc/\xfa\xe38\xf1\xd3\x17U=M\x142\xe8Ph\xdf~\x9fh\xfci\x12i\x1a\x16)\x12Y\xf4\xbb\xe6\xb8 \xd2%TZ9T\xb4\xdc\xfbO\x8f\x80\xe2C\xfc\x93\x1f{5\xfdT\x9f\xbf\xfd\xac[\x9fB<\xee\xd84\xb6\xbc\x984I\x02\xdcބq\f\x16\xe1\x9f\xd7\xf0\x17h\x98\x90\x99\xbf\xcd\x18\b{Z9q\xc3\x1d/%\xd2\"\x04\\Ձ\a\x84}/T\xfdi\x95\x0fk\x04\x9b\x96q\v\r\x9a5^\xa8ϒW\x98\a\x86a\xf7\x88\xb7^O\x1c<\xf1w\x00\x00\x00\xff\xff_\xe3\x98\xca", } -var coreFiles = []string{"io/00_Object.io", "io/01_Call.io", "io/02_Sequence.io", "io/03_Coroutine.io", "io/04_Exception.io", "io/05_Number.io", "io/06_List.io", "io/07_File.io", "io/08_Directory.io", "io/09_Path.io", "io/10_Map.io", "io/11_Date.io", "io/12_Block.io", "io/13_Message.io", "io/14_OperatorTable.io", "io/15_Addon.io", "io/16_System.io", "io/17_Stop.io", "io/98_Debugger.io", "io/99_UnitTest.io"} +var coreFiles = []string{"io/00_Object.io", "io/01_Call.io", "io/02_Sequence.io", "io/04_Exception.io", "io/05_Number.io", "io/06_List.io", "io/10_Map.io", "io/12_Block.io", "io/13_Message.io", "io/14_OperatorTable.io", "io/16_System.io", "io/17_Stop.io"} diff --git a/vm_test.go b/internal/vm_test.go similarity index 72% rename from vm_test.go rename to internal/vm_test.go index efceb75..28e8b82 100644 --- a/vm_test.go +++ b/internal/vm_test.go @@ -1,21 +1,23 @@ -package iolang +package internal_test import ( "reflect" "testing" + + "github.com/zephyrtronium/iolang/testutils" ) // TestNewVM tests that NewVM creates an object. func TestNewVM(t *testing.T) { // We can use testVM to test NewVM. - if TestingVM() == nil { + if testutils.VM() == nil { t.Fatal("testVM is nil") } } // TestNewVMAttrs tests that a new VM has the attributes we expect. func TestNewVMAttrs(t *testing.T) { - vm := TestingVM() + vm := testutils.VM() attrs := []string{ "Lobby", "Core", "Addons", "BaseObject", "True", "False", "Nil", "Operators", @@ -42,14 +44,14 @@ func TestNewVMAttrs(t *testing.T) { // TestLobbySlots tests that a new VM Lobby has the slots we expect. func TestLobbySlots(t *testing.T) { - vm := TestingVM() + vm := testutils.VM() slots := []string{"Lobby", "Protos"} - CheckSlots(t, vm.Lobby, slots) + testutils.CheckSlots(t, vm.Lobby, slots) } // TestLobbyProtos tests that a new VM Lobby has the protos we expect. func TestLobbyProtos(t *testing.T) { - vm := TestingVM() + vm := testutils.VM() // Lobby's proto is a generic object that has Core and Addon slots and Core // and Addons as protos. Check that this is all correct. protos := vm.Lobby.Protos() @@ -62,7 +64,7 @@ func TestLobbyProtos(t *testing.T) { } p := protos[0] slots := []string{"Core", "Addons"} - CheckSlots(t, p, slots) + testutils.CheckSlots(t, p, slots) opro := p.Protos() switch len(opro) { case 0, 1: @@ -82,29 +84,29 @@ func TestLobbyProtos(t *testing.T) { // TestCoreSlots tests that a new VM Core has the slots we expect. func TestCoreSlots(t *testing.T) { slots := []string{ - "Addon", - "AddonLoader", + // "Addon", // TODO: coreext + // "AddonLoader", // TODO: coreext "Block", "Break", "CFunction", // "CLI", "Call", - "Collector", + // "Collector", // TODO: coreext // "Compiler", "Continue", - "Coroutine", - "Date", - "Debugger", - "Directory", - "DirectoryCollector", + // "Coroutine", // TODO: coreext + // "Date", // TODO: coreext + // "Debugger", // TODO: coreext + // "Directory", // TODO: coreext + // "DirectoryCollector", // TODO: coreext // "DummyLine", - "Duration", + // "Duration", // TODO: coreext "Eol", "Error", "Exception", - "File", - "FileCollector", - "Future", + // "File", // TODO: coreext + // "FileCollector", // TODO: coreext + // "Future", // TODO: coreext "ImmutableSequence", // "Importer", "List", @@ -116,40 +118,40 @@ func TestCoreSlots(t *testing.T) { "Number", "Object", "OperatorTable", - "Path", + // "Path", // TODO: coreext // "Profiler", "Return", - "RunnerMixIn", + // "RunnerMixIn", // TODO: coreext // "Sandbox", "Scheduler", "Sequence", "SerializationStream", "String", "System", - "TestRunner", - "TestSuite", - "UnitTest", + // "TestRunner", // TODO: coreext + // "TestSuite", // TODO: coreext + // "UnitTest", // TODO: coreext "Vector", "false", "nil", - "tildeExpandsTo", + // "tildeExpandsTo", // TODO: coreext "true", "vector", } - CheckSlots(t, TestingVM().Core, slots) + testutils.CheckSlots(t, testutils.VM().Core, slots) } // TestCoreProtos checks that a new VM Core is an Object type. func TestCoreProtos(t *testing.T) { - CheckObjectIsProto(t, TestingVM().Core) + testutils.CheckObjectIsProto(t, testutils.VM().Core) } // TestAddonsSlots checks that a new VM Addons has empty slots. func TestAddonsSlots(t *testing.T) { - CheckSlots(t, TestingVM().Addons, nil) + testutils.CheckSlots(t, testutils.VM().Addons, nil) } // TestAddonsProtos checks that a new VM Addons is an Object type. func TestAddonsProtos(t *testing.T) { - CheckObjectIsProto(t, TestingVM().Addons) + testutils.CheckObjectIsProto(t, testutils.VM().Addons) } diff --git a/doc.go b/iolang.go similarity index 58% rename from doc.go rename to iolang.go index e292131..6f81870 100644 --- a/doc.go +++ b/iolang.go @@ -141,10 +141,130 @@ GitHub repository at https://github.com/IoLanguage/io for code samples. */ package iolang -// IoVersion is the interpreter version, used for the System version slot. It -// bears no relation to versions of the original implementation. -const IoVersion = "1" - -// IoSpecVer is the Io language version, used for the System iospecVersion -// slot. -const IoSpecVer = "0.0.0" +import ( + "github.com/zephyrtronium/iolang/internal" +) + +// A VM processes Io programs. +type VM = internal.VM + +// Object is the basic type of Io. Everything is an Object. +// +// Always use NewObject, ObjectWith, or a type-specific constructor to obtain +// new objects. Creating objects directly will result in arbitrary failures. +type Object = internal.Object + +// Slots represents the set of messages to which an object responds. +type Slots = internal.Slots + +// Tag is a type indicator for iolang objects. Tag values must be comparable. +// Tags for different types must not be equal, meaning they must have different +// underlying types or different values otherwise. +type Tag = internal.Tag + +// BasicTag is a special Tag type for basic primitive types which do not have +// special activation and whose clones have values that are shallow copies of +// their parents. +type BasicTag = internal.BasicTag + +// A Stop represents a reason for flow control. +type Stop = internal.Stop + +// RemoteStop is a wrapped object and control flow status for sending to coros. +type RemoteStop = internal.RemoteStop + +// A Block is a reusable, lexically scoped message. Essentially a function. +// +// NOTE: Unlike most other primitives in iolang, Block values are NOT +// synchronized. It is a race condition to modify a block that might be in use, +// such as 'call activated' or any block or method object in a scope other than +// the locals of the innermost currently executing block. +type Block = internal.Block + +// Call wraps information about the activation of a Block. +type Call = internal.Call + +// A CFunction is an object whose value represents a compiled function. +type CFunction = internal.CFunction + +// An Exception is an Io exception. +type Exception = internal.Exception + +// A Message is the fundamental syntactic element and functionality of Io. +// +// NOTE: Unlike most other primitive types in iolang, Message values are NOT +// synchronized. It is a race condition to modify a message that might be in +// use, such as 'call message' or any message object in a scope other than the +// locals of the innermost currently executing block. +type Message = internal.Message + +// Scheduler helps manage a group of Io coroutines. +type Scheduler = internal.Scheduler + +// A Sequence is a collection of data of one fixed-size type. +type Sequence = internal.Sequence + +// An Fn is a statically compiled function which can be executed in an Io VM. +type Fn = internal.Fn + +// SeqKind represents a sequence data type. +type SeqKind = internal.SeqKind + +// Tag variables for core types. +var ( + BlockTag = internal.BlockTag + CallTag = internal.CallTag + CFunctionTag = internal.CFunctionTag + ExceptionTag = internal.ExceptionTag + ListTag = internal.ListTag + MapTag = internal.MapTag + MessageTag = internal.MessageTag + SequenceTag = internal.SequenceTag +) + +// Tag constants for core types. +const ( + NumberTag = internal.NumberTag + SchedulerTag = internal.SchedulerTag +) + +// Control flow reasons. +const ( + NoStop = internal.NoStop + ContinueStop = internal.ContinueStop + BreakStop = internal.BreakStop + ReturnStop = internal.ReturnStop + ExceptionStop = internal.ExceptionStop + ExitStop = internal.ExitStop +) + +// SeqMaxItemSize is the maximum size in bytes of a single sequence element. +const SeqMaxItemSize = internal.SeqMaxItemSize + +// NewVM prepares a new VM to interpret Io code. String arguments may be passed +// to occupy the System args slot, typically os.Args[1:]. +func NewVM(args ...string) *VM { + return internal.NewVM(args...) +} + +// ForeachArgs gets the arguments for a foreach method utilizing the standard +// foreach([[key,] value,] message) syntax. +func ForeachArgs(msg *Message) (kn, vn string, hkn, hvn bool, ev *Message) { + return internal.ForeachArgs(msg) +} + +// SliceArgs gets start, stop, and step values for a standard slice-like +// method invocation, which may be any of the following: +// +// slice(start) +// slice(start, stop) +// slice(start, stop, step) +// +// start and stop are fixed in the following sense: for each, if it is less +// than zero, then size is added to it, then, if it is still less than zero, +// it becomes -1 if the step is negative and 0 otherwise; if it is greater than +// or equal to the size, then it becomes size - 1 if step is negative and size +// otherwise. +func SliceArgs(vm *VM, locals *Object, msg *Message, size int) (start, step, stop int, exc *Object, control Stop) { + return internal.SliceArgs(vm, locals, msg, size) +} diff --git a/object_test.go b/object_test.go deleted file mode 100644 index ff2866a..0000000 --- a/object_test.go +++ /dev/null @@ -1,744 +0,0 @@ -package iolang - -import ( - "testing" -) - -// TestGetSlot tests that GetSlot can find local and ancestor slots, and that no -// object is checked more than once. -func TestGetSlot(t *testing.T) { - vm := TestingVM() - cases := map[string]struct { - o, v, p *Object - slot string - }{ - "Local": {vm.Lobby, vm.Lobby, vm.Lobby, "Lobby"}, - "Ancestor": {vm.Lobby, vm.BaseObject, vm.Core, "Object"}, - "Never": {vm.Lobby, nil, nil, "fail to find"}, - } - // TODO: test case where the lookup chain expands into the general case, - // then returns to the single-proto case - for name, c := range cases { - t.Run(name, func(t *testing.T) { - v, p := vm.GetSlot(c.o, c.slot) - if v != c.v { - t.Errorf("slot %s found wrong object: have %T@%p, want %T@%p", c.slot, v, v, c.v, c.v) - } - if p != c.p { - t.Errorf("slot %s found on wrong proto: have %T@%p, want %T@%p", c.slot, p, p, c.p, c.p) - } - }) - } -} - -// BenchmarkGetSlot benchmarks VM.GetSlot in various depths of search. -func BenchmarkGetSlot(b *testing.B) { - vm := TestingVM() - o := vm.BaseObject.Clone().Clone().Clone().Clone().Clone().Clone().Clone().Clone().Clone().Clone().Clone() - cases := map[string]struct { - o *Object - slot string - }{ - "Local": {vm.Lobby, "Lobby"}, - "Proto": {vm.BaseObject, "Lobby"}, - "Ancestor": {o, "Lobby"}, - "Missing": {vm.Lobby, "Lobby fail to find"}, - } - for name, c := range cases { - b.Run(name, func(b *testing.B) { - for i := 0; i < b.N; i++ { - BenchDummy, _ = vm.GetSlot(c.o, c.slot) - } - }) - } -} - -// TestGetLocalSlot tests that GetLocalSlot can find local but not ancestor -// slots. -func TestGetLocalSlot(t *testing.T) { - vm := TestingVM() - cases := map[string]struct { - o, v *Object - ok bool - slot string - }{ - "Local": {vm.Lobby, vm.Lobby, true, "Lobby"}, - "Ancestor": {vm.Lobby, nil, false, "Object"}, - "Never": {vm.Lobby, nil, false, "fail to find"}, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - v, ok := vm.GetLocalSlot(c.o, c.slot) - if ok != c.ok { - t.Errorf("slot %s has wrong presence: have %v, want %v", c.slot, ok, c.ok) - } - if v != c.v { - t.Errorf("slot %s found wrong object: have %T@%p, want %T@%p", c.slot, v, v, c.v, c.v) - } - }) - } -} - -// TestObjectGoActivate tests that an Object set to be activatable activates its -// activate slot when activated. -func TestObjectGoActivate(t *testing.T) { - vm := TestingVM() - o := vm.NewObject(Slots{}) - vm.SetSlot(vm.Lobby, "TestObjectActivate", o) - cases := map[string]SourceTestCase{ - "InactiveNoActivate": {`getSlot("TestObjectActivate") removeSlot("activate") setIsActivatable(false)`, PassEqual(o)}, - "InactiveActivate": {`getSlot("TestObjectActivate") do(activate := Lobby) setIsActivatable(false)`, PassEqual(o)}, - "ActiveNoActivate": {`getSlot("TestObjectActivate") removeSlot("activate") setIsActivatable(true)`, PassEqual(o)}, - "ActiveActivate": {`getSlot("TestObjectActivate") do(activate := Lobby) setIsActivatable(true)`, PassEqual(vm.Lobby)}, - } - for name, c := range cases { - t.Run(name, c.TestFunc("TestObjectActivate/"+name)) - } - vm.RemoveSlot(vm.Lobby, "TestObjectActivate") -} - -// TestObjectSlots tests that a new VM Object has the slots we expect. -func TestObjectSlots(t *testing.T) { - slots := []string{ - "", - "!=", - "-", - "..", - "<", - "<=", - "==", - ">", - ">=", - "?", - "@", - "@@", - // "actorProcessQueue", - // "actorRun", - "addTrait", - "ancestorWithSlot", - "ancestors", - "and", - "appendProto", - "apropos", - "asBoolean", - "asGoRepr", - "asSimpleString", - "asString", - "asyncSend", - "block", - "break", - "clone", - "cloneWithoutInit", - "compare", - "contextWithSlot", - "continue", - "coroDo", - "coroDoLater", - "coroFor", - "coroWith", - "currentCoro", - "deprecatedWarning", - "do", - "doFile", - "doMessage", - "doRelativeFile", - "doString", - "evalArg", - "evalArgAndReturnNil", - "evalArgAndReturnSelf", - "for", - "foreachSlot", - "futureSend", - "getLocalSlot", - "getSlot", - // "handleActorException", - "hasLocalSlot", - "hasProto", - "hasSlot", - "if", - "ifError", - "ifNil", - "ifNilEval", - "ifNonNil", - "ifNonNilEval", - "in", - "init", - "inlineMethod", - "isActivatable", - "isError", - "isIdenticalTo", - "isKindOf", - "isLaunchScript", - "isNil", - "isTrue", - "justSerialized", - "launchFile", - "lazySlot", - "lexicalDo", - "list", - "loop", - // "memorySize", - "message", - "method", - "newSlot", - "not", - "or", - "pause", - "perform", - "performWithArgList", - "prependProto", - "print", - "println", - "proto", - "protos", - "raiseIfError", - "relativeDoFile", - "removeAllProtos", - "removeAllSlots", - "removeProto", - "removeSlot", - "resend", - "return", - "returnIfError", - "returnIfNonNil", - "serialized", - "serializedSlots", - "serializedSlotsWithNames", - "setIsActivatable", - "setProto", - "setProtos", - "setSlot", - "setSlotWithType", - "shallowCopy", - "slotDescriptionMap", - "slotNames", - "slotSummary", - "slotValues", - "stopStatus", - "super", - "switch", - "thisContext", - "thisLocalContext", - "thisMessage", - "try", - "type", - "uniqueHexId", - "uniqueId", - "updateSlot", - "wait", - "while", - "write", - "writeln", - "yield", - } - CheckSlots(t, TestingVM().BaseObject, slots) -} - -// TestObjectScript tests Object methods by executing Io scripts. -func TestObjectMethods(t *testing.T) { - vm := TestingVM() - list012 := vm.NewList(vm.NewNumber(0), vm.NewNumber(1), vm.NewNumber(2)) - listxyz := vm.NewList(vm.NewString("x"), vm.NewString("y"), vm.NewString("z")) - // If this test runs before TestLobbySlots, any new slots that tests create - // will cause that to fail. To circumvent this, we provide an object to - // carry test values, then remove it once all tests have run. This object - // initially carries default test configuration values. - config := Slots{ - // coroWaitTime is the time in seconds that coros should wait while - // testing methods that spawn new coroutines. The new coroutines may - // take any amount of time to execute, as the VM does not wait for them - // to finish. - "coroWaitTime": vm.NewNumber(0.02), - // obj is a generic object with some slots to simplify some tests. - "obj": vm.NewObject(Slots{"x": vm.NewNumber(1), "y": vm.NewNumber(2), "z": vm.NewNumber(0)}), - // manyProtos is an object that has many protos. - "manyProtos": vm.ObjectWith(nil, []*Object{vm.BaseObject, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil}, nil, nil), - // manyProtosToRemove is an object that has many protos to test that - // removeAllProtos succeeds. - "manyProtosToRemove": vm.ObjectWith(nil, []*Object{vm.BaseObject, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil}, nil, nil), - } - vm.SetSlot(vm.Lobby, "testValues", vm.NewObject(config)) - cases := map[string]map[string]SourceTestCase{ - "evalArg": { - "evalArg": {`evalArg(Lobby)`, PassEqual(vm.Lobby)}, - "continue": {`evalArg(continue; Lobby)`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`evalArg(Exception raise; Lobby)`, PassFailure()}, - }, - "notEqual": { - "0!=1": {`0 !=(1)`, PassIdentical(vm.True)}, - "0!=0": {`0 !=(0)`, PassIdentical(vm.False)}, - "1!=0": {`1 !=(0)`, PassIdentical(vm.True)}, - "incomparable": {`Lobby !=(Core)`, PassIdentical(vm.True)}, - "identical": {`Lobby !=(Lobby)`, PassIdentical(vm.False)}, - "continue": {`Lobby !=(continue); Lobby`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`Lobby !=(Exception raise); Lobby`, PassFailure()}, - }, - "minus": { - "-1": {`-(1)`, PassEqual(vm.NewNumber(-1))}, - "-seq": {`-("abc")`, PassFailure()}, - }, - "dotdot": { - "1..2": {`1 ..(2)`, PassEqual(vm.NewString("12"))}, - }, - "less": { - "0<1": {`0 <(1)`, PassIdentical(vm.True)}, - "0<0": {`0 <(0)`, PassIdentical(vm.False)}, - "1<0": {`1 <(0)`, PassIdentical(vm.False)}, - "incomparable": {`Lobby <(Core)`, PassSuccess()}, - "identical": {`Lobby <(Lobby)`, PassIdentical(vm.False)}, - "continue": {`0 <(continue); Lobby`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`0 <(Exception raise); Lobby`, PassFailure()}, - }, - "lessEqual": { - "0<=1": {`0 <=(1)`, PassIdentical(vm.True)}, - "0<=0": {`0 <=(0)`, PassIdentical(vm.True)}, - "1<=0": {`1 <=(0)`, PassIdentical(vm.False)}, - "incomparable": {`Lobby <=(Core)`, PassSuccess()}, - "identical": {`Lobby <=(Lobby)`, PassIdentical(vm.True)}, - "continue": {`0 <=(continue); Lobby`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`0 <=(Exception raise); Lobby`, PassFailure()}, - }, - "equal": { - "0==1": {`0 ==(1)`, PassIdentical(vm.False)}, - "0==0": {`0 ==(0)`, PassIdentical(vm.True)}, - "1==0": {`1 ==(0)`, PassIdentical(vm.False)}, - "incomparable": {`Lobby ==(Core)`, PassIdentical(vm.False)}, - "identical": {`Lobby ==(Lobby)`, PassIdentical(vm.True)}, - "continue": {`Lobby ==(continue); Lobby`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`Lobby ==(Exception raise); Lobby`, PassFailure()}, - }, - "greater": { - "0>1": {`0 >(1)`, PassIdentical(vm.False)}, - "0>0": {`0 >(0)`, PassIdentical(vm.False)}, - "1>0": {`1 >(0)`, PassIdentical(vm.True)}, - "incomparable": {`Lobby >(Core)`, PassSuccess()}, - "identical": {`Lobby >(Lobby)`, PassIdentical(vm.False)}, - "continue": {`0 >(continue); Lobby`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`0 >(Exception raise); Lobby`, PassFailure()}, - }, - "greaterEqual": { - "0>=1": {`0 >=(1)`, PassIdentical(vm.False)}, - "0>=0": {`0 >=(0)`, PassIdentical(vm.True)}, - "1>=0": {`1 >=(0)`, PassIdentical(vm.True)}, - "incomparable": {`Lobby >=(Core)`, PassSuccess()}, - "identical": {`Lobby >=(Lobby)`, PassIdentical(vm.True)}, - "continue": {`0 >=(continue); Lobby`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`0 >=(Exception raise); Lobby`, PassFailure()}, - }, - "question": { - "have": {`?Lobby`, PassIdentical(vm.Lobby)}, - "not": {`?nothing`, PassIdentical(vm.Nil)}, - "effect": {`testValues questionEffect := 0; ?testValues questionEffect := 1; testValues questionEffect`, PassEqual(vm.NewNumber(1))}, - "continue": {`?continue`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`?Exception raise`, PassFailure()}, - "parens": {`testValues questionEffect := 0; ?(testValues questionEffect := 1) ?(nothing); testValues questionEffect`, PassEqual(vm.NewNumber(1))}, - }, - "addTrait": { - "all": {`Object clone addTrait(testValues obj)`, PassEqualSlots(Slots{"x": vm.NewNumber(1), "y": vm.NewNumber(2), "z": vm.NewNumber(0)})}, - "res": {`Object clone do(x := 4) addTrait(testValues obj, Map clone do(atPut("x", "w")))`, PassEqualSlots(Slots{"w": vm.NewNumber(1), "x": vm.NewNumber(4), "y": vm.NewNumber(2), "z": vm.NewNumber(0)})}, - "unres": {`Object clone do(x := 4) addTrait(testValues obj, Map clone do(atPut("w", "x")))`, PassFailure()}, - "badres": {`Object clone do(x := 4) addTrait(testValues obj, Map clone do(atPut("x", 1)))`, PassFailure()}, - "short": {`Object clone addTrait`, PassFailure()}, - }, - "ancestorWithSlot": { - "local": {`Number ancestorWithSlot("abs")`, PassIdentical(vm.Nil)}, - "proto": {`Lobby clone ancestorWithSlot("Lobby")`, PassIdentical(vm.Lobby)}, - "localInProtos": {`Lobby ancestorWithSlot("Lobby")`, PassIdentical(vm.Lobby)}, - "nowhere": {`Lobby ancestorWithSlot("this slot doesn't exist")`, PassIdentical(vm.Nil)}, - "bad": {`Lobby ancestorWithSlot(0)`, PassFailure()}, - "continue": {`Lobby ancestorWithSlot(continue); Lobby`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`Lobby ancestorWithSlot(Exception raise); Lobby`, PassFailure()}, - }, - "ancestors": { - "ancestors": {`testValues anc := Object clone ancestors; testValues anc containsIdenticalTo(Object) and testValues anc containsIdenticalTo(Core)`, PassIdentical(vm.True)}, - }, - "and": { - "true": {`and(Object)`, PassIdentical(testVM.True)}, - "false": {`and(Object clone do(isTrue := false))`, PassIdentical(testVM.False)}, - }, - // TODO: apropos needs special tests, since it prints - "appendProto": { - "appendProto": {`Object clone do(appendProto(Lobby)) protos containsIdenticalTo(Lobby)`, PassIdentical(vm.True)}, - "continue": {`Object clone appendProto(continue); Lobby`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`Object clone appendProto(Exception raise); Lobby`, PassFailure()}, - }, - "asBoolean": { - "asBoolean": {`Object asBoolean`, PassIdentical(vm.True)}, - }, - // asGoRepr gets no tests // - "asSimpleString": { - "isSequence": {`Object asSimpleString`, PassTag(SequenceTag)}, - }, - "asString": { - "isSequence": {`Object asString`, PassTag(SequenceTag)}, - }, - "asyncSend": { - "spawns": {`while(Scheduler coroCount > 0, yield); testValues asyncSendSync := true; asyncSend(while(testValues asyncSendSync, yield)); wait(testValues coroWaitTime); testValues asyncSendCoros := Scheduler coroCount; testValues asyncSendSync = false; testValues asyncSendCoros`, PassEqual(vm.NewNumber(1))}, - "sideEffects": {`testValues asyncSendSideEffect := 0; asyncSend(Lobby testValues asyncSendSideEffect = 1); wait(testValues coroWaitTime); testValues asyncSendSideEffect`, PassEqual(vm.NewNumber(1))}, - "empty": {`asyncSend`, PassFailure()}, - }, - "block": { - "noMessage": {`block`, PassTag(BlockTag)}, - "exception": {`block(Exception raise)`, PassSuccess()}, - }, - "break": { - "break": {`break`, PassControl(vm.Nil, BreakStop)}, - "value": {`break(Lobby)`, PassControl(vm.Lobby, BreakStop)}, - "continue": {`break(continue)`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`break(Exception raise)`, PassFailure()}, - }, - "clone": { - "new": {`Object clone != Object`, PassIdentical(vm.True)}, - "proto": {`Object clone protos containsIdenticalTo(Object)`, PassIdentical(vm.True)}, - "init": {`testValues initValue := 0; Object clone do(init := method(Lobby testValues initValue = 1)) clone; testValues initValue`, PassEqual(vm.NewNumber(1))}, - }, - "cloneWithoutInit": { - "new": {`Object cloneWithoutInit != Object`, PassIdentical(vm.True)}, - "proto": {`Object cloneWithoutInit protos containsIdenticalTo(Object)`, PassIdentical(vm.True)}, - "init": {`testValues noInitValue := 0; Object clone do(init := method(Lobby testValues noInitValue = 1)) cloneWithoutInit; testValues noInitValue`, PassEqual(vm.NewNumber(0))}, - }, - "compare": { - "incomparable": {`Object compare("string")`, PassTag(NumberTag)}, - "continue": {`Object compare(continue)`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`Object compare(Exception raise)`, PassFailure()}, - }, - "contextWithSlot": { - "local": {`Number contextWithSlot("abs")`, PassEqual(vm.NewNumber(0))}, - "proto": {`Lobby clone contextWithSlot("Lobby")`, PassIdentical(vm.Lobby)}, - "localInProtos": {`Lobby contextWithSlot("Lobby")`, PassIdentical(vm.Lobby)}, - "nowhere": {`Lobby contextWithSlot("this slot doesn't exist")`, PassIdentical(vm.Nil)}, - "bad": {`Lobby contextWithSlot(0)`, PassFailure()}, - "continue": {`Lobby contextWithSlot(continue); Lobby`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`Lobby contextWithSlot(Exception raise); Lobby`, PassFailure()}, - }, - "continue": { - "continue": {`continue`, PassControl(vm.Nil, ContinueStop)}, - "value": {`continue(Lobby)`, PassControl(vm.Lobby, ContinueStop)}, - "exception": {`continue(Exception raise)`, PassFailure()}, - }, - "coroDo": { - "spawns": {`while(Scheduler coroCount > 0, yield); testValues coroDoSync := true; coroDo(while(testValues coroDoSync, yield)); wait(testValues coroWaitTime); testValues coroDoCoros := Scheduler coroCount; testValues coroDoSync = false; testValues coroDoCoros`, PassEqual(vm.NewNumber(1))}, - "sideEffects": {`testValues coroDoSideEffect := 0; coroDo(testValues coroDoSideEffect := 1); wait(testValues coroWaitTime); testValues coroDoSideEffect`, PassEqual(vm.NewNumber(1))}, - // TODO: test that this has the right target - }, - "coroDoLater": { - "spawns": {`while(Scheduler coroCount > 0, yield); testValues coroDoLaterSync := true; coroDoLater(while(testValues coroDoLaterSync, yield)); wait(testValues coroWaitTime); testValues coroDoLaterCoros := Scheduler coroCount; testValues coroDoLaterSync = false; testValues coroDoLaterCoros`, PassEqual(vm.NewNumber(1))}, - "sideEffects": {`testValues coroDoLaterSideEffect := 0; coroDoLater(testValues coroDoLaterSideEffect := 1); wait(testValues coroWaitTime); testValues coroDoLaterSideEffect`, PassEqual(vm.NewNumber(1))}, - // TODO: test that this has the right target - }, - "coroFor": { - "noSpawns": {`while(Scheduler coroCount > 0, yield); testValues coroForSync := true; coroFor(while(testValues coroForSync, yield)); wait(testValues coroWaitTime); testValues coroForCoros := Scheduler coroCount; testValues coroForSync = false; testValues coroForCoros`, PassEqual(vm.NewNumber(0))}, - "noSideEffects": {`testValues coroForSideEffect := 0; coroFor(testValues coroForSideEffect := 1); wait(testValues coroWaitTime); testValues coroForSideEffect`, PassEqual(vm.NewNumber(0))}, - "type": {`coroFor(nil)`, PassTag(CoroutineTag)}, - "message": {`coroFor(nil) runMessage name`, PassEqual(vm.NewString("nil"))}, - "target": {`0 coroFor(nil) runTarget`, PassIdentical(vm.Lobby)}, - "locals": {`0 coroFor(nil) runLocals`, PassIdentical(vm.Lobby)}, - }, - "coroWith": { - "noSpawns": {`while(Scheduler coroCount > 0, yield); testValues coroWithSync := true; coroWith(while(testValues coroWithSync, yield)); wait(testValues coroWaitTime); testValues coroWithCoros := Scheduler coroCount; testValues coroWithSync = false; testValues coroWithCoros`, PassEqual(vm.NewNumber(0))}, - "noSideEffects": {`testValues coroWithSideEffect := 0; coroWith(testValues coroWithSideEffect := 1); wait(testValues coroWaitTime); testValues coroWithSideEffect`, PassEqual(vm.NewNumber(0))}, - "type": {`coroWith(nil)`, PassTag(CoroutineTag)}, - "message": {`coroWith(nil) runMessage name`, PassEqual(vm.NewString("nil"))}, - "target": {`0 coroWith(nil) runTarget`, PassEqual(vm.NewNumber(0))}, - "locals": {`0 coroWith(nil) runLocals`, PassIdentical(vm.Lobby)}, - }, - "currentCoro": { - "isCurrent": {`currentCoro`, PassIdentical(vm.Coro)}, - }, - "deprecatedWarning": { - "context": {`deprecatedWarning`, PassFailure()}, - // TODO: deprecatedWarning needs special tests, since it prints - }, - "do": { - "result": {`Object do(Lobby)`, PassIdentical(vm.BaseObject)}, - "context": {`testValues doValue := 0; testValues do(doValue := 1); testValues doValue`, PassEqual(vm.NewNumber(1))}, - "continue": {`do(continue)`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`do(Exception raise)`, PassFailure()}, - }, - // TODO: doFile needs special testing - "doMessage": { - "doMessage": {`testValues doMessageValue := 0; testValues doMessage(message(doMessageValue = 1)); testValues doMessageValue`, PassEqual(vm.NewNumber(1))}, - "context": {`testValues doMessageValue := 2; doMessage(message(testValues doMessageValue = doMessageValue + 1), testValues); testValues doMessageValue`, PassEqual(vm.NewNumber(3))}, - "bad": {`testValues doMessage("doMessageValue := 4")`, PassFailure()}, - }, - // TODO: doRelativeFile needs special testing - "doString": { - "doString": {`testValues doStringValue := 0; testValues doString("doStringValue = 1"); testValues doStringValue`, PassEqual(vm.NewNumber(1))}, - "label": {`testValues doStringLabel := "foo"; testValues doString("doStringLabel = thisMessage label", "bar"); testValues doStringLabel`, PassEqual(vm.NewString("bar"))}, - "bad": {`testValues doString(message(doStringValue := 4))`, PassFailure()}, - }, - "evalArgAndReturnNil": { - "result": {`evalArgAndReturnNil(Lobby)`, PassIdentical(vm.Nil)}, - "eval": {`testValues evalNil := 0; evalArgAndReturnNil(testValues evalNil := 1); testValues evalNil`, PassEqual(vm.NewNumber(1))}, - "continue": {`evalArgAndReturnNil(continue)`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`evalArgAndReturnNil(Exception raise)`, PassFailure()}, - }, - "evalArgAndReturnSelf": { - "result": {`evalArgAndReturnSelf(nil)`, PassIdentical(vm.Lobby)}, - "eval": {`testValues evalSelf := 0; evalArgAndReturnSelf(testValues evalSelf := 1); testValues evalSelf`, PassEqual(vm.NewNumber(1))}, - "continue": {`evalArgAndReturnSelf(continue)`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`evalArgAndReturnSelf(Exception raise)`, PassFailure()}, - }, - "for": { - "result": {`testValues do(forResult := for(forCtr, 0, 2, forCtr * 2)) forResult`, PassEqual(vm.NewNumber(4))}, - "nothing": {`testValues do(forResult := for(forCtr, 2, 0, Exception raise)) forResult`, PassIdentical(vm.Nil)}, - "order": {`testValues do(forList := list; for(forCtr, 0, 2, forList append(forCtr))) forList`, PassEqual(list012)}, - "step": {`testValues do(forList := list; for(forCtr, 2, 0, -1, forList append(forCtr))) forList reverse`, PassEqual(list012)}, - "continue": {`testValues do(forList := list; for(forCtr, 0, 2, forList append(forCtr); continue; forList append(forCtr))) forList`, PassEqual(list012)}, - "continueResult": {`testValues do(forResult := for(forCtr, 0, 2, continue(forCtr * 2))) forResult`, PassEqual(vm.NewNumber(4))}, - "break": {`testValues do(for(forCtr, 0, 2, break)) forCtr`, PassEqual(vm.NewNumber(0))}, - "breakResult": {`testValues do(forResult := for(forCtr, 0, 2, break(4))) forResult`, PassEqual(vm.NewNumber(4))}, - "return": {`testValues do(for(forCtr, 0, 2, return))`, PassControl(vm.Nil, ReturnStop)}, - "exception": {`testValues do(for(forCtr, 0, 2, Exception raise))`, PassFailure()}, - "name": {`testValues do(for(forCtrNew no responderino, 0, 2, nil))`, PassLocalSlots([]string{"forCtrNew"}, []string{"no", "responderino"})}, - "short": {`testValues do(for(forCtr))`, PassFailure()}, - "long": {`testValues do(for(forCtr, 0, 1, 2, 3, 4, 5))`, PassFailure()}, - }, - "foreachSlot": { - "result": {`testValues do(foreachSlotResult := obj foreachSlot(value, 4)) foreachSlotResult`, PassEqual(vm.NewNumber(4))}, - "nothing": {`testValues do(foreachSlotResult := Object clone foreachSlot(value, Exception raise)) foreachSlotResult`, PassIdentical(vm.Nil)}, - "key": {`testValues do(forList := list; obj foreachSlot(slot, value, forList append(slot))) forList sort`, PassEqual(listxyz)}, - "value": {`testValues do(forList := list; obj foreachSlot(slot, value, forList append(value))) forList sort`, PassEqual(list012)}, - "continue": {`testValues do(forList := list; obj foreachSlot(slot, value, forList append(slot); continue; forList append(slot))) forList sort`, PassEqual(listxyz)}, - "continueResult": {`testValues do(foreachSlotResult := obj foreachSlot(slot, value, continue(Lobby))) foreachSlotResult`, PassIdentical(vm.Lobby)}, - "break": {`testValues do(foreachSlotIters := 0; obj foreachSlot(slot, value, foreachSlotIters = foreachSlotIters + 1; break)) foreachSlotIters`, PassEqual(vm.NewNumber(1))}, - "breakResult": {`testValues do(foreachSlotResult := obj foreachSlot(slot, value, break(Lobby))) foreachSlotResult`, PassIdentical(vm.Lobby)}, - "return": {`testValues do(obj foreachSlot(slot, value, return))`, PassControl(vm.Nil, ReturnStop)}, - "exception": {`testValues do(obj foreachSlot(slot, value, Exception raise))`, PassFailure()}, - "name": {`testValues do(obj foreachSlot(slotNew no responderino, valueNew no responderino, nil))`, PassLocalSlots([]string{"slotNew", "valueNew"}, []string{"no", "responderino"})}, - "short": {`testValues do(obj foreachSlot(nil))`, PassFailure()}, - "long": {`testValues do(obj foreachSlot(slot, value, 1, 2, 3))`, PassFailure()}, - }, - "futureSend": { - "result": {`futureSend(1) isNil not`, PassIdentical(vm.True)}, - "evaluates": {`futureSend(1) + 1`, PassEqual(vm.NewNumber(2))}, - "spawns": {`while(Scheduler coroCount > 0, yield); testValues futureSendSync := true; futureSend(while(testValues futureSendSync, yield)); wait(testValues coroWaitTime); testValues futureSendCoros := Scheduler coroCount; testValues futureSendSync = false; testValues futureSendCoros`, PassEqual(vm.NewNumber(1))}, - "sideEffects": {`testValues futureSendSideEffect := 0; futureSend(Lobby testValues futureSendSideEffect = 1); wait(testValues coroWaitTime); testValues futureSendSideEffect`, PassEqual(vm.NewNumber(1))}, - "empty": {`futureSend`, PassFailure()}, - }, - "getLocalSlot": { - "local": {`getLocalSlot("Lobby")`, PassIdentical(vm.Lobby)}, - "ancestor": {`Lobby clone getLocalSlot("Lobby")`, PassIdentical(vm.Nil)}, - "never": {`getLocalSlot("this slot does not exist")`, PassIdentical(vm.Nil)}, - "bad": {`getLocalSlot(Lobby)`, PassFailure()}, - }, - "getSlot": { - "local": {`getSlot("Lobby")`, PassIdentical(vm.Lobby)}, - "ancestor": {`Lobby clone getSlot("Lobby")`, PassIdentical(vm.Lobby)}, - "never": {`getSlot("this slot does not exist")`, PassIdentical(vm.Nil)}, - "bad": {`getSlot(Lobby)`, PassFailure()}, - }, - "hasLocalSlot": { - "local": {`hasLocalSlot("Lobby")`, PassIdentical(vm.True)}, - "ancestor": {`Lobby clone hasLocalSlot("Lobby")`, PassIdentical(vm.False)}, - "never": {`hasLocalSlot("this slot does not exist")`, PassIdentical(vm.False)}, - "bad": {`hasLocalSlot(Lobby)`, PassFailure()}, - }, - "hasSlot": { - "local": {`hasSlot("Lobby")`, PassIdentical(vm.True)}, - "ancestor": {`Lobby clone hasSlot("Lobby")`, PassIdentical(vm.True)}, - "never": {`hasSlot("this slot does not exist")`, PassIdentical(vm.False)}, - "bad": {`hasSlot(Lobby)`, PassFailure()}, - }, - "if": { - "evalTrue": {`testValues ifResult := nil; if(true, testValues ifResult := true, testValues ifResult := false); testValues ifResult`, PassIdentical(vm.True)}, - "evalFalse": {`testValues ifResult := nil; if(false, testValues ifResult := true, testValues ifResult := false); testValues ifResult`, PassIdentical(vm.False)}, - "resultTrue": {`if(true, 1, 0)`, PassEqual(vm.NewNumber(1))}, - "resultFalse": {`if(false, 1, 0)`, PassEqual(vm.NewNumber(0))}, - "resultTrueDiadic": {`if(true, 1)`, PassEqual(vm.NewNumber(1))}, - "resultFalseDiadic": {`if(false, 1)`, PassIdentical(vm.False)}, - "resultTrueMonadic": {`if(true)`, PassIdentical(vm.True)}, - "resultFalseMonadic": {`if(false)`, PassIdentical(vm.False)}, - "continue1": {`if(continue, nil, nil)`, PassControl(vm.Nil, ContinueStop)}, - "continue2": {`if(true, continue, nil)`, PassControl(vm.Nil, ContinueStop)}, - "continue3": {`if(false, nil, continue)`, PassControl(vm.Nil, ContinueStop)}, - "exception1": {`if(Exception raise, nil, nil)`, PassFailure()}, - "exception2": {`if(true, Exception raise, nil)`, PassFailure()}, - "exception3": {`if(false, nil, Exception raise)`, PassFailure()}, - }, - "ifError": { - "noEval": {`testValues ifErrorResult := nil; ifError(testValues ifErrorResult := true); testValues ifErrorResult`, PassIdentical(vm.Nil)}, - "self": {`ifError(nil)`, PassIdentical(vm.Lobby)}, - }, - "ifNil": { - "noEval": {`testValues ifNilResult := false; ifNil(testValues ifNilResult := true); testValues ifNilResult`, PassIdentical(vm.False)}, - "self": {`ifNil(nil)`, PassIdentical(vm.Lobby)}, - }, - "ifNilEval": { - "noEval": {`testValues ifNilEvalResult := false; ifNilEval(testValues ifNilEvalResult := true); testValues ifNilEvalResult`, PassIdentical(vm.False)}, - "self": {`ifNilEval(nil)`, PassIdentical(vm.Lobby)}, - }, - "ifNonNil": { - "result": {`ifNonNil(nil)`, PassIdentical(vm.Lobby)}, - "eval": {`testValues ifNonNilResult := 0; ifNonNil(testValues ifNonNilResult := 1); testValues ifNonNilResult`, PassEqual(vm.NewNumber(1))}, - "continue": {`ifNonNil(continue)`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`ifNonNil(Exception raise)`, PassFailure()}, - }, - "ifNonNilEval": { - "evalArg": {`ifNonNilEval(Lobby)`, PassEqual(vm.Lobby)}, - "continue": {`ifNonNilEval(continue; Lobby)`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`ifNonNilEval(Exception raise; Lobby)`, PassFailure()}, - }, - "in": { - "contains": {`testValues contains := method(self inResult := true); Object in(testValues); testValues inResult`, PassIdentical(vm.True)}, - }, - "init": { - "self": {`init`, PassIdentical(vm.Lobby)}, - }, - "inlineMethod": { - "type": {`inlineMethod(nil)`, PassTag(MessageTag)}, - "text": {`inlineMethod(nil) name`, PassEqual(vm.NewString("nil"))}, - "next": {`inlineMethod(true nil) next name`, PassEqual(vm.NewString("nil"))}, - "prev": {`inlineMethod(true) previous`, PassIdentical(vm.Nil)}, - }, - "isActivatable": { - "false": {`Object isActivatable`, PassIdentical(vm.False)}, - }, - "isError": { - "false": {`Object isError`, PassIdentical(vm.False)}, - }, - "isIdenticalTo": { - "0===0": {`123456789 isIdenticalTo(123456789)`, PassIdentical(vm.False)}, - "unidentical": {`Lobby isIdenticalTo(Core)`, PassIdentical(vm.False)}, - "identical": {`Lobby isIdenticalTo(Lobby)`, PassIdentical(vm.True)}, - "continue": {`Lobby isIdenticalTo(continue); Lobby`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`Lobby isIdenticalTo(Exception raise); Lobby`, PassFailure()}, - }, - "isKindOf": { - "self": {`Object isKindOf(Object)`, PassIdentical(vm.True)}, - "proto": {`Object clone isKindOf(Object)`, PassIdentical(vm.True)}, - "ancestor": {`0 isKindOf(Lobby)`, PassIdentical(vm.True)}, - "not": {`Object isKindOf(Exception)`, PassIdentical(vm.False)}, - "continue": {`isKindOf(continue; Lobby)`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`isKindOf(Exception raise; Lobby)`, PassFailure()}, - }, - // isLaunchScript needs special testing - "isNil": { - "false": {`Object isNil`, PassIdentical(vm.False)}, - }, - "isTrue": { - "true": {`Object isTrue`, PassIdentical(vm.True)}, - }, - "justSerialized": { - "same": {`testValues justSerializedStream := SerializationStream clone; testValues obj justSerialized(testValues justSerializedStream); doString(testValues justSerializedStream output)`, PassEqualSlots(vm.GetAllSlots(config["obj"]))}, - }, - // launchFile needs special testing - "lazySlot": { - "initial1": {`lazySlot(1)`, PassUnequal(vm.NewNumber(1))}, - "initial2": {`testValues lazySlot("lazySlotValue", 1); testValues getSlot("lazySlotValue")`, PassUnequal(vm.NewNumber(1))}, - "eval1": {`testValues lazySlotValue := lazySlot(1); testValues lazySlotValue`, PassEqual(vm.NewNumber(1))}, - "eval2": {`testValues lazySlot("lazySlotValue", 1); testValues lazySlotValue`, PassEqual(vm.NewNumber(1))}, - "replace1": {`testValues lazySlotValue := lazySlot(1); testValues lazySlotValue; testValues getSlot("lazySlotValue")`, PassEqual(vm.NewNumber(1))}, - "replace2": {`testValues lazySlot("lazySlotValue", 1); testValues lazySlotValue; testValues getSlot("lazySlotValue")`, PassEqual(vm.NewNumber(1))}, - "once1": {`testValues lazySlotCount := 0; testValues lazySlotValue := lazySlot(testValues lazySlotCount = testValues lazySlotCount + 1; 1); testValues lazySlotValue; testValues lazySlotValue; testValues lazySlotValue; testValues lazySlotCount`, PassEqual(vm.NewNumber(1))}, - "once2": {`testValues lazySlotCount := 0; testValues lazySlot("lazySlotValue", testValues lazySlotCount = testValues lazySlotCount + 1; 1); testValues lazySlotValue; testValues lazySlotValue; testValues lazySlotValue; testValues lazySlotCount`, PassEqual(vm.NewNumber(1))}, - }, - "lexicalDo": { - // These tests have to be careful not to call lexicalDo on an - // object that already has the lexical context as a proto. - "result": {`Lobby lexicalDo(Object)`, PassIdentical(vm.Lobby)}, - "context": {`testValues lexicalDoValue := 0; testValues lexicalDo(lexicalDoValue := 1); testValues lexicalDoValue`, PassEqual(vm.NewNumber(1))}, - "lexical": {`testValues lexicalDo(lexicalDoHasProto := thisContext protos containsIdenticalTo(Lobby)); testValues lexicalDoHasProto`, PassIdentical(vm.True)}, - "continue": {`Object clone lexicalDo(continue)`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`Object clone lexicalDo(Exception raise)`, PassFailure()}, - }, - "list": { - // Object list is List with, but still test it in both places. - "zero": {`Object list`, PassEqual(vm.NewList())}, - "one": {`Object list(nil)`, PassEqual(vm.NewList(vm.Nil))}, - "five": {`Object list(nil, nil, nil, nil, nil)`, PassEqual(vm.NewList(vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil))}, - "continue": {`Object list(nil, nil, nil, nil, continue)`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`Object list(nil, nil, nil, nil, Exception raise)`, PassFailure()}, - }, - "loop": { - "loop": {`testValues loopCount := 0; loop(testValues loopCount = testValues loopCount + 1; if(testValues loopCount >= 5, break)); testValues loopCount`, PassEqual(vm.NewNumber(5))}, - "continue": {`testValues loopCount := 0; loop(testValues loopCount = testValues loopCount + 1; if(testValues loopCount < 5, continue); break); testValues loopCount`, PassEqual(vm.NewNumber(5))}, - "break": {`testValues loopCount := 0; loop(break; testValues loopCount = 1); testValues loopCount`, PassEqual(vm.NewNumber(0))}, - "return": {`testValues loopCount := 0; loop(return nil; testValues loopCount = 1); testValues loopCount`, PassControl(vm.Nil, ReturnStop)}, - "exception": {`testValues loopCount := 0; loop(Exception raise; testValues loopCount = 1); testValues loopCount`, PassFailure()}, - }, - "message": { - "nothing": {`message`, PassIdentical(vm.Nil)}, - "message": {`message(message)`, PassTag(MessageTag)}, - "continue": {`message(continue)`, PassTag(MessageTag)}, - "exception": {`message(Exception raise)`, PassTag(MessageTag)}, - }, - "method": { - "noMessage": {`method`, PassTag(BlockTag)}, - "exception": {`method(Exception raise)`, PassSuccess()}, - }, - "newSlot": { - "makes": {`testValues newSlot("newSlotValue"); testValues`, PassLocalSlots([]string{"newSlotValue", "setNewSlotValue"}, nil)}, - "value": {`testValues newSlot("newSlotValue", 1); testValues newSlotValue`, PassEqual(vm.NewNumber(1))}, - "setter": {`testValues newSlot("newSlotValue", 1); testValues setNewSlotValue(2); testValues newSlotValue`, PassEqual(vm.NewNumber(2))}, - "result": {`testValues newSlot("newSlotValue", 1)`, PassEqual(vm.NewNumber(1))}, - }, - "not": { - "nil": {`Object not`, PassIdentical(vm.Nil)}, - }, - "or": { - // It might be nice to change or to be a coalescing operator like - // Python's or, by changing it to thisContext. - "true": {`Object or`, PassIdentical(vm.True)}, - }, - "pause": { - "pause": {`testValues pauseValue := 0; testValues pauseCoro := coroDo(testValues pauseValue = 1; Object pause; testValues pauseValue = 2); while(testValues pauseValue == 0, yield); while(Scheduler coroCount > 0, yield); testValues pauseObs := testValues pauseValue; testValues pauseCoro resume; while(testValues pauseValue < 2, yield); testValues pauseObs`, PassEqual(vm.NewNumber(1))}, - }, - "perform": { - "string": {`testValues performValue := 0; testValues perform("setSlot", "performValue", 1); testValues performValue`, PassEqual(vm.NewNumber(1))}, - "message": {`testValues performValue := 0; testValues perform(message(setSlot("performValue", 1))); testValues performValue`, PassEqual(vm.NewNumber(1))}, - "single": {`testValues performValue := 0; testValues perform(message(nil; setSlot("performValue", 1))); testValues performValue`, PassEqual(vm.NewNumber(0))}, - "several": {`testValues perform(message(nil), message(nil))`, PassFailure()}, - "wrong": {`testValues perform(nil)`, PassFailure()}, - "continue": {`testValues perform(continue)`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`testValues perform(Exception raise)`, PassFailure()}, - }, - "performWithArgList": { - "perform": {`testValues performWithValue := 0; testValues performWithArgList("setSlot", list("performWithValue", 1)); testValues performWithValue`, PassEqual(vm.NewNumber(1))}, - "wrong": {`testValues performWithArgList("nil", "nil")`, PassFailure()}, - "continue": {`testValues performWithArgList(continue, list)`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`testValues performWithArgList(Exception raise, list)`, PassFailure()}, - }, - "prependProto": { - "none": {`testValues prependProtoObj := Object clone removeAllProtos; Object getSlot("prependProto") performOn(testValues prependProtoObj, thisLocalContext, message(prependProto(Lobby))); Object getSlot("protos") performOn(testValues prependProtoObj)`, PassEqual(vm.NewList(vm.Lobby))}, - "one": {`testValues prependProtoObj := Object clone; testValues prependProtoObj prependProto(Lobby); testValues prependProtoObj protos`, PassEqual(vm.NewList(vm.Lobby, vm.BaseObject))}, - "ten": {`testValues prependProtoObj := Object clone; testValues prependProtoObj prependProto(Lobby) prependProto(Lobby) prependProto(Lobby) prependProto(Lobby) prependProto(Lobby) prependProto(Lobby) prependProto(Lobby) prependProto(Lobby) prependProto(Lobby) prependProto(Lobby); testValues prependProtoObj protos`, PassEqual(vm.NewList(vm.Lobby, vm.Lobby, vm.Lobby, vm.Lobby, vm.Lobby, vm.Lobby, vm.Lobby, vm.Lobby, vm.Lobby, vm.Lobby, vm.BaseObject))}, - "continue": {`Object clone prependProto(continue)`, PassControl(vm.Nil, ContinueStop)}, - "exception": {`Object clone prependProto(Exception raise)`, PassFailure()}, - }, - // print and println need special tests - "proto": { - "proto": {`testValues proto`, PassIdentical(vm.BaseObject)}, - }, - "protos": { - "none": {`Object getSlot("protos") performOn(Object clone removeAllProtos)`, PassEqual(vm.NewList())}, - "one": {`Object clone protos`, PassEqual(vm.NewList(vm.BaseObject))}, - "ten": {`testValues manyProtos protos`, PassEqual(vm.NewList(vm.BaseObject, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil, vm.Nil))}, - }, - "raiseIfError": { - "nothing": {`Object raiseIfError`, PassSuccess()}, - }, - // relativeDoFile needs special tests - "removeAllProtos": { - // There is no way to test from an Io script that both protos and - // removeAllProtos work. We only test that removeAllProtos does not - // completely fail here and save tests that it actually works for - // another test function. - "none": {`Object getSlot("removeAllProtos") performOn(Object clone removeAllProtos)`, PassSuccess()}, - "one": {`Object clone removeAllProtos`, PassSuccess()}, - "ten": {`testValues manyProtosToRemove removeAllProtos`, PassSuccess()}, - }, - "removeAllSlots": { - "none": {`Object clone removeAllSlots`, PassSuccess()}, - "one": {`testValues slotsObj := Object clone do(x := 0); testValues slotsObj clone do(x := 1) removeAllSlots x`, PassEqual(vm.NewNumber(0))}, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - for name, s := range c { - t.Run(name, s.TestFunc("TestObjectMethods")) - } - }) - } - vm.RemoveSlot(vm.Lobby, "testValues") -} diff --git a/path.go b/path.go deleted file mode 100644 index 865469e..0000000 --- a/path.go +++ /dev/null @@ -1,44 +0,0 @@ -package iolang - -import ( - "path/filepath" - "runtime" -) - -func (vm *VM) initPath() { - slots := Slots{ - "absolute": vm.NewCFunction(PathAbsolute, nil), - "hasDriveLetters": vm.IoBool(runtime.GOOS == "windows"), - "isPathAbsolute": vm.NewCFunction(PathIsPathAbsolute, nil), - "listSeparator": vm.NewString(string(filepath.ListSeparator)), - "separator": vm.NewString(string(filepath.Separator)), - } - vm.coreInstall("Path", slots, nil, nil) -} - -// PathAbsolute is a Path method. -// -// absolute returns an absolute version of the argument path. -func PathAbsolute(vm *VM, target, locals *Object, msg *Message) *Object { - s, exc, stop := msg.StringArgAt(vm, locals, 0) - if stop != NoStop { - return vm.Stop(exc, stop) - } - abs, err := filepath.Abs(filepath.FromSlash(s)) - if err != nil { - return vm.IoError(err) - } - return vm.NewString(filepath.ToSlash(abs)) -} - -// PathIsPathAbsolute is a Path method. -// -// isPathAbsolute returns whether the argument is an absolute path. The path -// may be operating system- or Io-style. -func PathIsPathAbsolute(vm *VM, target, locals *Object, msg *Message) *Object { - s, exc, stop := msg.StringArgAt(vm, locals, 0) - if stop != NoStop { - return vm.Stop(exc, stop) - } - return vm.IoBool(filepath.IsAbs(filepath.FromSlash(s))) -} diff --git a/testutils/testutils.go b/testutils/testutils.go index a1d95ae..55368f9 100644 --- a/testutils/testutils.go +++ b/testutils/testutils.go @@ -15,16 +15,16 @@ var testVM *iolang.VM var testVMInit sync.Once -// TestingVM returns a VM for testing Io. The VM is shared by all tests that +// VM returns a VM for testing Io. The VM is shared by all tests that // use this package. -func TestingVM() *iolang.VM { - testVMInit.Do(ResetTestingVM) +func VM() *iolang.VM { + testVMInit.Do(ResetVM) return testVM } -// ResetTestingVM reinitializes the VM returned by TestVM. It is not safe to +// ResetVM reinitializes the VM returned by TestVM. It is not safe to // call this in parallel tests. -func ResetTestingVM() { +func ResetVM() { testVM = iolang.NewVM() } @@ -42,7 +42,7 @@ type SourceTestCase struct { // parse and execute the code. func (c SourceTestCase) TestFunc(name string) func(*testing.T) { return func(t *testing.T) { - vm := TestingVM() + vm := VM() msg, err := vm.ParseScanner(strings.NewReader(c.Source), name) if err != nil { t.Fatalf("could not parse %q: %v", c.Source, err) @@ -85,7 +85,7 @@ func PassEqual(want *iolang.Object) func(*iolang.Object, iolang.Stop) bool { // not 0. func PassUnequal(want *iolang.Object) func(*iolang.Object, iolang.Stop) bool { return func(result *iolang.Object, control iolang.Stop) bool { - vm := TestingVM() + vm := VM() if control != iolang.NoStop { return false } @@ -120,7 +120,7 @@ func PassIdentical(want *iolang.Object) func(*iolang.Object, iolang.Stop) bool { // the value check. Equality here has the same semantics as in PassEqual. func PassControl(want *iolang.Object, stop iolang.Stop) func(*iolang.Object, iolang.Stop) bool { return func(result *iolang.Object, control iolang.Stop) bool { - vm := TestingVM() + vm := VM() if control != stop { return false } @@ -174,7 +174,7 @@ func PassSuccess() func(*iolang.Object, iolang.Stop) bool { // false. func PassLocalSlots(want, exclude []string) func(*iolang.Object, iolang.Stop) bool { return func(result *iolang.Object, control iolang.Stop) bool { - vm := TestingVM() + vm := VM() if control != iolang.NoStop { return false } @@ -197,7 +197,7 @@ func PassLocalSlots(want, exclude []string) func(*iolang.Object, iolang.Stop) bo // compare equal. If the Stop is not NoStop, then the predicate returns false. func PassEqualSlots(want iolang.Slots) func(*iolang.Object, iolang.Stop) bool { return func(result *iolang.Object, control iolang.Stop) bool { - vm := TestingVM() + vm := VM() if control != iolang.NoStop { return false } @@ -230,7 +230,7 @@ func PassEqualSlots(want iolang.Slots) func(*iolang.Object, iolang.Stop) bool { func CheckSlots(t *testing.T, obj *iolang.Object, slots []string) { t.Helper() checked := make(map[string]bool, len(slots)) - on := TestingVM().GetAllSlots(obj) + on := VM().GetAllSlots(obj) for _, name := range slots { checked[name] = true t.Run("Have_"+name, func(t *testing.T) { @@ -264,7 +264,7 @@ func CheckObjectIsProto(t *testing.T, obj *iolang.Object) { default: t.Error("incorrect number of protos: expected 1, have", len(protos)) } - vm := TestingVM() + vm := VM() if p := protos[0]; p != vm.BaseObject { t.Errorf("wrong proto: expected %T@%p, have %T@%p", vm.BaseObject, vm.BaseObject, p, p) } diff --git a/testutils_test.go b/testutils_test.go deleted file mode 100644 index be83f59..0000000 --- a/testutils_test.go +++ /dev/null @@ -1,262 +0,0 @@ -package iolang - -import ( - "fmt" - "strings" - "sync" - "testing" -) - -// testVM is the VM used for all tests. -var testVM *VM - -var testVMInit sync.Once - -// TestingVM returns a VM for testing Io. The VM is shared by all tests. -func TestingVM() *VM { - testVMInit.Do(ResetTestingVM) - return testVM -} - -// ResetTestingVM reinitializes the VM returned by TestVM. It is not safe to -// call this in parallel tests. -func ResetTestingVM() { - testVM = NewVM() -} - -// BenchDummy is a dummy variable to prevent dead code elimination in -// benchmarks. -var BenchDummy *Object - -// A SourceTestCase is a test case containing source code and a predicate to -// check the result. -type SourceTestCase struct { - Source string - Pass func(result *Object, control Stop) bool -} - -// TestFunc returns a test function for the test case. -func (c SourceTestCase) TestFunc(name string) func(*testing.T) { - return func(t *testing.T) { - vm := TestingVM() - msg, err := vm.ParseScanner(strings.NewReader(c.Source), name) - if err != nil { - t.Fatalf("could not parse %q: %v", c.Source, err) - } - if err := vm.OpShuffle(vm.MessageObject(msg)); err != nil { - t.Fatalf("could not opshuffle %q: %v", c.Source, err) - } - if r, s := vm.DoMessage(msg, vm.Lobby); !c.Pass(r, s) { - if s == ExceptionStop && r.Tag() == ExceptionTag { - w := strings.Builder{} - ex := r.Value.(Exception) - fmt.Fprintf(&w, "%q produced wrong result; an exception occurred:\n", c.Source) - for i := len(ex.Stack) - 1; i >= 0; i-- { - m := ex.Stack[i] - if m.IsStart() { - fmt.Fprintf(&w, "\t%s\t%s:%d\n", m.Name(), m.Label, m.Line) - } else { - fmt.Fprintf(&w, "\t%s %s\t%s:%d\n", m.Prev.Name(), m.Name(), m.Label, m.Line) - } - } - fmt.Fprint(&w, vm.AsString(r)) - t.Error(w.String()) - } else { - t.Errorf("%q produced wrong result; got %s@%p (%s)", c.Source, vm.AsString(r), r, s) - } - } - } -} - -// PassEqual returns a Pass function for a SourceTestCase that predicates on -// equality. To determine equality, this first checks for equal identities; if -// not, it checks that the result of TestVM.Compare(want, result) is 0. -func PassEqual(want *Object) func(*Object, Stop) bool { - return PassControl(want, NoStop) -} - -// PassUnequal returns a Pass function for a SourceTestCase that predicates on -// non-equality by checking that the result of testVM.Compare(want, result) is -// not 0. -func PassUnequal(want *Object) func(*Object, Stop) bool { - return func(result *Object, control Stop) bool { - vm := TestingVM() - if control != NoStop { - return false - } - if want == result { - return false - } - v, obj, stop := vm.Compare(want, result) - if stop != NoStop { - return false - } - if obj != nil { - return false - } - return v != 0 - } -} - -// PassIdentical returns a Pass function for a SourceTestCase that predicates -// on identity equality. -func PassIdentical(want *Object) func(*Object, Stop) bool { - return func(result *Object, control Stop) bool { - if control != NoStop { - return false - } - return want == result - } -} - -// PassControl returns a Pass function for a SourceTestCase that predicates on -// equality with a certain control flow status. The control flow check precedes -// the value check. Equality here has the same semantics as in PassEqual. -func PassControl(want *Object, stop Stop) func(*Object, Stop) bool { - return func(result *Object, control Stop) bool { - vm := TestingVM() - if control != stop { - return false - } - if want == result { - return true - } - v, obj, stop := vm.Compare(want, result) - if stop != NoStop { - return false - } - if obj != nil { - return false - } - return v == 0 - } -} - -// PassTag returns a Pass function for a SourceTestCase that predicates on -// equality of the Tag of the result. -func PassTag(want Tag) func(*Object, Stop) bool { - return func(result *Object, control Stop) bool { - if control != NoStop { - return false - } - return result.Tag() == want - } -} - -// PassFailure returns a Pass function for a SourceTestCase that returns true -// iff the result is a raised exception. -func PassFailure() func(*Object, Stop) bool { - // This doesn't need to be a function returning a function, but it's nice to - // stay consistent with the other predicate generators. - return func(result *Object, control Stop) bool { - return control == ExceptionStop - } -} - -// PassSuccess returns a Pass function for a SourceTestCase that returns true -// iff the control flow status is NoStop. -func PassSuccess() func(*Object, Stop) bool { - return func(result *Object, control Stop) bool { - return control == NoStop - } -} - -// PassLocalSlots returns a Pass function for a SourceTestCase that returns -// true iff the result locally has all of the slots in want and none of the -// slots in exclude. -func PassLocalSlots(want, exclude []string) func(*Object, Stop) bool { - return func(result *Object, control Stop) bool { - vm := TestingVM() - if control != NoStop { - return false - } - for _, slot := range want { - if _, ok := vm.GetLocalSlot(result, slot); !ok { - return false - } - } - for _, slot := range exclude { - if _, ok := vm.GetLocalSlot(result, slot); ok { - return false - } - } - return true - } -} - -// PassEqualSlots returns a Pass function for a SourceTestCase that returns -// true iff the result has exactly the same slots as want and the slots' values -// compare equal. -func PassEqualSlots(want Slots) func(*Object, Stop) bool { - return func(result *Object, control Stop) bool { - vm := TestingVM() - if control != NoStop { - return false - } - slots := vm.GetAllSlots(result) - for slot := range slots { - if _, ok := want[slot]; !ok { - return false - } - } - for slot, value := range want { - x, ok := slots[slot] - if !ok { - return false - } - v, obj, stop := vm.Compare(x, value) - if stop != NoStop { - return false - } - if obj != nil { - return false - } - return v == 0 - } - return true - } -} - -// CheckSlots is a testing helper to check whether an object has exactly the -// slots we expect. -func CheckSlots(t *testing.T, obj *Object, slots []string) { - t.Helper() - checked := make(map[string]bool, len(slots)) - on := TestingVM().GetAllSlots(obj) - for _, name := range slots { - checked[name] = true - t.Run("Have_"+name, func(t *testing.T) { - slot, ok := on[name] - if !ok { - t.Fatal("no slot", name) - } - if slot == nil { - t.Fatal("slot", name, "is nil") - } - }) - } - for name := range on { - t.Run("Want_"+name, func(t *testing.T) { - if !checked[name] { - t.Fatal("unexpected slot", name) - } - }) - } -} - -// CheckObjectIsProto is a testing helper to check that an object has exactly -// one proto, which is Core Object. obj must come from the test VM. -func CheckObjectIsProto(t *testing.T, obj *Object) { - t.Helper() - switch obj.NumProtos() { - case 0: - t.Fatal("no protos") - case 1: // do nothing - default: - t.Error("incorrect number of protos: expected 1, have", obj.NumProtos()) - } - vm := TestingVM() - if p := obj.protos.p; p != vm.BaseObject { - t.Errorf("wrong proto: expected %T@%p, have %T@%p", vm.BaseObject, vm.BaseObject, p, p) - } -}