diff --git a/build.zig b/build.zig index 52d23e329..ec5255c17 100644 --- a/build.zig +++ b/build.zig @@ -7,11 +7,12 @@ const tcc_lib = @import("lib/tcc/lib.zig"); // FIND: v0.2 const Version = "0.2"; -var useMalloc: bool = undefined; +var optMalloc: ?config.Allocator = undefined; var selinux: bool = undefined; var vmEngine: config.Engine = undefined; var testFilter: ?[]const u8 = undefined; var trace: bool = undefined; +var optFFI: ?bool = undefined; var stdx: *std.build.Module = undefined; var tcc: *std.build.Module = undefined; @@ -23,8 +24,9 @@ pub fn build(b: *std.build.Builder) !void { selinux = b.option(bool, "selinux", "Whether you are building on linux distro with selinux. eg. Fedora.") orelse false; testFilter = b.option([]const u8, "test-filter", "Test filter."); - useMalloc = b.option(bool, "use-malloc", "Use C allocator.") orelse false; vmEngine = b.option(config.Engine, "vm", "Build with `zig` or `c` VM.") orelse .c; + optMalloc = b.option(config.Allocator, "malloc", "Override default allocator: `malloc`, `mimalloc`, `zig`"); + optFFI = b.option(bool, "ffi", "Override default FFI: true, false"); trace = b.option(bool, "trace", "Enable tracing features.") orelse (optimize == .Debug); stdx = b.createModule(.{ @@ -36,7 +38,8 @@ pub fn build(b: *std.build.Builder) !void { { const step = b.step("cli", "Build main cli."); - const opts = getDefaultOptions(target, optimize); + var opts = getDefaultOptions(target, optimize); + opts.applyOverrides(); const exe = b.addExecutable(.{ .name = "cyber", @@ -63,7 +66,7 @@ pub fn build(b: *std.build.Builder) !void { // exe.linkLibC(); exe.addModule("stdx", stdx); - if (opts.useMimalloc) { + if (opts.malloc == .mimalloc) { mimalloc_lib.addModule(exe, "mimalloc", mimalloc); mimalloc_lib.buildAndLink(b, exe, .{}); } @@ -78,7 +81,10 @@ pub fn build(b: *std.build.Builder) !void { { const step = b.step("lib", "Build as a library."); - const opts = getDefaultOptions(target, optimize); + var opts = getDefaultOptions(target, optimize); + opts.ffi = false; + opts.malloc = .malloc; + opts.applyOverrides(); const lib = b.addSharedLibrary(.{ .name = "cyber", @@ -101,7 +107,7 @@ pub fn build(b: *std.build.Builder) !void { try addBuildOptions(b, lib, opts); lib.addModule("stdx", stdx); - if (opts.useMimalloc) { + if (opts.malloc == .mimalloc) { mimalloc_lib.addModule(lib, "mimalloc", mimalloc); mimalloc_lib.buildAndLink(b, lib, .{}); } else { @@ -112,7 +118,7 @@ pub fn build(b: *std.build.Builder) !void { try buildCVM(b.allocator, lib, opts); } - if (!target.getCpuArch().isWasm()) { + if (opts.ffi) { tcc_lib.addModule(lib, "tcc", tcc); tcc_lib.buildAndLink(b, lib, .{ .selinux = selinux, @@ -133,6 +139,7 @@ pub fn build(b: *std.build.Builder) !void { var opts = getDefaultOptions(target, optimize); opts.trackGlobalRc = true; + opts.applyOverrides(); var step = b.addTest(.{ // Lib test includes main tests. @@ -148,7 +155,7 @@ pub fn build(b: *std.build.Builder) !void { step.addModule("stdx", stdx); step.rdynamic = true; - if (opts.useMimalloc) { + if (opts.malloc == .mimalloc) { mimalloc_lib.addModule(step, "mimalloc", mimalloc); mimalloc_lib.buildAndLink(b, step, .{}); } @@ -159,7 +166,7 @@ pub fn build(b: *std.build.Builder) !void { step.linkLibC(); - if (!target.getCpuArch().isWasm()) { + if (opts.ffi) { tcc_lib.addModule(step, "tcc", tcc); tcc_lib.buildAndLink(b, step, .{ .selinux = selinux, @@ -183,6 +190,7 @@ pub fn build(b: *std.build.Builder) !void { var opts = getDefaultOptions(target, optimize); opts.trackGlobalRc = true; + opts.applyOverrides(); var step = b.addTest(.{ .root_source_file = .{ .path = "./test/main_test.zig" }, @@ -197,7 +205,7 @@ pub fn build(b: *std.build.Builder) !void { step.addModule("stdx", stdx); step.rdynamic = true; - if (opts.useMimalloc) { + if (opts.malloc == .mimalloc) { mimalloc_lib.addModule(step, "mimalloc", mimalloc); mimalloc_lib.buildAndLink(b, step, .{}); } @@ -221,6 +229,7 @@ pub fn build(b: *std.build.Builder) !void { var opts = getDefaultOptions(target, optimize); opts.trackGlobalRc = true; + opts.applyOverrides(); var step = b.addTest(.{ .root_source_file = .{ .path = "./test/lib_test.zig" }, @@ -235,7 +244,7 @@ pub fn build(b: *std.build.Builder) !void { step.addModule("stdx", stdx); step.rdynamic = true; - if (opts.useMimalloc) { + if (opts.malloc == .mimalloc) { mimalloc_lib.addModule(step, "mimalloc", mimalloc); mimalloc_lib.buildAndLink(b, step, .{}); } @@ -259,6 +268,8 @@ pub fn build(b: *std.build.Builder) !void { const mainStep = b.step("test-trace", "Run trace tests."); var opts = getDefaultOptions(target, optimize); opts.trackGlobalRc = true; + opts.applyOverrides(); + const step = try addTraceTest(b, opts); mainStep.dependOn(&b.addRunArtifact(step).step); } @@ -282,8 +293,18 @@ pub const Options = struct { trace: bool, target: std.zig.CrossTarget, optimize: std.builtin.OptimizeMode, - useMimalloc: bool, + malloc: config.Allocator, gc: bool, + ffi: bool, + + fn applyOverrides(self: *Options) void { + if (optMalloc) |malloc| { + self.malloc = malloc; + } + if (optFFI) |ffi| { + self.ffi = ffi; + } + } }; // deps: struct { @@ -291,6 +312,15 @@ pub const Options = struct { // }, fn getDefaultOptions(target: std.zig.CrossTarget, optimize: std.builtin.OptimizeMode) Options { + var malloc: config.Allocator = .malloc; + // Use mimalloc for fast builds. + if (target.getCpuArch().isWasm()) { + malloc = .zig; + } else { + if (optimize == .ReleaseFast) { + malloc = .mimalloc; + } + } return .{ .selinux = selinux, .trackGlobalRc = optimize != .ReleaseFast, @@ -298,9 +328,8 @@ fn getDefaultOptions(target: std.zig.CrossTarget, optimize: std.builtin.Optimize .target = target, .optimize = optimize, .gc = true, - - // Use mimalloc for fast builds. - .useMimalloc = optimize == .ReleaseFast and !target.getCpuArch().isWasm(), + .malloc = malloc, + .ffi = !target.getCpuArch().isWasm(), }; } @@ -323,14 +352,13 @@ fn createBuildOptions(b: *std.build.Builder, opts: Options) !*std.build.Step.Opt build_options.addOption([]const u8, "version", Version); build_options.addOption([]const u8, "build", buildTag); build_options.addOption([]const u8, "commit", commitTag); - build_options.addOption(bool, "useMalloc", useMalloc); - build_options.addOption(bool, "useMimalloc", opts.useMimalloc); - + build_options.addOption(config.Allocator, "malloc", opts.malloc); build_options.addOption(config.Engine, "vmEngine", vmEngine); build_options.addOption(bool, "trace", opts.trace); build_options.addOption(bool, "trackGlobalRC", opts.trackGlobalRc); build_options.addOption(bool, "is32Bit", is32Bit(opts.target)); build_options.addOption(bool, "gc", opts.gc); + build_options.addOption(bool, "ffi", opts.ffi); build_options.addOption([]const u8, "full_version", b.fmt("Cyber {s} build-{s}-{s}", .{Version, buildTag, commitTag})); return build_options; } @@ -356,7 +384,7 @@ fn addTraceTest(b: *std.build.Builder, opts: Options) !*std.build.LibExeObjStep try addBuildOptions(b, step, newOpts); step.addModule("stdx", stdx); - if (newOpts.useMimalloc) { + if (newOpts.malloc == .mimalloc) { mimalloc_lib.addModule(step, "mimalloc", mimalloc); mimalloc_lib.buildAndLink(b, step, .{}); } diff --git a/docs/hugo/content/docs/toc/modules.md b/docs/hugo/content/docs/toc/modules.md index 9a18243db..d74a22199 100644 --- a/docs/hugo/content/docs/toc/modules.md +++ b/docs/hugo/content/docs/toc/modules.md @@ -79,42 +79,34 @@ The annotation `@hide` provides a hint to editors that the static symbol should > _Planned Feature_ ## Builtin Modules. +Builtin modules are the bare minimum that comes with Cyber. The [embeddable library]({{}}) contains these modules and nothing more. They include: +- [builtins](#builtins): Cyber related functions and commonly used utilities. +- [math](#math): Math constants and functions. +> _Incomplete: The docs for builtin modules are not completely up-to-date. They will be auto generated in the future._ -Cyber currently contains the builtin modules: -- [core](#core-module): Cyber related functions and commonly used utilities. -- [math](#math-module): Math constants and functions. -- [os](#os-module): System level functions. -- [test](#test-module): Utilities for testing. - -> _Incomplete: The docs for builtins are not completely up-to-date. They will be auto generated in the future._ - -## Core Module. -The core module contains functions related to Cyber and common utilities. It is automatically imported into each script's namespace. +## builtins. +The `builtins` module contains functions related to Cyber and common utilities. It is automatically imported into each script's namespace. Sample usage: ```cy +-- `print` and `typeid` are available without imports. print 'hello' -var contents = readFile 'foo.txt' -print contents +var id = typeid('my str') +print id ``` | Function | Summary | | ------------- | ----- | | `arrayFill(val any, n int) List` | Creates a list with initial capacity of `n` and values set to `val`. If the value is an object, it is shallow copied `n` times. | | `boolean(val any) boolean` | Converts a value to either `true` or `false`. | -| `cacheUrl(url string) string` | Returns the path of a locally cached file of `url`. If no such file exists locally, it's fetched from `url`. | | `copy(val any) any` | Copies a primitive value or creates a shallow copy of an object value. | | `error(e (enum \| symbol)) error` | Create an error from an enum or symbol. | -| `execCmd(args []string) Map{ out, err, exited }` | Runs a shell command and returns the stdout/stderr. | -| `exit(status int) noreturn` | Exits the program with a status code. | | `evalJS(val string) none` | Evals JS from the host environment. This is only available in a web WASM build of Cyber. | -| `fetchUrl(url string) rawstring` | Fetches the contents at `url` using the HTTP GET request method. | -| `getInput() rawstring` | Reads stdin until a new line is reached. This is intended to read user input from the command line. For bulk reads from stdin, use `os.stdin`. | +| `float(val any) float` | Casts or converts the value to a `float`. Panics if type conversion fails. | | `int(val any) int` | Converts a value to an 32-bit integer. | | `isAlpha(val int) boolean` | Returns whether a rune is an alphabetic letter. | | `isDigit(val int) boolean` | Returns whether a rune is a digit. | | `must(val any) any \| noreturn` | If `val` is an error, `panic(val)` is invoked. Otherwise, `val` is returned. | -| `float(val any) float` | Casts or converts the value to a `float`. Panics if type conversion fails. | | `panic(e symbol) noreturn` | Stop execution in the current fiber and starts unwinding the call stack. See [Unexpected Errors]({{}}). | | `parseCyber(src any) map` | Parses Cyber source string into structured map object. Currently, only metadata about static declarations is made available but this will be extended to include an AST. | | `parseCyon(src any) any` | Parses a CYON string into a value. | @@ -123,16 +115,13 @@ print contents | `print(s string) none` | Prints a value as a string to stdout. The new line is also printed. | | `prints(s string) none` | Prints a value as a string to stdout. | | `rawstring(str string) rawstring` | Converts a string to a `rawstring`. | -| `readAll() rawstring` | Reads stdin to the EOF as a `rawstring`. | -| `readFile(path string) rawstring` | Reads the file contents into a `rawstring` value. | | `runestr(val int) string` | Converts a rune to a string. | | `string(val any) string` | Converts a value to a string. | | `toCyon(val any) string` | Encodes a value to CYON string. | | `typeof(any) metatype` | Returns the value's type as a `metatype` object. | | `typesym(any) symbol` | Returns the value's type as one of the predefined symbols: #float, #int, #boolean, #object, #list, #map, #string, #rawstring, #function, #fiber, #pointer, #symbol, #metatype, #none, #error | -| `writeFile(path string, contents string) none` | Writes a string value to a file. | -## Math Module. +## math. The math module contains commonly used math constants and functions. Sample usage: @@ -195,7 +184,13 @@ print(m.pi * r^2) | tanh(float) float | Returns the hyperbolic tangent of x. | | trunc(float) float | Returns the integer portion of x, removing any fractional digits. | -## Os Module. +## Std Modules. +Std modules come with Cyber's CLI. They include: +- [os](#os): System level functions. +- [test](#test): Utilities for testing. +> _Incomplete: The docs for std modules are not completely up-to-date. They will be auto generated in the future._ + +## os. Cyber's os module contains system level functions. It's still undecided as to how much should be included here so it's incomplete. You can still access os and libc functions yourself using Cyber's FFI or embedding API. Sample usage: @@ -222,29 +217,37 @@ for map each k, v: | `args() List` | Returns the command line arguments as a list. Each argument is validated and returned as a UTF-8 `string` or `rawstring` if the validation failed. | | `bindLib(path any, decls [](CFunc\|CStruct)) Object \| Map` | Calls `bindLib(path, decls, {})`. | | `bindLib(path any, decls [](CFunc\|CStruct), config: BindLibConfig) Object \| Map` | Creates an FFI binding to a dynamic library and it's symbols. By default, an anonymous object is returned with the C-functions binded as the object's methods. If `config` contains `genMap: true`, a `Map` is returned instead with C-functions binded as function values. | +| `cacheUrl(url string) string` | Returns the path of a locally cached file of `url`. If no such file exists locally, it's fetched from `url`. | | `copyFile(srcPath any, dstPath any) none \| error` | Copies a file to a destination path. | | `createDir(path any) true \| error` | Creates the directory at `path`. Returns `true` if successful. | | `createFile(path any, truncate boolean) File \| error` | Creates and opens the file at `path`. If `truncate` is true, an existing file will be truncated. | | `cstr(any) pointer` | Returns a null terminated C string. | | `cwd() string` | Returns the current working directory. | | `dirName(path any) string \| none` | Returns the given path with its last component removed. | +| `execCmd(args []string) Map{ out, err, exited }` | Runs a shell command and returns the stdout/stderr. | | `exePath() string` | Returns the current executable's path. | +| `exit(status int) noreturn` | Exits the program with a status code. | +| `fetchUrl(url string) rawstring` | Fetches the contents at `url` using the HTTP GET request method. | | `free(ptr pointer) none` | Frees the memory located at `ptr`. | | `fromCstr(pointer) rawstring` | Returns a `rawstring` from a null terminated C string. | | `getEnv(key any) string \| none` | Returns an environment value by key. | | `getEnvAll() Map` | Returns all environment entries as a `Map`. | +| `getInput() rawstring` | Reads stdin until a new line is reached. This is intended to read user input from the command line. For bulk reads from stdin, use `os.stdin`. | | `malloc(size int) pointer` | Allocates `size` bytes of memory and returns a pointer. | | `milliTime() float` | Return the calendar timestamp, in milliseconds, relative to UTC 1970-01-01. | | `openDir(path any) Dir \| error` | Invokes `openDir(path, false)`. | | `openDir(path any, iterable boolean) Dir \| error` | Opens a directory at the given `path`. `iterable` indicates that the directory's entries can be iterated. | | `openFile(path any, mode (#read \| #write \| #readWrite)) File \| error` | Opens a file at the given `path` with the `#read`, `#write`, or `#readWrite` mode. | | `parseArgs(options list[ArgOption]) map` | Given expected `ArgOption`s, returns a map of the options and a `rest` entry which contains the non-option arguments. | +| `readAll() rawstring` | Reads stdin to the EOF as a `rawstring`. | +| `readFile(path string) rawstring` | Reads the file contents into a `rawstring` value. | | `realPath(path any) string \| error` | Returns the absolute path of the given path. | | `removeDir(path any) true \| error` | Removes an empty directory at `path`. Returns `true` if successful. | | `removeFile(path any) true \| error` | Removes the file at `path`. Returns `true` if successful. | | `setEnv(key any, value any) none` | Sets an environment value by key. | | `sleep(ms float) none` | Pauses the current thread for given milliseconds. | | `unsetEnv(key any) none` | Removes an environment value by key. | +| `writeFile(path string, contents string) none` | Writes a string value to a file. | ### `type File` | Method | Summary | @@ -287,7 +290,7 @@ for map each k, v: | `'type' -> metatype(string \| float \| boolean)` | Parse as given value type. | | `'default' -> any` | Optional: Default value if option is missing. `none` is used if this is not provided. | -## Test Module. +## test. The `test` module contains utilities for testing. Sample usage: diff --git a/docs/hugo/content/docs/toc/syntax.md b/docs/hugo/content/docs/toc/syntax.md index 86b2f8d04..f68ee235b 100644 --- a/docs/hugo/content/docs/toc/syntax.md +++ b/docs/hugo/content/docs/toc/syntax.md @@ -159,7 +159,7 @@ var myImage: The final resulting value that is assigned to the static variable is provided by a `break` statement. If a `break` statement is not provided, `none` is assigned instead. ## Keywords. -There are currently `33` keywords in Cyber. This list categorizes them and shows you when you might need them. +There are currently `34` keywords in Cyber. This list categorizes them and shows you when you might need them. - [Control Flow]({{}}): `if` `then` `else` `match` `while` `for` `each` `break` `continue` `pass` `some` - [Operators](#operators): `or` `and` `not` `is` @@ -169,6 +169,7 @@ There are currently `33` keywords in Cyber. This list categorizes them and shows - [Data Types]({{}}): `type` `object` `enum` `true` `false` `none` - [Error Handling]({{}}): `try` `catch` `error` `throw` - [Modules]({{}}): `import` +- [Embedding]({{}}): `hostfunc` ## Operators. Cyber supports the following operators. They are ordered from highest to lowest precedence. diff --git a/src/api.zig b/src/api.zig index a3cb72afb..cb9d05cb3 100644 --- a/src/api.zig +++ b/src/api.zig @@ -16,7 +16,7 @@ const Value = cy.Value; /// A simplified VM handle. pub const UserVM = struct { - dummy: u64 align(@alignOf(cy.VM)) = undefined, + dummy: u64 = undefined, pub fn init(self: *UserVM, alloc: std.mem.Allocator) !void { try self.internal().init(alloc); @@ -34,6 +34,29 @@ pub const UserVM = struct { return @ptrCast(self); } + pub fn setApiError(self: *const UserVM, str: []const u8) void { + const vm = self.constInternal(); + vm.compiler.hasApiError = true; + vm.alloc.free(vm.compiler.apiError); + vm.compiler.apiError = vm.alloc.dupe(u8, str) catch cy.fatal(); + } + + pub fn setPrint(self: *UserVM, print: cy.PrintFn) void { + self.internal().print = print; + } + + pub fn getModuleLoader(self: *const UserVM) cy.ModuleLoaderFn { + return self.constInternal().compiler.moduleLoader; + } + + pub fn setModuleLoader(self: *const UserVM, loader: cy.ModuleLoaderFn) void { + self.constInternal().compiler.moduleLoader = loader; + } + + pub fn setModuleResolver(self: *const UserVM, resolver: cy.ModuleResolverFn) void { + self.constInternal().compiler.moduleResolver = resolver; + } + pub fn ensureUntypedFuncSig(self: *UserVM, numParams: u32) !cy.sema.FuncSigId { return cy.sema.ensureResolvedUntypedFuncSig(self.internal().compiler, numParams); } @@ -46,10 +69,6 @@ pub const UserVM = struct { self.internal().userData = userData; } - pub fn addModuleLoader(self: *UserVM, absSpec: []const u8, func: cy.ModuleLoaderFunc) !void { - return self.internal().compiler.addModuleLoader(absSpec, func); - } - pub fn getStackTrace(self: *UserVM) *const cy.StackTrace { return @as(*const VM, @ptrCast(self)).getStackTrace(); } diff --git a/src/builtins/bindings.zig b/src/builtins/bindings.zig index b7009e77a..ba3dd7347 100644 --- a/src/builtins/bindings.zig +++ b/src/builtins/bindings.zig @@ -445,18 +445,18 @@ pub fn bindCore(self: *cy.VM) linksection(cy.InitSection) !void { try b.addMethod(rt.FileT, streamLines, &.{ bt.Any, bt.Float }, bt.Any, fileStreamLines1); try b.addMethod(rt.FileT, write, &.{ bt.Any, bt.Any }, bt.Any, fileWrite); } else { - try b.addMethod(rt.FileT, close, &.{ bt.Any }, bt.None, objNop0); - try b.addMethod(rt.FileT, self.iteratorMGID, &.{ bt.Any }, bt.Any, objNop0); - try b.addMethod(rt.FileT, self.nextMGID, &.{ bt.Any }, bt.Any, objNop0); - try b.addMethod(rt.FileT, read, &.{ bt.Any, bt.Integer }, bt.Any, objNop1); - try b.addMethod(rt.FileT, readToEnd, &.{ bt.Any }, bt.Any, objNop1); - try b.addMethod(rt.FileT, seek, &.{ bt.Any, bt.Integer }, bt.Any, objNop1); - try b.addMethod(rt.FileT, seekFromCur, &.{ bt.Any, bt.Integer }, bt.Any, objNop1); - try b.addMethod(rt.FileT, seekFromEnd, &.{ bt.Any, bt.Integer }, bt.Any, objNop1); - try b.addMethod(rt.FileT, stat, &.{ bt.Any }, bt.Any, objNop0); - try b.addMethod(rt.FileT, streamLines, &.{ bt.Any }, bt.Any, objNop0); - try b.addMethod(rt.FileT, streamLines, &.{ bt.Any, bt.Float }, bt.Any, objNop1); - try b.addMethod(rt.FileT, write, &.{ bt.Any, bt.Any }, bt.Any, objNop1); + try b.addMethod(rt.FileT, close, &.{ bt.Any }, bt.None, nop); + try b.addMethod(rt.FileT, self.iteratorMGID, &.{ bt.Any }, bt.Any, nop); + try b.addMethod(rt.FileT, self.nextMGID, &.{ bt.Any }, bt.Any, nop); + try b.addMethod(rt.FileT, read, &.{ bt.Any, bt.Integer }, bt.Any, nop); + try b.addMethod(rt.FileT, readToEnd, &.{ bt.Any }, bt.Any, nop); + try b.addMethod(rt.FileT, seek, &.{ bt.Any, bt.Integer }, bt.Any, nop); + try b.addMethod(rt.FileT, seekFromCur, &.{ bt.Any, bt.Integer }, bt.Any, nop); + try b.addMethod(rt.FileT, seekFromEnd, &.{ bt.Any, bt.Integer }, bt.Any, nop); + try b.addMethod(rt.FileT, stat, &.{ bt.Any }, bt.Any, nop); + try b.addMethod(rt.FileT, streamLines, &.{ bt.Any }, bt.Any, nop); + try b.addMethod(rt.FileT, streamLines, &.{ bt.Any, bt.Float }, bt.Any, nop); + try b.addMethod(rt.FileT, write, &.{ bt.Any, bt.Any }, bt.Any, nop); } id = try self.addBuiltinType("Dir", cy.NullId); @@ -466,9 +466,9 @@ pub fn bindCore(self: *cy.VM) linksection(cy.InitSection) !void { try b.addMethod(rt.DirT, stat, &.{ bt.Any }, bt.Any, fileOrDirStat); try b.addMethod(rt.DirT, walk, &.{ bt.Any }, bt.Any, dirWalk); } else { - try b.addMethod(rt.DirT, self.iteratorMGID, &.{ bt.Any }, bt.Any, objNop0); - try b.addMethod(rt.DirT, stat, &.{ bt.Any }, bt.Any, objNop0); - try b.addMethod(rt.DirT, walk, &.{ bt.Any }, bt.Any, objNop0); + try b.addMethod(rt.DirT, self.iteratorMGID, &.{ bt.Any }, bt.Any, nop); + try b.addMethod(rt.DirT, stat, &.{ bt.Any }, bt.Any, nop); + try b.addMethod(rt.DirT, walk, &.{ bt.Any }, bt.Any, nop); } id = try self.addBuiltinType("DirIterator", cy.NullId); @@ -476,7 +476,7 @@ pub fn bindCore(self: *cy.VM) linksection(cy.InitSection) !void { if (cy.hasStdFiles) { try b.addMethod(rt.DirIteratorT, self.nextMGID, &.{ bt.Any }, bt.Any, dirIteratorNext); } else { - try b.addMethod(rt.DirIteratorT, self.nextMGID, &.{ bt.Any }, bt.Any, objNop0); + try b.addMethod(rt.DirIteratorT, self.nextMGID, &.{ bt.Any }, bt.Any, nop); } id = try self.addBuiltinType("MetaType", bt.MetaType); @@ -562,8 +562,8 @@ fn ensureSymbol(vm: *cy.VM, name: []const u8, sym: Symbol) !void { std.debug.assert(id == @intFromEnum(sym)); } -fn listSort(vm: *cy.UserVM, recv: Value, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { - const obj = recv.asHeapObject(); +fn listSort(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { + const obj = args[0].asHeapObject(); const list = cy.ptrAlignCast(*cy.List(Value), &obj.list.list); const LessContext = struct { lessFn: Value, @@ -571,7 +571,7 @@ fn listSort(vm: *cy.UserVM, recv: Value, args: [*]const Value, nargs: u8) linkse newFramePtr: u32, }; var lessCtx = LessContext{ - .lessFn = args[0], + .lessFn = args[1], .vm = vm, .newFramePtr = vm.getNewFramePtrOffset(args, nargs), }; @@ -589,9 +589,9 @@ fn listSort(vm: *cy.UserVM, recv: Value, args: [*]const Value, nargs: u8) linkse return Value.None; } -fn listRemove(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.Section) Value { - const index: i64 = @intCast(args[0].asInteger()); - const list = recv.asHeapObject(); +fn listRemove(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.Section) Value { + const index: i64 = @intCast(args[1].asInteger()); + const list = args[0].asHeapObject(); const inner = cy.ptrAlignCast(*cy.List(Value), &list.list.list); if (index < 0 or index >= inner.len) { return prepareThrowSymbol(vm, .OutOfBounds); @@ -601,10 +601,10 @@ fn listRemove(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksect return Value.None; } -fn listInsert(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.Section) Value { - const index: i64 = @intCast(args[0].asInteger()); - const value = args[1]; - const list = recv.asHeapObject(); +fn listInsert(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.Section) Value { + const index: i64 = @intCast(args[1].asInteger()); + const value = args[2]; + const list = args[0].asHeapObject(); const inner = cy.ptrAlignCast(*cy.List(Value), &list.list.list); if (index < 0 or index > inner.len) { return prepareThrowSymbol(vm, .OutOfBounds); @@ -614,24 +614,24 @@ fn listInsert(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksect return Value.None; } -fn listAdd(vm: *cy.UserVM, recv: Value, args: [*]const Value, nargs: u8) linksection(cy.Section) Value { +fn listAdd(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.Section) Value { fmt.printDeprecated("list.add()", "0.1", "Use list.append() instead.", &.{}); - return listAppend(vm, recv, args, nargs); + return listAppend(vm, args, nargs); } -fn listAppend(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.Section) Value { - const obj = recv.asHeapObject(); - vm.retain(args[0]); - obj.list.append(vm.allocator(), args[0]); +fn listAppend(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.Section) Value { + const obj = args[0].asHeapObject(); + vm.retain(args[1]); + obj.list.append(vm.allocator(), args[1]); return Value.None; } -fn listJoinString(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = recv.asHeapObject(); +fn listJoinString(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = args[0].asHeapObject(); const items = obj.list.items(); if (items.len > 0) { var sepCharLen: u32 = undefined; - const sep = vm.valueToTempString2(args[0], &sepCharLen); + const sep = vm.valueToTempString2(args[1], &sepCharLen); const alloc = vm.allocator(); const tempSlices = &@as(*cy.VM, @ptrCast(vm)).u8Buf2; @@ -690,9 +690,9 @@ fn listJoinString(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) link } } -fn listConcat(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = recv.asHeapObject(); - const list = args[0].asHeapObject(); +fn listConcat(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = args[0].asHeapObject(); + const list = args[1].asHeapObject(); for (list.list.items()) |it| { vm.retain(it); obj.list.append(vm.allocator(), it); @@ -700,8 +700,8 @@ fn listConcat(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksect return Value.None; } -fn listIteratorNextPair(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(cy.Section) cy.ValuePair { - const obj = recv.asHeapObject(); +fn listIteratorNextPair(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.Section) cy.ValuePair { + const obj = args[0].asHeapObject(); const list = obj.listIter.list; if (obj.listIter.nextIdx < list.list.len) { defer obj.listIter.nextIdx += 1; @@ -717,8 +717,8 @@ fn listIteratorNextPair(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) l }; } -fn listIteratorNext(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(cy.Section) Value { - const obj = recv.asHeapObject(); +fn listIteratorNext(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.Section) Value { + const obj = args[0].asHeapObject(); const list = obj.listIter.list; if (obj.listIter.nextIdx < list.list.len) { defer obj.listIter.nextIdx += 1; @@ -728,16 +728,17 @@ fn listIteratorNext(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) links } else return Value.None; } -fn listIterator(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(cy.Section) Value { - const obj = recv.asHeapObject(); +fn listIterator(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.Section) Value { + const obj = args[0].asHeapObject(); vm.retainObject(obj); return vm.allocListIterator(&obj.list) catch fatal(); } -fn listResize(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { +fn listResize(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const recv = args[0]; const list = recv.asHeapObject(); const inner = cy.ptrAlignCast(*cy.List(Value), &list.list.list); - const size: u32 = @intCast(args[0].asInteger()); + const size: u32 = @intCast(args[1].asInteger()); if (inner.len < size) { const oldLen = inner.len; inner.resize(vm.allocator(), size) catch cy.fatal(); @@ -754,14 +755,14 @@ fn listResize(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksect return Value.None; } -fn mapIterator(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(Section) Value { - const obj = recv.asHeapObject(); +fn mapIterator(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(Section) Value { + const obj = args[0].asHeapObject(); vm.retainObject(obj); return vm.allocMapIterator(&obj.map) catch fatal(); } -fn mapIteratorNextPair(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(Section) cy.ValuePair { - const obj = recv.asHeapObject(); +fn mapIteratorNextPair(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(Section) cy.ValuePair { + const obj = args[0].asHeapObject(); const map: *cy.ValueMap = @ptrCast(&obj.mapIter.map.inner); if (map.next(&obj.mapIter.nextIdx)) |entry| { vm.retain(entry.key); @@ -776,8 +777,8 @@ fn mapIteratorNextPair(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) li }; } -fn mapIteratorNext(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(cy.Section) Value { - const obj = recv.asHeapObject(); +fn mapIteratorNext(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.Section) Value { + const obj = args[0].asHeapObject(); const map: *cy.ValueMap = @ptrCast(&obj.mapIter.map.inner); if (map.next(&obj.mapIter.nextIdx)) |entry| { vm.retain(entry.value); @@ -785,21 +786,21 @@ fn mapIteratorNext(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linkse } else return Value.None; } -fn mapSize(_: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(cy.Section) Value { - const obj = recv.asHeapObject(); +fn mapSize(_: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.Section) Value { + const obj = args[0].asHeapObject(); const inner = cy.ptrAlignCast(*cy.MapInner, &obj.map.inner); return Value.initInt(@intCast(inner.size)); } -fn mapRemove(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = recv.asHeapObject(); +fn mapRemove(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = args[0].asHeapObject(); const inner = cy.ptrAlignCast(*cy.MapInner, &obj.map.inner); - _ = inner.remove(@ptrCast(vm), args[0]); + _ = inner.remove(@ptrCast(vm), args[1]); return Value.None; } -fn listLen(_: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(cy.Section) Value { - const list = recv.asHeapObject(); +fn listLen(_: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.Section) Value { + const list = args[0].asHeapObject(); const inner = cy.ptrAlignCast(*cy.List(Value), &list.list.list); return Value.initInt(@intCast(inner.len)); } @@ -829,7 +830,8 @@ fn listLen(_: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(cy. // return Value.None; // } -fn errorValue(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) Value { +fn errorValue(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { + const recv = args[0]; const enumId = (recv.val & 0xFF00) >> 8; if (enumId == cy.NullU8) { return Value.initSymbol(recv.asErrorSymbol()); @@ -839,13 +841,13 @@ fn errorValue(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) Value { } } -fn pointerValue(_: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) Value { - const obj = recv.asHeapObject(); +fn pointerValue(_: *cy.UserVM, args: [*]const Value, _: u8) Value { + const obj = args[0].asHeapObject(); return Value.initInt(@bitCast(@as(u48, (@intCast(@intFromPtr(obj.pointer.ptr)))))); } -fn fiberStatus(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) Value { - const fiber = recv.asPointer(*vmc.Fiber); +fn fiberStatus(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { + const fiber = args[0].asPointer(*vmc.Fiber); if (vm.internal().curFiber == fiber) { return Value.initSymbol(@intFromEnum(Symbol.running)); @@ -859,10 +861,10 @@ fn fiberStatus(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) Value { } } -pub fn stringUpper(comptime T: cy.StringType) cy.NativeObjFuncPtr { +pub fn stringUpper(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = getStringObject(T, recv); + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = getStringObject(T, args[0]); const str = getStringSlice(T, vm, obj); if (isAstringObject(T, obj)) { const new = vm.allocUnsetAstringObject(str.len) catch fatal(); @@ -885,10 +887,10 @@ pub fn stringUpper(comptime T: cy.StringType) cy.NativeObjFuncPtr { return S.inner; } -pub fn stringLower(comptime T: cy.StringType) cy.NativeObjFuncPtr { +pub fn stringLower(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = getStringObject(T, recv); + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = getStringObject(T, args[0]); const str = getStringSlice(T, vm, obj); if (isAstringObject(T, obj)) { const new = vm.allocUnsetAstringObject(str.len) catch fatal(); @@ -911,21 +913,21 @@ pub fn stringLower(comptime T: cy.StringType) cy.NativeObjFuncPtr { return S.inner; } -pub fn stringLess(comptime T: cy.StringType) cy.NativeObjFuncPtr { +pub fn stringLess(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = getStringObject(T, recv); + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = getStringObject(T, args[0]); const str = getStringSlice(T, vm, obj); if (isRawStringObject(T)) { var right: []const u8 = undefined; - if (args[0].isRawString()) { - right = args[0].asRawString(); + if (args[1].isRawString()) { + right = args[1].asRawString(); } else { - right = vm.valueToTempString(args[0]); + right = vm.valueToTempString(args[1]); } return Value.initBool(std.mem.lessThan(u8, str, right)); } else { - const right = vm.valueToTempString(args[0]); + const right = vm.valueToTempString(args[1]); return Value.initBool(std.mem.lessThan(u8, str, right)); } } @@ -933,10 +935,10 @@ pub fn stringLess(comptime T: cy.StringType) cy.NativeObjFuncPtr { return S.inner; } -pub fn stringLen(comptime T: cy.StringType) cy.NativeObjFuncPtr { +pub fn stringLen(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(cy.Section) Value { - const obj = getStringObject(T, recv); + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.Section) Value { + const obj = getStringObject(T, args[0]); if (isAstringObject(T, obj)) { if (T == .astring) { return Value.initInt(@intCast(obj.astring.len)); @@ -957,22 +959,22 @@ pub fn stringLen(comptime T: cy.StringType) cy.NativeObjFuncPtr { return S.inner; } -fn stringCharAt(comptime T: cy.StringType) cy.NativeObjFuncPtr { +fn stringCharAt(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { + fn inner(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { fmt.printDeprecated("string.charAt()", "0.2", "Use string.sliceAt() instead.", &.{}); - return @call(.never_inline, stringSliceAt(T), .{vm, recv, args, nargs}); + return @call(.never_inline, stringSliceAt(T), .{vm, args, nargs}); } }; return S.inner; } -pub fn stringSliceAt(comptime T: cy.StringType) cy.NativeObjFuncPtr { +pub fn stringSliceAt(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = getStringObject(T, recv); + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = getStringObject(T, args[0]); const str = getStringSlice(T, vm, obj); - var idx = args[0].asInteger(); + var idx = args[1].asInteger(); if (isAstringObject(T, obj)) { if (idx < 0) { @@ -1033,22 +1035,22 @@ pub fn stringSliceAt(comptime T: cy.StringType) cy.NativeObjFuncPtr { return S.inner; } -pub fn stringCodeAt(comptime T: cy.StringType) cy.NativeObjFuncPtr { +pub fn stringCodeAt(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { + fn inner(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { fmt.printDeprecated("string.codeAt()", "0.2", "Use string.runeAt() instead.", &.{}); - return @call(.never_inline, stringRuneAt(T), .{vm, recv, args, nargs}); + return @call(.never_inline, stringRuneAt(T), .{vm, args, nargs}); } }; return S.inner; } -fn stringRuneAt(comptime T: cy.StringType) cy.NativeObjFuncPtr { +fn stringRuneAt(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = getStringObject(T, recv); + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = getStringObject(T, args[0]); const str = getStringSlice(T, vm, obj); - const idx = args[0].asInteger(); + const idx = args[1].asInteger(); if (isAstringObject(T, obj)) { if (idx < 0 or idx >= str.len) { return prepareThrowSymbol(vm, .OutOfBounds); @@ -1098,16 +1100,16 @@ fn rawStringInsertByteCommon(vm: *cy.UserVM, str: []const u8, indexv: Value, val return Value.initPtr(new); } -fn rawStringInsertByte(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.Section) Value { - const obj = recv.asHeapObject(); +fn rawStringInsertByte(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.Section) Value { + const obj = args[0].asHeapObject(); const str = obj.rawstring.getConstSlice(); - return rawStringInsertByteCommon(vm, str, args[0], args[1]); + return rawStringInsertByteCommon(vm, str, args[1], args[2]); } -fn rawStringSliceInsertByte(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.Section) Value { - const obj = recv.asHeapObject(); +fn rawStringSliceInsertByte(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.Section) Value { + const obj = args[0].asHeapObject(); const str = obj.rawstringSlice.getConstSlice(); - return rawStringInsertByteCommon(vm, str, args[0], args[1]); + return rawStringInsertByteCommon(vm, str, args[1], args[2]); } fn ustringReplaceCommon(vm: *cy.UserVM, str: []const u8, needlev: Value, replacev: Value) linksection(cy.StdSection) ?Value { @@ -1144,13 +1146,13 @@ inline fn isAstringObject(comptime T: cy.StringType, obj: StringObject(T)) bool return T == .staticAstring or T == .astring or (T == .slice and cy.heap.StringSlice.isAstring(obj.stringSlice)); } -fn stringRepeat(comptime T: cy.StringType) cy.NativeObjFuncPtr { +fn stringRepeat(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = getStringObject(T, recv); + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = getStringObject(T, args[0]); const str = getStringSlice(T, vm, obj); - const n = args[0].asInteger(); + const n = args[1].asInteger(); if (n < 0) { return prepareThrowSymbol(vm, .InvalidArgument); } @@ -1193,7 +1195,7 @@ fn stringRepeat(comptime T: cy.StringType) cy.NativeObjFuncPtr { vm.retainObject(obj); return Value.initPtr(obj); } else { - return recv; + return args[0]; } } } @@ -1309,12 +1311,12 @@ inline fn getStringSlice(comptime T: cy.StringType, vm: *cy.UserVM, obj: StringO } } -fn stringSplit(comptime T: cy.StringType) cy.NativeObjFuncPtr { +fn stringSplit(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = getStringObject(T, recv); + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = getStringObject(T, args[0]); const str = getStringSlice(T, vm, obj); - const delim = vm.valueToTempString(args[0]); + const delim = vm.valueToTempString(args[1]); const res = vm.allocEmptyList() catch fatal(); if (delim.len == 0) { @@ -1372,16 +1374,16 @@ fn stringSplit(comptime T: cy.StringType) cy.NativeObjFuncPtr { return S.inner; } -fn stringTrim(comptime T: cy.StringType) cy.NativeObjFuncPtr { +fn stringTrim(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = getStringObject(T, recv); + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = getStringObject(T, args[0]); const str = getStringSlice(T, vm, obj); - const trimRunes = vm.valueToTempString(args[1]); + const trimRunes = vm.valueToTempString(args[2]); var res: []const u8 = undefined; - const mode = getBuiltinSymbol(args[0].asSymbolId()) orelse { + const mode = getBuiltinSymbol(args[1].asSymbolId()) orelse { return prepareThrowSymbol(vm, .InvalidArgument); }; switch (mode) { @@ -1406,36 +1408,36 @@ fn stringTrim(comptime T: cy.StringType) cy.NativeObjFuncPtr { return S.inner; } -fn stringReplace(comptime T: cy.StringType) cy.NativeObjFuncPtr { +fn stringReplace(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = getStringObject(T, recv); + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = getStringObject(T, args[0]); const str = getStringSlice(T, vm, obj); if (isAstringObject(T, obj)) { - if (astringReplaceCommon(vm, str, args[0], args[1])) |val| { + if (astringReplaceCommon(vm, str, args[1], args[2])) |val| { return val; } else { if (T != .staticAstring) { vm.retainObject(obj); return Value.initPtr(obj); } else { - return recv; + return args[0]; } } } else if (isUstringObject(T, obj)) { - if (ustringReplaceCommon(vm, str, args[0], args[1])) |val| { + if (ustringReplaceCommon(vm, str, args[1], args[2])) |val| { return val; } else { if (T != .staticUstring) { vm.retainObject(obj); return Value.initPtr(obj); } else { - return recv; + return args[0]; } } } else if (isRawStringObject(T)) { - const needle = vm.valueToTempString(args[0]); - const replacement = vm.valueToNextTempString(args[1]); + const needle = vm.valueToTempString(args[1]); + const replacement = vm.valueToNextTempString(args[2]); const idxBuf = &@as(*cy.VM, @ptrCast(vm)).u8Buf; idxBuf.clearRetainingCapacity(); @@ -1486,22 +1488,22 @@ fn astringReplaceCommon(vm: *cy.UserVM, str: []const u8, needlev: Value, replace } } -pub fn stringSlice(comptime T: cy.StringType) cy.NativeObjFuncPtr { +pub fn stringSlice(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = getStringObject(T, recv); + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = getStringObject(T, args[0]); const str = getStringSlice(T, vm, obj); if (isAstringObject(T, obj)) { - var start = args[0].asInteger(); + var start = args[1].asInteger(); if (start < 0) { start = @as(i48, @intCast(str.len)) + start; } var end: i48 = undefined; - if (args[1].isNone()) { + if (args[2].isNone()) { end = @intCast(str.len); - } else if (args[1].isInteger()) { - end = args[1].asInteger(); + } else if (args[2].isInteger()) { + end = args[2].asInteger(); } else { return prepareThrowSymbol(vm, .InvalidArgument); } @@ -1522,11 +1524,11 @@ pub fn stringSlice(comptime T: cy.StringType) cy.NativeObjFuncPtr { } } else if (isUstringObject(T, obj)) { const charLen = getStringCharLen(T, vm, obj); - var start: i48 = args[0].asInteger(); + var start: i48 = args[1].asInteger(); if (start < 0) { start = @as(i48, @intCast(charLen)) + start; } - var end: i48 = if (args[1].isNone()) @intCast(charLen) else args[1].asInteger(); + var end: i48 = if (args[2].isNone()) @intCast(charLen) else args[2].asInteger(); if (end < 0) { end = @as(i48, @intCast(charLen)) + end; } @@ -1548,11 +1550,11 @@ pub fn stringSlice(comptime T: cy.StringType) cy.NativeObjFuncPtr { return vm.allocUstringSlice(str[startByteIdx..endByteIdx], uend - ustart, obj) catch fatal(); } } else if (isRawStringObject(T)) { - var start: i48 = args[0].asInteger(); + var start: i48 = args[1].asInteger(); if (start < 0) { start = @as(i48, @intCast(str.len)) + start; } - var end: i48 = if (args[1].isNone()) @intCast(str.len) else args[1].asInteger(); + var end: i48 = if (args[2].isNone()) @intCast(str.len) else args[2].asInteger(); if (end < 0) { end = @as(i48, @intCast(str.len)) + end; } @@ -1568,24 +1570,24 @@ pub fn stringSlice(comptime T: cy.StringType) cy.NativeObjFuncPtr { return S.inner; } -fn stringAppend(comptime T: cy.StringType) cy.NativeObjFuncPtr { +fn stringAppend(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { + fn inner(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { fmt.printDeprecated("string.append()", "0.1", "Use string.concat() instead.", &.{}); - return stringConcat(T)(vm, recv, args, nargs); + return stringConcat(T)(vm, args, nargs); } }; return S.inner; } -fn stringConcat(comptime T: cy.StringType) cy.NativeObjFuncPtr { +fn stringConcat(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = getStringObject(T, recv); + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = getStringObject(T, args[0]); const str = getStringSlice(T, vm, obj); if (isAstringObject(T, obj)) { var rcharLen: u32 = undefined; - const rstr = vm.valueToTempString2(args[0], &rcharLen); + const rstr = vm.valueToTempString2(args[1], &rcharLen); if (rcharLen == rstr.len) { return vm.allocAstringConcat(str, rstr) catch fatal(); } else { @@ -1593,11 +1595,11 @@ fn stringConcat(comptime T: cy.StringType) cy.NativeObjFuncPtr { } } else if (isUstringObject(T, obj)) { var rcharLen: u32 = undefined; - const rstr = vm.valueToTempString2(args[0], &rcharLen); + const rstr = vm.valueToTempString2(args[1], &rcharLen); const charLen = getStringCharLen(T, vm, obj); return vm.allocUstringConcat(str, rstr, charLen + rcharLen) catch fatal(); } else if (isRawStringObject(T)) { - const rstr = vm.valueToTempString(args[0]); + const rstr = vm.valueToTempString(args[1]); return vm.allocRawStringConcat(str, rstr) catch fatal(); } else fatal(); } @@ -1605,18 +1607,18 @@ fn stringConcat(comptime T: cy.StringType) cy.NativeObjFuncPtr { return S.inner; } -fn stringInsert(comptime T: cy.StringType) cy.NativeObjFuncPtr { +fn stringInsert(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = getStringObject(T, recv); + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = getStringObject(T, args[0]); const str = getStringSlice(T, vm, obj); - const idx = args[0].asInteger(); + const idx = args[1].asInteger(); if (isAstringObject(T, obj)) { if (idx < 0 or idx > str.len) { return prepareThrowSymbol(vm, .OutOfBounds); } var insertCharLen: u32 = undefined; - const insert = vm.valueToTempString2(args[1], &insertCharLen); + const insert = vm.valueToTempString2(args[2], &insertCharLen); const uidx: u32 = @intCast(idx); if (insertCharLen == insert.len) { return vm.allocAstringConcat3(str[0..uidx], insert, str[uidx..]) catch fatal(); @@ -1629,7 +1631,7 @@ fn stringInsert(comptime T: cy.StringType) cy.NativeObjFuncPtr { return prepareThrowSymbol(vm, .OutOfBounds); } var insertCharLen: u32 = undefined; - const insert = vm.valueToTempString2(args[1], &insertCharLen); + const insert = vm.valueToTempString2(args[2], &insertCharLen); const uidx: u32 = @intCast(idx); const mru = getUstringMruChar(T, vm, obj); const start: u32 = @intCast(cy.string.ustringSeekByCharIndex(str, mru.byteIdx, mru.charIdx, uidx)); @@ -1640,7 +1642,7 @@ fn stringInsert(comptime T: cy.StringType) cy.NativeObjFuncPtr { if (idx < 0 or idx > str.len) { return prepareThrowSymbol(vm, .OutOfBounds); } - const insert = vm.valueToTempString(args[1]); + const insert = vm.valueToTempString(args[2]); const new = vm.allocUnsetRawStringObject(str.len + insert.len) catch cy.fatal(); const buf = new.rawstring.getSlice(); const uidx: u32 = @intCast(idx); @@ -1654,22 +1656,22 @@ fn stringInsert(comptime T: cy.StringType) cy.NativeObjFuncPtr { return S.inner; } -fn stringIndex(comptime T: cy.StringType) cy.NativeObjFuncPtr { +fn stringIndex(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { + fn inner(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { fmt.printDeprecated("string.index()", "0.2", "Use string.find() instead.", &.{}); - return @call(.never_inline, stringFind(T), .{vm, recv, args, nargs}); + return @call(.never_inline, stringFind(T), .{vm, args, nargs}); } }; return S.inner; } -fn stringFind(comptime T: cy.StringType) cy.NativeObjFuncPtr { +fn stringFind(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = getStringObject(T, recv); + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = getStringObject(T, args[0]); const str = getStringSlice(T, vm, obj); - const needle = vm.valueToTempString(args[0]); + const needle = vm.valueToTempString(args[1]); if (needle.len > 0 and needle.len <= str.len) { if (needle.len == 1) { // One ascii char special case. Perform indexOfChar. @@ -1697,37 +1699,37 @@ fn stringFind(comptime T: cy.StringType) cy.NativeObjFuncPtr { return S.inner; } -fn stringStartsWith(comptime T: cy.StringType) cy.NativeObjFuncPtr { +fn stringStartsWith(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = getStringObject(T, recv); + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = getStringObject(T, args[0]); const str = getStringSlice(T, vm, obj); - const needle = vm.valueToTempString(args[0]); + const needle = vm.valueToTempString(args[1]); return Value.initBool(std.mem.startsWith(u8, str, needle)); } }; return S.inner; } -fn stringEndsWith(comptime T: cy.StringType) cy.NativeObjFuncPtr { +fn stringEndsWith(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = getStringObject(T, recv); + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = getStringObject(T, args[0]); const str = getStringSlice(T, vm, obj); - const needle = vm.valueToTempString(args[0]); + const needle = vm.valueToTempString(args[1]); return Value.initBool(std.mem.endsWith(u8, str, needle)); } }; return S.inner; } -fn rawStringSliceToString(vm: *cy.UserVM, recv: Value, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { +fn rawStringSliceToString(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { fmt.printDeprecated("rawstring.toString()", "0.1", "Use rawstring.utf8() instead.", &.{}); - return rawStringSliceUtf8(vm, recv, args, nargs); + return rawStringSliceUtf8(vm, args, nargs); } -fn rawStringSliceUtf8(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = recv.asHeapObject(); +fn rawStringSliceUtf8(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = args[0].asHeapObject(); const str = obj.rawstringSlice.getConstSlice(); if (cy.validateUtf8(str)) |size| { if (size == str.len) { @@ -1740,13 +1742,13 @@ fn rawStringSliceUtf8(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) lin } } -fn rawStringToString(vm: *cy.UserVM, recv: Value, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { +fn rawStringToString(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { fmt.printDeprecated("rawstring.toString()", "0.1", "Use rawstring.utf8() instead.", &.{}); - return rawStringUtf8(vm, recv, args, nargs); + return rawStringUtf8(vm, args, nargs); } -fn rawStringUtf8(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = recv.asHeapObject(); +fn rawStringUtf8(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = args[0].asHeapObject(); const str = obj.rawstring.getConstSlice(); if (cy.validateUtf8(str)) |size| { if (size == str.len) { @@ -1759,9 +1761,9 @@ fn rawStringUtf8(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksect } } -fn rawStringSliceByteAt(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = recv.asHeapObject(); - const idx: i48 = args[0].asInteger(); +fn rawStringSliceByteAt(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = args[0].asHeapObject(); + const idx: i48 = args[1].asInteger(); if (idx < 0 or idx >= obj.rawstringSlice.len) { return prepareThrowSymbol(vm, .OutOfBounds); } @@ -1770,9 +1772,9 @@ fn rawStringSliceByteAt(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8 return Value.initInt(@intCast(str[uidx])); } -fn rawStringByteAt(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = recv.asHeapObject(); - const idx: i48 = args[0].asInteger(); +fn rawStringByteAt(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = args[0].asHeapObject(); + const idx: i48 = args[1].asInteger(); if (idx < 0 or idx >= obj.rawstring.len) { return prepareThrowSymbol(vm, .OutOfBounds); } @@ -1794,15 +1796,15 @@ fn valueToChar(vm: *cy.UserVM, val: Value) u8 { } } -fn stringIsAscii(comptime T: cy.StringType) cy.NativeObjFuncPtr { +fn stringIsAscii(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(_: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(cy.StdSection) Value { + fn inner(_: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { if (T == .staticAstring) { return Value.True; } else if (T == .staticUstring) { return Value.False; } else { - const obj = getStringObject(T, recv); + const obj = getStringObject(T, args[0]); if (isAstringObject(T, obj)) { return Value.True; } else if (isUstringObject(T, obj)) { @@ -1816,23 +1818,23 @@ fn stringIsAscii(comptime T: cy.StringType) cy.NativeObjFuncPtr { return S.inner; } -pub fn stringIndexCharSet(comptime T: cy.StringType) cy.NativeObjFuncPtr { +pub fn stringIndexCharSet(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { + fn inner(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { fmt.printDeprecated("string.indexCharSet()", "0.2", "Use string.findAnyRune() instead.", &.{}); - return @call(.never_inline, stringFindAnyRune(T), .{vm, recv, args, nargs}); + return @call(.never_inline, stringFindAnyRune(T), .{vm, args, nargs}); } }; return S.inner; } -fn stringFindAnyRune(comptime T: cy.StringType) cy.NativeObjFuncPtr { +fn stringFindAnyRune(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = getStringObject(T, recv); + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = getStringObject(T, args[0]); const str = getStringSlice(T, vm, obj); var setCharLen: u32 = undefined; - const set = vm.valueToTempString2(args[0], &setCharLen); + const set = vm.valueToTempString2(args[1], &setCharLen); const setIsAscii = setCharLen == set.len; if (set.len > 0) { @@ -1917,22 +1919,22 @@ fn stringFindAnyRune(comptime T: cy.StringType) cy.NativeObjFuncPtr { return S.inner; } -pub fn stringIndexCode(comptime T: cy.StringType) cy.NativeObjFuncPtr { +pub fn stringIndexCode(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { + fn inner(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { fmt.printDeprecated("string.indexCode()", "0.2", "Use string.findRune() instead.", &.{}); - return @call(.never_inline, stringFindRune(T), .{vm, recv, args, nargs}); + return @call(.never_inline, stringFindRune(T), .{vm, args, nargs}); } }; return S.inner; } -fn stringFindRune(comptime T: cy.StringType) cy.NativeObjFuncPtr { +fn stringFindRune(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const obj = getStringObject(T, recv); + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const obj = getStringObject(T, args[0]); const str = getStringSlice(T, vm, obj); - const needle = args[0].asInteger(); + const needle = args[1].asInteger(); if (needle > 0) { const code: u21 = @intCast(needle); @@ -1977,14 +1979,14 @@ fn stringFindRune(comptime T: cy.StringType) cy.NativeObjFuncPtr { return S.inner; } -fn stringIndexChar(comptime T: cy.StringType) cy.NativeObjFuncPtr { +fn stringIndexChar(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { - fn inner(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { fmt.printDeprecated("string.indexChar()", "0.2", "Use string.findRune() instead.", &.{}); - const needle = vm.valueToTempString(args[0]); + const needle = vm.valueToTempString(args[1]); if (needle.len > 0) { const cp = cy.string.utf8CodeAtNoCheck(needle, 0); - return @call(.never_inline, stringFindRune(T), .{vm, recv, &[_]Value{Value.initF64(@floatFromInt(cp))}, 1}); + return @call(.never_inline, stringFindRune(T), .{vm, &[_]Value{Value.initF64(@floatFromInt(cp))}, 1}); } return Value.None; } @@ -1992,14 +1994,14 @@ fn stringIndexChar(comptime T: cy.StringType) cy.NativeObjFuncPtr { return S.inner; } -pub fn fileStreamLines(vm: *cy.UserVM, recv: Value, _: [*]const Value, nargs: u8) linksection(StdSection) Value { - return fileStreamLines1(vm, recv, &[_]Value{ Value.initF64(@floatFromInt(4096)) }, nargs); +pub fn fileStreamLines(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(StdSection) Value { + return fileStreamLines1(vm, &[_]Value{ args[0], Value.initF64(@floatFromInt(4096)) }, nargs); } -pub fn fileStreamLines1(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(StdSection) Value { +pub fn fileStreamLines1(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(StdSection) Value { // Don't need to release obj since it's being returned. - const obj = recv.asHeapObject(); - const bufSize: u32 = @intFromFloat(args[0].asF64()); + const obj = args[0].asHeapObject(); + const bufSize: u32 = @intFromFloat(args[1].asF64()); var createReadBuf = true; if (obj.file.hasReadBuf) { if (bufSize != obj.file.readBufCap) { @@ -2017,11 +2019,11 @@ pub fn fileStreamLines1(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8 obj.file.readBufCap = @intCast(readBuf.len); obj.file.hasReadBuf = true; } - return recv; + return args[0]; } -pub fn dirWalk(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(StdSection) Value { - const obj = recv.asHeapObject(); +pub fn dirWalk(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(StdSection) Value { + const obj = args[0].asHeapObject(); if (obj.dir.iterable) { vm.retainObject(obj); return vm.allocDirIterator(@ptrCast(obj), true) catch fatal(); @@ -2030,8 +2032,8 @@ pub fn dirWalk(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksectio } } -pub fn dirIterator(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(StdSection) Value { - const obj = recv.asHeapObject(); +pub fn dirIterator(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(StdSection) Value { + const obj = args[0].asHeapObject(); if (obj.dir.iterable) { vm.retainObject(obj); return vm.allocDirIterator(@ptrCast(obj), false) catch fatal(); @@ -2040,22 +2042,22 @@ pub fn dirIterator(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linkse } } -pub fn fileIterator(_: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(StdSection) Value { +pub fn fileIterator(_: *cy.UserVM, args: [*]const Value, _: u8) linksection(StdSection) Value { // Don't need to release obj since it's being returned. - const obj = recv.asHeapObject(); + const obj = args[0].asHeapObject(); obj.file.curPos = 0; obj.file.readBufEnd = 0; - return recv; + return args[0]; } -pub fn fileSeekFromEnd(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(StdSection) Value { - const obj = recv.asHeapObject(); +pub fn fileSeekFromEnd(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(StdSection) Value { + const obj = args[0].asHeapObject(); if (obj.file.closed) { return prepareThrowSymbol(vm, .Closed); } - const numBytes = args[0].asInteger(); + const numBytes = args[1].asInteger(); if (numBytes > 0) { return prepareThrowSymbol(vm, .InvalidArgument); } @@ -2067,14 +2069,14 @@ pub fn fileSeekFromEnd(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) return Value.None; } -pub fn fileSeekFromCur(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(StdSection) Value { - const obj = recv.asHeapObject(); +pub fn fileSeekFromCur(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(StdSection) Value { + const obj = args[0].asHeapObject(); if (obj.file.closed) { return prepareThrowSymbol(vm, .Closed); } - const numBytes = args[0].asInteger(); + const numBytes = args[1].asInteger(); const file = obj.file.getStdFile(); file.seekBy(numBytes) catch |err| { @@ -2083,14 +2085,14 @@ pub fn fileSeekFromCur(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) return Value.None; } -pub fn fileSeek(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(StdSection) Value { - const obj = recv.asHeapObject(); +pub fn fileSeek(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(StdSection) Value { + const obj = args[0].asHeapObject(); if (obj.file.closed) { return prepareThrowSymbol(vm, .Closed); } - const numBytes = args[0].asInteger(); + const numBytes = args[1].asInteger(); if (numBytes < 0) { return prepareThrowSymbol(vm, .InvalidArgument); } @@ -2103,14 +2105,14 @@ pub fn fileSeek(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linkse return Value.None; } -pub fn fileWrite(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(StdSection) Value { - const obj = recv.asHeapObject(); +pub fn fileWrite(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(StdSection) Value { + const obj = args[0].asHeapObject(); if (obj.file.closed) { return prepareThrowSymbol(vm, .Closed); } - var buf = vm.valueToTempRawString(args[0]); + var buf = vm.valueToTempRawString(args[1]); const file = obj.file.getStdFile(); const numWritten = file.write(buf) catch |err| { return fromUnsupportedError(vm, "write", err, @errorReturnTrace()); @@ -2119,20 +2121,20 @@ pub fn fileWrite(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) links return Value.initInt(@intCast(numWritten)); } -pub fn fileClose(_: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(StdSection) Value { - const obj = recv.asHeapObject(); +pub fn fileClose(_: *cy.UserVM, args: [*]const Value, _: u8) linksection(StdSection) Value { + const obj = args[0].asHeapObject(); obj.file.close(); return Value.None; } -pub fn fileRead(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(StdSection) Value { - const obj = recv.asHeapObject(); +pub fn fileRead(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(StdSection) Value { + const obj = args[0].asHeapObject(); if (obj.file.closed) { return prepareThrowSymbol(vm, .Closed); } - const numBytes = args[0].asInteger(); + const numBytes = args[1].asInteger(); if (numBytes <= 0) { return prepareThrowSymbol(vm, .InvalidArgument); } @@ -2153,8 +2155,8 @@ pub fn fileRead(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linkse return vm.allocRawString(tempBuf.buf[0..numRead]) catch fatal(); } -pub fn fileReadToEnd(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(StdSection) Value { - const obj = recv.asHeapObject(); +pub fn fileReadToEnd(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(StdSection) Value { + const obj = args[0].asHeapObject(); if (obj.file.closed) { return prepareThrowSymbol(vm, .Closed); @@ -2188,8 +2190,8 @@ pub fn fileReadToEnd(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) link } } -pub fn fileOrDirStat(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(StdSection) Value { - const obj = recv.asHeapObject(); +pub fn fileOrDirStat(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(StdSection) Value { + const obj = args[0].asHeapObject(); if (obj.getTypeId() == rt.FileT) { if (obj.file.closed) { @@ -2238,13 +2240,13 @@ pub fn fileOrDirStat(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) link return map; } -pub fn metatypeId(_: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(StdSection) Value { - const obj = recv.asHeapObject(); +pub fn metatypeId(_: *cy.UserVM, args: [*]const Value, _: u8) linksection(StdSection) Value { + const obj = args[0].asHeapObject(); return Value.initInt(obj.metatype.symId); } -pub fn dirIteratorNext(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(StdSection) Value { - const obj = recv.asHeapObject(); +pub fn dirIteratorNext(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(StdSection) Value { + const obj = args[0].asHeapObject(); const ivm = vm.internal(); const iter: *cy.DirIterator = @ptrCast(obj); @@ -2310,8 +2312,8 @@ pub fn dirIteratorNext(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) li } } -pub fn fileNext(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(StdSection) Value { - const obj = recv.asHeapObject(); +pub fn fileNext(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(StdSection) Value { + const obj = args[0].asHeapObject(); if (obj.file.iterLines) { const alloc = vm.allocator(); const readBuf = obj.file.readBuf[0..obj.file.readBufCap]; @@ -2432,21 +2434,21 @@ inline fn inlineUnaryOp(pc: [*]cy.Inst, code: cy.OpCode) void { pc[2].val = startLocal; } -pub fn intBitwiseNot(_: *cy.UserVM, pc: [*]cy.Inst, _: Value, _: [*]const Value, _: u8) void { +pub fn intBitwiseNot(_: *cy.UserVM, pc: [*]cy.Inst, _: [*]const Value, _: u8) void { inlineUnaryOp(pc, .bitwiseNot); } -pub fn intNeg(_: *cy.UserVM, pc: [*]cy.Inst, _: Value, _: [*]const Value, _: u8) void { +pub fn intNeg(_: *cy.UserVM, pc: [*]cy.Inst, _: [*]const Value, _: u8) void { inlineUnaryOp(pc, .negInt); } -pub fn floatNeg(_: *cy.UserVM, pc: [*]cy.Inst, _: Value, _: [*]const Value, _: u8) void { +pub fn floatNeg(_: *cy.UserVM, pc: [*]cy.Inst, _: [*]const Value, _: u8) void { inlineUnaryOp(pc, .negFloat); } -fn inlineTernNoRetOp(comptime code: cy.OpCode) cy.OptimizingNativeMethod { +fn inlineTernNoRetOp(comptime code: cy.OpCode) cy.OptimizingFuncFn { const S = struct { - pub fn method(_: *cy.UserVM, pc: [*]cy.Inst, _: Value, _: [*]const Value, _: u8) void { + pub fn method(_: *cy.UserVM, pc: [*]cy.Inst, _: [*]const Value, _: u8) void { const startLocal = pc[1].val; // Save callObjSym data. pc[8].val = startLocal; @@ -2462,9 +2464,9 @@ fn inlineTernNoRetOp(comptime code: cy.OpCode) cy.OptimizingNativeMethod { return S.method; } -fn inlineBinOp(comptime code: cy.OpCode) cy.OptimizingNativeMethod { +fn inlineBinOp(comptime code: cy.OpCode) cy.OptimizingFuncFn { const S = struct { - pub fn method(_: *cy.UserVM, pc: [*]cy.Inst, _: Value, _: [*]const Value, _: u8) void { + pub fn method(_: *cy.UserVM, pc: [*]cy.Inst, _: [*]const Value, _: u8) void { const startLocal = pc[1].val; // Save callObjSym data. pc[8].val = startLocal; @@ -2525,38 +2527,7 @@ pub fn errorCall(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.Std } } -pub fn nop0(vm: *cy.UserVM, _: [*]const Value, _: u8) linksection(cy.StdSection) Value { - return vm.returnPanic("Unsupported."); -} - -pub fn nop1(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - vm.release(args[0]); - return vm.returnPanic("Unsupported."); -} - -pub fn nop2(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - vm.release(args[0]); - vm.release(args[1]); - return vm.returnPanic("Unsupported."); -} - -pub fn nop3(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - vm.release(args[0]); - vm.release(args[1]); - vm.release(args[2]); - return vm.returnPanic("Unsupported."); -} - -pub fn objNop0(vm: *cy.UserVM, recv: Value, _: [*]const Value, _: u8) linksection(StdSection) Value { - const obj = recv.asHeapObject(); - vm.releaseObject(obj); - return vm.returnPanic("Unsupported."); -} - -pub fn objNop1(vm: *cy.UserVM, recv: Value, args: [*]const Value, _: u8) linksection(StdSection) Value { - const obj = recv.asHeapObject(); - vm.releaseObject(obj); - vm.release(args[0]); +pub fn nop(vm: *cy.UserVM, _: [*]const Value, _: u8) linksection(cy.StdSection) Value { return vm.returnPanic("Unsupported."); } @@ -2590,7 +2561,7 @@ pub const ModuleBuilder = struct { try self.mod().setTypedVar(self.compiler, name, typeSymId, val); } - pub fn setFunc(self: *const ModuleBuilder, name: []const u8, params: []const sema.SymbolId, ret: sema.SymbolId, ptr: cy.NativeFuncPtr) !void { + pub fn setFunc(self: *const ModuleBuilder, name: []const u8, params: []const sema.SymbolId, ret: sema.SymbolId, ptr: cy.ZHostFuncFn) !void { try self.mod().setNativeTypedFunc(self.compiler, name, params, ret, ptr); } @@ -2600,7 +2571,7 @@ pub const ModuleBuilder = struct { pub fn addOptimizingMethod( self: *const ModuleBuilder, typeId: rt.TypeId, mgId: vmc.MethodGroupId, - params: []const types.TypeId, ret: types.TypeId, ptr: cy.OptimizingNativeMethod + params: []const types.TypeId, ret: types.TypeId, ptr: cy.OptimizingFuncFn, ) !void { const funcSigId = try sema.ensureFuncSig(self.compiler, params, ret); const funcSig = self.compiler.sema.getFuncSig(funcSigId); @@ -2612,7 +2583,7 @@ pub const ModuleBuilder = struct { pub fn addMethod( self: *const ModuleBuilder, typeId: rt.TypeId, mgId: vmc.MethodGroupId, - params: []const types.TypeId, ret: types.TypeId, ptr: cy.NativeObjFuncPtr + params: []const types.TypeId, ret: types.TypeId, ptr: cy.ZHostFuncFn, ) !void { const funcSigId = try sema.ensureFuncSig(self.compiler, params, ret); const funcSig = self.compiler.sema.getFuncSig(funcSigId); @@ -2625,7 +2596,7 @@ pub const ModuleBuilder = struct { pub fn addMethod2( self: *const ModuleBuilder, typeId: rt.TypeId, mgId: vmc.MethodGroupId, - params: []const types.TypeId, ret: types.TypeId, ptr: cy.NativeObjFunc2Ptr + params: []const types.TypeId, ret: types.TypeId, ptr: cy.ZHostFuncPairFn, ) !void { const funcSigId = try sema.ensureFuncSig(self.compiler, params, ret); const funcSig = self.compiler.sema.getFuncSig(funcSigId); diff --git a/src/builtins/builtins.cy b/src/builtins/builtins.cy new file mode 100644 index 000000000..d47c40dd1 --- /dev/null +++ b/src/builtins/builtins.cy @@ -0,0 +1,22 @@ +hostfunc arrayFill(val any, n int) List +hostfunc asciiCode(val any) any +hostfunc bool(val any) boolean +hostfunc char(val any) any +hostfunc copy(val any) any +hostfunc errorReport() string +hostfunc isAlpha(val int) boolean +hostfunc isDigit(val int) boolean +hostfunc must(val any) any +hostfunc opaque(val any) pointer +hostfunc panic(err any) none +hostfunc parseCyber(src any) Map +hostfunc parseCyon(src any) any +hostfunc performGC() Map +hostfunc print(str any) none +hostfunc prints(str any) none +hostfunc runestr(val int) string +hostfunc toCyon(val any) string +hostfunc typeid(val any) int +hostfunc valtag(val any) symbol +hostfunc typesym(val any) symbol +hostfunc typeof(val any) metatype \ No newline at end of file diff --git a/src/builtins/core.zig b/src/builtins/builtins.zig similarity index 60% rename from src/builtins/core.zig rename to src/builtins/builtins.zig index 60f99939b..d2db2c3e7 100644 --- a/src/builtins/core.zig +++ b/src/builtins/builtins.zig @@ -9,82 +9,52 @@ const bindings = @import("bindings.zig"); const Symbol = bindings.Symbol; const prepareThrowSymbol = bindings.prepareThrowSymbol; const fmt = @import("../fmt.zig"); -const os_mod = @import("os.zig"); -const http = @import("../http.zig"); -const cache = @import("../cache.zig"); const bt = cy.types.BuiltinTypeSymIds; +const vmc = cy.vmc; const log = cy.log.scoped(.core); -pub fn initModule(self: *cy.VMcompiler, modId: cy.ModuleId) anyerror!void { - const b = bindings.ModuleBuilder.init(self, modId); - try b.mod().syms.ensureTotalCapacity(self.alloc, 13); - - // Funcs. - try b.setFunc("arrayFill", &.{bt.Any, bt.Integer}, bt.List, arrayFill); - try b.setFunc("asciiCode", &.{bt.Any}, bt.Any, asciiCode); - if (cy.hasJit) { - try b.setFunc("bindLib", &.{bt.Any, bt.List}, bt.Any, bindLib); - try b.setFunc("bindLib", &.{bt.Any, bt.List, bt.Map}, bt.Any, bindLibExt); - } else { - try b.setFunc("bindLib", &.{bt.Any, bt.List}, bt.Any, bindings.nop2); - try b.setFunc("bindLib", &.{bt.Any, bt.List, bt.Map}, bt.Any, bindings.nop3); - } - try b.setFunc("bool", &.{ bt.Any }, bt.Boolean, coreBool); - if (cy.isWasm) { - try b.setFunc("cacheUrl", &.{ bt.Any }, bt.Any, bindings.nop1); - } else { - try b.setFunc("cacheUrl", &.{ bt.Any }, bt.Any, cacheUrl); +pub const Src = @embedFile("builtins.cy"); +pub fn defaultFuncLoader(_: *cy.UserVM, func: cy.HostFuncInfo) callconv(.C) vmc.HostFuncFn { + if (std.mem.eql(u8, funcs[func.idx].@"0", func.name.slice())) { + return @ptrCast(funcs[func.idx].@"1"); } - try b.setFunc("char", &.{bt.Any}, bt.Any, char); - try b.setFunc("copy", &.{bt.Any}, bt.Any, copy); - try b.setFunc("errorReport", &.{}, bt.String, errorReport); + return null; +} + +pub fn postLoad(vm: *cy.UserVM, modId: cy.ModuleId) callconv(.C) void { + const b = bindings.ModuleBuilder.init(vm.internal().compiler, modId); if (cy.isWasm) { - try b.setFunc("evalJS", &.{bt.Any}, bt.None, evalJS); - try b.setFunc("execCmd", &.{bt.List}, bt.Any, bindings.nop1); - } else { - try b.setFunc("execCmd", &.{bt.List}, bt.Any, execCmd); - } - try b.setFunc("exit", &.{bt.Integer}, bt.None, exit); - try b.setFunc("fetchUrl", &.{bt.Any}, bt.Any, fetchUrl); - if (cy.hasStdFiles) { - try b.setFunc("getInput", &.{}, bt.Any, getInput); - } else { - try b.setFunc("getInput", &.{}, bt.Any, bindings.nop0); - } - try b.setFunc("isAlpha", &.{ bt.Integer }, bt.Boolean, isAlpha); - try b.setFunc("isDigit", &.{ bt.Integer }, bt.Boolean, isDigit); - // try mod.setNativeFunc(alloc, "dump", 1, dump); - try b.setFunc("must", &.{ bt.Any }, bt.Any, must); - try b.setFunc("opaque", &.{ bt.Any }, bt.Pointer, coreOpaque); - try b.setFunc("panic", &.{ bt.Any }, bt.None, panic); - try b.setFunc("parseCyber", &.{ bt.Any }, bt.Map, parseCyber); - try b.setFunc("parseCyon", &.{ bt.Any }, bt.Any, parseCyon); - try b.setFunc("performGC", &.{}, bt.Map, performGC); - try b.setFunc("print", &.{bt.Any}, bt.None, print); - try b.setFunc("prints", &.{bt.Any}, bt.None, prints); - if (cy.hasStdFiles) { - try b.setFunc("readAll", &.{}, bt.Any, readAll); - try b.setFunc("readFile", &.{ bt.Any }, bt.Any, readFile); - try b.setFunc("readLine", &.{}, bt.Any, readLine); - } else { - try b.setFunc("readAll", &.{}, bt.Any, bindings.nop0); - try b.setFunc("readFile", &.{ bt.Any }, bt.Any, bindings.nop1); - try b.setFunc("readLine", &.{}, bt.Any, bindings.nop0); - } - try b.setFunc("runestr", &.{ bt.Integer }, bt.String, runestr); - try b.setFunc("toCyon", &.{ bt.Any }, bt.String, toCyon); - try b.setFunc("typeid", &.{ bt.Any }, bt.Integer, typeid); - try b.setFunc("valtag", &.{ bt.Any }, bt.Symbol, valtag); - try b.setFunc("typesym", &.{ bt.Any }, bt.Symbol, typesym); - try b.setFunc("typeof", &.{ bt.Any }, bt.MetaType, typeof); - if (cy.hasStdFiles) { - try b.setFunc("writeFile", &.{ bt.Any, bt.Any }, bt.Any, writeFile); - } else { - try b.setFunc("writeFile", &.{ bt.Any, bt.Any }, bt.Any, bindings.nop2); + b.setFunc("evalJS", &.{bt.Any}, bt.None, evalJS) catch cy.fatal(); } } +const NameHostFunc = struct { []const u8, cy.ZHostFuncFn }; +const funcs = [_]NameHostFunc{ + .{"arrayFill", arrayFill}, + .{"asciiCode", asciiCode}, + .{"bool", btBool}, + .{"char", char}, + .{"copy", copy}, + .{"errorReport", errorReport}, + .{"isAlpha", isAlpha}, + .{"isDigit", isDigit}, + .{"must", must}, + .{"opaque", btOpaque}, + .{"panic", panic}, + .{"parseCyber", parseCyber}, + .{"parseCyon", parseCyon}, + .{"performGC", performGC}, + .{"print", print}, + .{"prints", prints}, + .{"runestr", runestr}, + .{"toCyon", toCyon}, + .{"typeid", typeid}, + .{"valtag", valtag}, + .{"typesym", typesym}, + .{"typeof", typeof}, +}; + pub fn arrayFill(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { return vm.allocListFill(args[0], @intCast(args[1].asInteger())) catch cy.fatal(); } @@ -99,52 +69,7 @@ pub fn asciiCode(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.Std } } -fn cacheUrl(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const alloc = vm.allocator(); - const url = vm.valueToTempString(args[0]); - - const specGroup = cache.getSpecHashGroup(alloc, url) catch cy.fatal(); - defer specGroup.deinit(alloc); - - if (vm.internal().config.reload) { - specGroup.markEntryBySpecForRemoval(url) catch cy.fatal(); - } else { - // First check local cache. - if (specGroup.findEntryBySpec(url) catch cy.fatal()) |entry| { - const path = cache.allocSpecFilePath(alloc, entry) catch cy.fatal(); - defer alloc.free(path); - return vm.allocStringInfer(path) catch cy.fatal(); - } - } - - const resp = http.get(alloc, vm.internal().httpClient, url) catch |err| { - log.debug("cacheUrl error: {}", .{err}); - return prepareThrowSymbol(vm, .UnknownError); - }; - defer alloc.free(resp.body); - if (resp.status != .ok) { - log.debug("cacheUrl response status: {}", .{resp.status}); - return prepareThrowSymbol(vm, .UnknownError); - } else { - const entry = cache.saveNewSpecFile(alloc, specGroup, url, resp.body) catch cy.fatal(); - defer entry.deinit(alloc); - const path = cache.allocSpecFilePath(alloc, entry) catch cy.fatal(); - defer alloc.free(path); - return vm.allocStringInfer(path) catch cy.fatal(); - } -} - -pub fn bindLib(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { - fmt.printDeprecated("bindLib", "0.1", "Use os.bindLib() instead.", &.{}); - return os_mod.bindLib(vm, args, nargs); -} - -pub fn bindLibExt(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { - fmt.printDeprecated("bindLib", "0.1", "Use os.bindLib() instead.", &.{}); - return os_mod.bindLibExt(vm, args, nargs); -} - -pub fn coreBool(vm: *cy.UserVM, args: [*]const Value, nargs: u8) Value { +pub fn btBool(vm: *cy.UserVM, args: [*]const Value, nargs: u8) Value { fmt.printDeprecated("bool", "0.2", "Use boolean() instead.", &.{}); return bindings.booleanCall(vm, args, nargs); } @@ -156,7 +81,7 @@ pub fn char(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdS pub fn copy(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { const val = args[0]; - return cy.value.shallowCopy(@ptrCast(vm), val); + return cy.value.shallowCopy(@ptrCast(@alignCast(vm)), val); } pub fn errorReport(vm: *cy.UserVM, _: [*]const Value, _: u8) linksection(cy.StdSection) Value { @@ -187,99 +112,6 @@ pub fn errorReport(vm: *cy.UserVM, _: [*]const Value, _: u8) linksection(cy.StdS return vm.allocStringInfer(buf.items) catch fatal(); } -pub fn execCmd(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const alloc = vm.allocator(); - const ivm = vm.internal(); - - const obj = args[0].asHeapObject(); - var buf: std.ArrayListUnmanaged([]const u8) = .{}; - defer { - for (buf.items) |arg| { - alloc.free(arg); - } - buf.deinit(alloc); - } - for (obj.list.items()) |arg| { - buf.append(alloc, vm.valueToString(arg) catch cy.fatal()) catch cy.fatal(); - } - - const res = std.ChildProcess.exec(.{ - .allocator = alloc, - .argv = buf.items, - .max_output_bytes = 1024 * 1024 * 10, - }) catch |err| { - switch (err) { - error.FileNotFound => - return prepareThrowSymbol(vm, .FileNotFound), - error.StdoutStreamTooLong => - return prepareThrowSymbol(vm, .StreamTooLong), - error.StderrStreamTooLong => - return prepareThrowSymbol(vm, .StreamTooLong), - else => cy.panicFmt("exec err {}\n", .{err}), - } - }; - - const map = vm.allocEmptyMap() catch cy.fatal(); - const outKey = vm.allocAstring("out") catch cy.fatal(); - const errKey = vm.allocAstring("err") catch cy.fatal(); - defer { - vm.release(outKey); - vm.release(errKey); - } - - // TODO: Use allocOwnedString - defer alloc.free(res.stdout); - const out = vm.allocStringInfer(res.stdout) catch cy.fatal(); - defer vm.release(out); - map.asHeapObject().map.set(ivm, outKey, out) catch cy.fatal(); - // TODO: Use allocOwnedString - defer alloc.free(res.stderr); - const err = vm.allocStringInfer(res.stderr) catch cy.fatal(); - defer vm.release(err); - map.asHeapObject().map.set(ivm, errKey, err) catch cy.fatal(); - if (res.term == .Exited) { - const exitedKey = vm.allocAstring("exited") catch cy.fatal(); - defer vm.release(exitedKey); - map.asHeapObject().map.set(ivm, exitedKey, Value.initF64(@floatFromInt(res.term.Exited))) catch cy.fatal(); - } - return map; -} - -pub fn exit(_: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const status: u8 = @intCast(args[0].asInteger()); - std.os.exit(status); -} - -pub fn fetchUrl(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { - const alloc = vm.allocator(); - const url = vm.valueToTempString(args[0]); - if (cy.isWasm) { - hostFetchUrl(url.ptr, url.len); - return Value.None; - } else { - const resp = http.get(alloc, vm.internal().httpClient, url) catch |err| { - log.debug("fetchUrl error: {}", .{err}); - return prepareThrowSymbol(vm, .UnknownError); - }; - defer alloc.free(resp.body); - // TODO: Use allocOwnedString - return vm.allocRawString(resp.body) catch cy.fatal(); - } -} - -extern fn hostFetchUrl(url: [*]const u8, urlLen: usize) void; - -pub fn getInput(vm: *cy.UserVM, _: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const input = std.io.getStdIn().reader().readUntilDelimiterAlloc(vm.allocator(), '\n', 10e8) catch |err| { - if (err == error.EndOfStream) { - return prepareThrowSymbol(vm, .EndOfStream); - } else cy.fatal(); - }; - defer vm.allocator().free(input); - // TODO: Use allocOwnedString - return vm.allocRawString(input) catch cy.fatal(); -} - pub fn must(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { if (!args[0].isError()) { return args[0]; @@ -288,7 +120,7 @@ pub fn must(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdS } } -pub fn coreOpaque(vm: *cy.UserVM, args: [*]const Value, nargs: u8) Value { +pub fn btOpaque(vm: *cy.UserVM, args: [*]const Value, nargs: u8) Value { fmt.printDeprecated("opaque", "0.1", "Use pointer() instead.", &.{}); return bindings.pointerCall(vm, args, nargs); } @@ -298,7 +130,7 @@ pub fn panic(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSect return vm.returnPanic(str); } -fn isAlpha(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { +pub fn isAlpha(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { const num = args[0].asInteger(); if (num < 0 or num >= 2 << 21) { return prepareThrowSymbol(vm, .InvalidRune); @@ -310,7 +142,7 @@ fn isAlpha(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSectio } } -fn isDigit(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { +pub fn isDigit(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { const num = args[0].asInteger(); if (num < 0 or num >= 2 << 21) { return prepareThrowSymbol(vm, .InvalidRune); @@ -322,7 +154,7 @@ fn isDigit(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSectio } } -fn runestr(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { +pub fn runestr(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { const num = args[0].asInteger(); if (num < 0 or num >= 2 << 21) { return prepareThrowSymbol(vm, .InvalidRune); @@ -445,7 +277,7 @@ pub fn toCyon(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSec return vm.allocStringInfer(cyon) catch fatal(); } -fn parseCyber(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { +pub fn parseCyber(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { const src = vm.valueToTempRawString(args[0]); const alloc = vm.allocator(); @@ -500,6 +332,12 @@ fn parseCyberGenResult(vm: *cy.UserVM, parser: *const cy.Parser, res: cy.ParseRe name = res.getFirstNodeString(header.head.funcHeader.name); pos = res.tokens[node.start_token].pos(); }, + .hostFunc => { + const node = nodes[decl.inner.hostFunc]; + const header = nodes[node.head.func.header]; + name = res.getFirstNodeString(header.head.funcHeader.name); + pos = res.tokens[node.start_token].pos(); + }, .import => { const node = nodes[decl.inner.import]; name = res.getFirstNodeString(node.head.left_right.left); @@ -601,67 +439,28 @@ pub fn performGC(vm: *cy.UserVM, _: [*]const Value, _: u8) linksection(cy.StdSec pub fn print(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { const str = vm.valueToTempRawString(args[0]); - if (cy.isWasmFreestanding) { - hostFileWrite(1, str.ptr, str.len); - hostFileWrite(1, "\n", 1); - } else { - const w = std.io.getStdOut().writer(); - w.writeAll(str) catch cy.fatal(); - w.writeByte('\n') catch cy.fatal(); - } + vm.internal().print(vm, cy.Str.initSlice(str)); + vm.internal().print(vm, cy.Str.initSlice("\n")); return Value.None; } -pub extern fn hostFileWrite(fid: u32, str: [*]const u8, strLen: usize) void; - pub fn prints(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { const str = vm.valueToTempRawString(args[0]); - if (cy.isWasm) { - hostFileWrite(1, str.ptr, str.len); - } else { - const w = std.io.getStdOut().writer(); - w.writeAll(str) catch cy.fatal(); - } + vm.internal().print(vm, cy.Str.initSlice(str)); return Value.None; } -pub fn readAll(vm: *cy.UserVM, _: [*]const Value, _: u8) Value { - const input = std.io.getStdIn().readToEndAlloc(vm.allocator(), 10e8) catch cy.fatal(); - defer vm.allocator().free(input); - // TODO: Use allocOwnString. - return vm.allocRawString(input) catch cy.fatal(); -} - -pub fn readFile(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { - const path = vm.valueToTempRawString(args[0]); - const content = std.fs.cwd().readFileAlloc(vm.allocator(), path, 10e8) catch |err| { - if (err == error.FileNotFound) { - return prepareThrowSymbol(vm, .FileNotFound); - } - log.debug("readFile {}", .{err}); - return prepareThrowSymbol(vm, .UnknownError); - }; - defer vm.allocator().free(content); - // TODO: Use allocOwnedString. - return vm.allocRawString(content) catch cy.fatal(); -} - -pub fn readLine(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { - fmt.printDeprecated("readLine", "0.1", "Use getInput() instead.", &.{}); - return getInput(vm, args, nargs); -} - pub fn typeid(_: *cy.UserVM, args: [*]const Value, _: u8) Value { fmt.printDeprecated("typeid", "0.2", "Use metatype.id() instead.", &.{}); return Value.initInt(@intCast(args[0].getTypeId())); } -fn valtag(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { +pub fn valtag(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { fmt.printDeprecated("valtag", "0.2", "Use typesym() instead.", &.{}); return typesym(vm, args, nargs); } -fn typeof(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { +pub fn typeof(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { const val = args[0]; const typeId = val.getTypeId(); return cy.heap.allocMetaType(vm.internal(), @intFromEnum(cy.heap.MetaTypeKind.object), typeId) catch fatal(); @@ -691,15 +490,6 @@ pub fn typesym(_: *cy.UserVM, args: [*]const Value, _: u8) Value { } } -pub fn writeFile(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const path = vm.valueToTempRawString(args[0]); - const pathDupe = vm.allocator().dupe(u8, path) catch fatal(); - defer vm.allocator().free(pathDupe); - const content = vm.valueToTempRawString(args[1]); - std.fs.cwd().writeFile(path, content) catch cy.fatal(); - return Value.None; -} - pub fn evalJS(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { const str = vm.valueToTempString(args[0]); hostEvalJS(str.ptr, str.len); diff --git a/src/builtins/math.cy b/src/builtins/math.cy new file mode 100644 index 000000000..b5a2409a1 --- /dev/null +++ b/src/builtins/math.cy @@ -0,0 +1,36 @@ +hostfunc abs(a float) float +hostfunc acos(a float) float +hostfunc acosh(a float) float +hostfunc asin(a float) float +hostfunc asinh(a float) float +hostfunc atan(a float) float +hostfunc atan2(a float, b float) float +hostfunc atanh(a float) float +hostfunc cbrt(a float) float +hostfunc ceil(a float) float +hostfunc clz32(a float) float +hostfunc cos(a float) float +hostfunc cosh(a float) float +hostfunc exp(a float) float +hostfunc expm1(a float) float +hostfunc floor(a float) float +hostfunc hypot(a float, b float) float +hostfunc isNaN(a float) boolean +hostfunc ln(a float) float +hostfunc log(a float, b float) float +hostfunc log10(a float) float +hostfunc log1p(a float) float +hostfunc log2(a float) float +hostfunc max(a float, b float) float +hostfunc min(a float, b float) float +hostfunc mul32(a float, b float) float +hostfunc pow(a float, b float) float +hostfunc random() float +hostfunc round(a float) float +hostfunc sign(a float) float +hostfunc sin(a float) float +hostfunc sinh(a float) float +hostfunc sqrt(a float) float +hostfunc tan(a float) float +hostfunc tanh(a float) float +hostfunc trunc(a float) float \ No newline at end of file diff --git a/src/builtins/math.zig b/src/builtins/math.zig index e92a0da86..a8024fc8d 100644 --- a/src/builtins/math.zig +++ b/src/builtins/math.zig @@ -1,9 +1,64 @@ const std = @import("std"); const cy = @import("../cyber.zig"); +const vmc = cy.vmc; const Value = cy.Value; const bt = cy.types.BuiltinTypeSymIds; -pub fn initModule(c: *cy.VMcompiler, modId: cy.ModuleId) anyerror!void { +pub const Src = @embedFile("math.cy"); +pub fn defaultFuncLoader(_: *cy.UserVM, func: cy.HostFuncInfo) callconv(.C) vmc.HostFuncFn { + if (std.mem.eql(u8, funcs[func.idx].@"0", func.name.slice())) { + return @ptrCast(funcs[func.idx].@"1"); + } + return null; +} + +const NameHostFunc = struct { []const u8, cy.ZHostFuncFn }; +const funcs = [_]NameHostFunc{ + .{"abs", abs}, + .{"acos", acos}, + .{"acosh", acosh}, + .{"asin", asin}, + .{"asinh", asinh}, + .{"atan", atan}, + .{"atan2", atan2}, + .{"atanh", atanh}, + .{"cbrt", cbrt}, + .{"ceil", ceil}, + .{"clz32", clz32}, + .{"cos", cos}, + .{"cosh", cosh}, + .{"exp", exp}, + .{"expm1", expm1}, + .{"floor", floor}, + .{"hypot", hypot}, + .{"isNaN", isNaN}, + .{"ln", ln}, + .{"log", log}, + .{"log10", log10}, + .{"log1p", log1p}, + .{"log2", log2}, + .{"max", max}, + .{"min", min}, + .{"mul32", mul32}, + .{"pow", pow}, + .{"random", random}, + .{"round", round}, + .{"sign", sign}, + .{"sin", sin}, + .{"sinh", sinh}, + .{"sqrt", sqrt}, + .{"tan", tan}, + .{"tanh", tanh}, + .{"trunc", trunc}, +}; + +pub fn postLoad(vm: *cy.UserVM, modId: cy.ModuleId) callconv(.C) void { + zPostLoad(vm.internal().compiler, modId) catch |err| { + cy.panicFmt("math module: {}", .{err}); + }; +} + +fn zPostLoad(c: *cy.VMcompiler, modId: cy.ModuleId) linksection(cy.InitSection) anyerror!void { const b = cy.bindings.ModuleBuilder.init(c, modId); // Euler's number and the base of natural logarithms; approximately 2.718. @@ -38,46 +93,6 @@ pub fn initModule(c: *cy.VMcompiler, modId: cy.ModuleId) anyerror!void { // Square root of 2; approximately 1.414. try b.setVar("sqrt2", bt.Float, Value.initF64(std.math.sqrt2)); - - const num1: []const cy.sema.SymbolId = &.{bt.Float}; - const num2: []const cy.sema.SymbolId = &.{bt.Float, bt.Float}; - - try b.setFunc("abs", num1, bt.Float, abs); - try b.setFunc("acos", num1, bt.Float, acos); - try b.setFunc("acosh", num1, bt.Float, acosh); - try b.setFunc("asin", num1, bt.Float, asin); - try b.setFunc("asinh", num1, bt.Float, asinh); - try b.setFunc("atan", num1, bt.Float, atan); - try b.setFunc("atan2", num2, bt.Float, atan2); - try b.setFunc("atanh", num1, bt.Float, atanh); - try b.setFunc("cbrt", num1, bt.Float, cbrt); - try b.setFunc("ceil", num1, bt.Float, ceil); - try b.setFunc("clz32", num1, bt.Float, clz32); - try b.setFunc("cos", num1, bt.Float, cos); - try b.setFunc("cosh", num1, bt.Float, cosh); - try b.setFunc("exp", num1, bt.Float, exp); - try b.setFunc("expm1", num1, bt.Float, expm1); - try b.setFunc("floor", num1, bt.Float, floor); - try b.setFunc("hypot", num2, bt.Float, hypot); - try b.setFunc("isNaN", num1, bt.Float, isNaN); - try b.setFunc("ln", num1, bt.Float, ln); - try b.setFunc("log", num2, bt.Float, log); - try b.setFunc("log10", num1, bt.Float, log10); - try b.setFunc("log1p", num1, bt.Float, log1p); - try b.setFunc("log2", num1, bt.Float, log2); - try b.setFunc("max", num2, bt.Float, max); - try b.setFunc("min", num2, bt.Float, min); - try b.setFunc("mul32", num2, bt.Float, mul32); - try b.setFunc("pow", num2, bt.Float, pow); - try b.setFunc("random", &.{}, bt.Float, random); - try b.setFunc("round", num1, bt.Float, round); - try b.setFunc("sign", num1, bt.Float, sign); - try b.setFunc("sin", num1, bt.Float, sin); - try b.setFunc("sinh", num1, bt.Float, sinh); - try b.setFunc("sqrt", num1, bt.Float, sqrt); - try b.setFunc("tan", num1, bt.Float, tan); - try b.setFunc("tanh", num1, bt.Float, tanh); - try b.setFunc("trunc", num1, bt.Float, trunc); } /// Returns the absolute value of x. diff --git a/src/chunk.zig b/src/chunk.zig index 4880dc420..452270319 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -85,6 +85,9 @@ pub const Chunk = struct { /// It's useful to store imports, importAlls that are only visible to the module. localSyms: std.HashMapUnmanaged(sema.LocalSymKey, sema.LocalSym, cy.hash.KeyU64Context, 80), + /// Using modules. + usingModules: std.ArrayListUnmanaged(cy.ModuleId), + /// /// Codegen pass /// @@ -114,6 +117,15 @@ pub const Chunk = struct { /// Its exported members will be populated in the Module as sema encounters them. modId: cy.ModuleId, + /// For binding hostfunc declarations. + funcLoader: ?cy.HostFuncLoaderFn = null, + /// Run after declarations have been loaded. + postLoad: ?cy.PostLoadModuleFn = null, + /// Run before chunk is destroyed. + destroy: ?cy.ModuleDestroyFn = null, + /// Counter for loading hostfuncs. + curHostFuncIdx: u32, + pub fn init(c: *cy.VMcompiler, id: ChunkId, srcUri: []const u8, src: []const u8) !Chunk { var new = Chunk{ .id = id, @@ -159,6 +171,8 @@ pub const Chunk = struct { .semaFuncDecls = .{}, .localSyms = .{}, .rega = cy.register.Allocator.init(c, id), + .curHostFuncIdx = 0, + .usingModules = .{}, // .funcCandidateStack = .{}, }; try new.parser.tokens.ensureTotalCapacityPrecise(c.alloc, 511); @@ -205,6 +219,7 @@ pub const Chunk = struct { self.semaFuncDecls.deinit(self.alloc); self.localSyms.deinit(self.alloc); + self.usingModules.deinit(self.alloc); self.alloc.free(self.nodeTypes); // self.funcCandidateStack.deinit(self.alloc); } diff --git a/src/cli.zig b/src/cli.zig new file mode 100644 index 000000000..69bc3d8a4 --- /dev/null +++ b/src/cli.zig @@ -0,0 +1,243 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const cy = @import("cyber.zig"); +const bindings = @import("builtins/bindings.zig"); +const os_mod = @import("std/os.zig"); +const test_mod = @import("std/test.zig"); +const cache = @import("cache.zig"); +const bt = cy.types.BuiltinTypeSymIds; +const v = cy.fmt.v; +const log = cy.log.scoped(.cli); + +const builtins = std.ComptimeStringMap(void, .{ + .{"builtins"}, + .{"math"}, +}); + +const stdMods = std.ComptimeStringMap(cy.ModuleLoaderResult, .{ + .{"os", cy.ModuleLoaderResult{ + .src = cy.Str.initSlice(os_mod.Src), .srcIsStatic = true, + .funcLoader = os_mod.defaultFuncLoader, + .postLoad = os_mod.postLoad, + .destroy = os_mod.destroy, + }}, + .{"test", cy.ModuleLoaderResult{ + .src = cy.Str.initSlice(test_mod.Src), .srcIsStatic = true, + .funcLoader = test_mod.defaultFuncLoader, + .postLoad = test_mod.postLoad, + }}, +}); + +var defaultLoader: cy.ModuleLoaderFn = undefined; +pub fn setupVMForCLI(vm: *cy.UserVM) void { + vm.setModuleResolver(resolve); + defaultLoader = vm.getModuleLoader(); + vm.setModuleLoader(loader); + vm.setPrint(print); +} + +fn print(_: *cy.UserVM, str: cy.Str) callconv(.C) void { + if (cy.isWasmFreestanding) { + os_mod.hostFileWrite(1, str.buf, str.len); + } else { + const w = std.io.getStdOut().writer(); + w.writeAll(str.slice()) catch cy.fatal(); + } +} + +pub fn loader(uvm: *cy.UserVM, spec_: cy.Str, out: *cy.ModuleLoaderResult) callconv(.C) bool { + const spec = spec_.slice(); + if (builtins.get(spec) != null) { + return defaultLoader(uvm, spec_, out); + } + if (stdMods.get(spec)) |res| { + out.* = res; + return true; + } + + if (cy.isWasm) { + return false; + } + + const vm = uvm.internal(); + + // Load from file or http. + var src: []const u8 = undefined; + if (std.mem.startsWith(u8, spec, "http://") or std.mem.startsWith(u8, spec, "https://")) { + src = loadUrl(uvm, spec) catch |err| { + if (err == error.HandledError) { + return false; + } else { + const msg = cy.fmt.allocFormat(vm.alloc, "Can not load `{}`. {}", &.{v(spec), v(err)}) catch return false; + defer vm.alloc.free(msg); + uvm.setApiError(msg); + return false; + } + }; + } else { + src = std.fs.cwd().readFileAlloc(vm.alloc, spec, 1e10) catch |err| { + const msg = cy.fmt.allocFormat(vm.alloc, "Can not load `{}`. {}", &.{v(spec), v(err)}) catch return false; + defer vm.alloc.free(msg); + uvm.setApiError(msg); + return false; + }; + } + + out.* = .{ + .src = cy.Str.initSlice(src), + .srcIsStatic = false, + }; + return true; +} + +fn resolve(uvm: *cy.UserVM, chunkId: cy.ChunkId, curUri: cy.Str, spec_: cy.Str, outUri: *cy.Str) callconv(.C) bool { + const spec = spec_.slice(); + if (builtins.get(spec) != null) { + outUri.* = spec_; + return true; + } + if (stdMods.get(spec) != null) { + outUri.* = spec_; + return true; + } + + if (cy.isWasm) { + return false; + } + + const vm = uvm.internal(); + + const res = zResolve(uvm, chunkId, curUri.slice(), spec) catch |err| { + if (err == error.HandledError) { + return false; + } else { + const msg = cy.fmt.allocFormat(vm.alloc, "Resolve module `{}`, error: `{}`", &.{v(spec), v(err)}) catch return false; + defer vm.alloc.free(msg); + uvm.setApiError(msg); + return false; + } + }; + outUri.* = cy.Str.initSlice(res); + return true; +} + +fn zResolve(uvm: *cy.UserVM, chunkId: cy.ChunkId, curUri: []const u8, spec: []const u8) ![]const u8 { + const vm = uvm.internal(); + const chunk = &vm.compiler.chunks.items[chunkId]; + if (std.mem.startsWith(u8, spec, "http://") or std.mem.startsWith(u8, spec, "https://")) { + const uri = try std.Uri.parse(spec); + if (std.mem.endsWith(u8, uri.host.?, "github.com")) { + if (std.mem.count(u8, uri.path, "/") == 2 and uri.path[uri.path.len-1] != '/') { + chunk.tempBufU8.clearRetainingCapacity(); + try chunk.tempBufU8.appendSlice(vm.alloc, uri.scheme); + try chunk.tempBufU8.appendSlice(vm.alloc, "://raw.githubusercontent.com"); + try chunk.tempBufU8.appendSlice(vm.alloc, uri.path); + try chunk.tempBufU8.appendSlice(vm.alloc, "/master/mod.cy"); + std.debug.print("{s}\n", .{chunk.tempBufU8.items}); + return chunk.tempBufU8.items; + } + } + return spec; + } + + chunk.tempBufU8.clearRetainingCapacity(); + + // Create path from the current script. + // There should always be a parent directory since `curUri` should be absolute when dealing with file modules. + const dir = std.fs.path.dirname(curUri) orelse return error.NoParentDir; + try chunk.tempBufU8.ensureTotalCapacity(vm.alloc, dir.len + 1 + spec.len + std.fs.MAX_PATH_BYTES); + try chunk.tempBufU8.appendSlice(vm.alloc, dir); + try chunk.tempBufU8.append(vm.alloc, '/'); + try chunk.tempBufU8.appendSlice(vm.alloc, spec); + const path = chunk.tempBufU8.items; + + // Get canonical path. + chunk.tempBufU8.items.len += std.fs.MAX_PATH_BYTES; + const absPath = std.fs.cwd().realpath(path, chunk.tempBufU8.items[path.len..]) catch |err| { + if (err == error.FileNotFound) { + const msg = try cy.fmt.allocFormat(vm.alloc, "Import path does not exist: `{}`", &.{v(path)}); + defer vm.alloc.free(msg); + uvm.setApiError(msg); + return error.HandledError; + } else { + return err; + } + }; + return absPath; +} + +fn loadUrl(uvm: *cy.UserVM, url: []const u8) ![]const u8 { + const vm = uvm.internal(); + const specGroup = try cache.getSpecHashGroup(vm.alloc, url); + defer specGroup.deinit(vm.alloc); + + if (vm.config.reload) { + // Remove cache entry. + try specGroup.markEntryBySpecForRemoval(url); + } else { + // First check local cache. + if (try specGroup.findEntryBySpec(url)) |entry| { + var found = true; + const src = cache.allocSpecFileContents(vm.alloc, entry) catch |err| b: { + if (err == error.FileNotFound) { + // Fallthrough. + found = false; + break :b ""; + } else { + return err; + } + }; + if (found) { + log.debug("Using cached {s}", .{url}); + return src; + } + } + } + + const client = vm.httpClient; + + const uri = try std.Uri.parse(url); + var req = client.request(.GET, uri, .{ .allocator = vm.alloc }) catch |err| { + if (err == error.UnknownHostName) { + const msg = try cy.fmt.allocFormat(vm.alloc, "Can not connect to `{}`.", &.{v(uri.host.?)}); + defer vm.alloc.free(msg); + uvm.setApiError(msg); + return error.HandledError; + } else { + return err; + } + }; + defer client.deinitRequest(&req); + + try client.startRequest(&req); + try client.waitRequest(&req); + + switch (req.response.status) { + .ok => { + // Whitelisted status codes. + }, + else => { + // Stop immediately. + const err = try cy.fmt.allocFormat(vm.alloc, "Can not load `{}`. Response code: {}", &.{v(url), v(req.response.status)}); + defer vm.alloc.free(err); + uvm.setApiError(err); + return error.HandledError; + }, + } + + var buf: std.ArrayListUnmanaged(u8) = .{}; + errdefer buf.deinit(vm.alloc); + var readBuf: [4096]u8 = undefined; + var read: usize = readBuf.len; + + while (read == readBuf.len) { + read = try client.readAll(&req, &readBuf); + try buf.appendSlice(vm.alloc, readBuf[0..read]); + } + + // Cache to local. + const entry = try cache.saveNewSpecFile(vm.alloc, specGroup, url, buf.items); + entry.deinit(vm.alloc); + + return try buf.toOwnedSlice(vm.alloc); +} diff --git a/src/codegen.zig b/src/codegen.zig index bb88ddf76..c2b8f01be 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -1109,6 +1109,11 @@ fn shouldGenMainScopeReleaseOps(self: *cy.VMcompiler) bool { } pub fn genStatements(self: *Chunk, head: cy.NodeId, comptime attachEnd: bool) anyerror!void { + if (head == cy.NullId) { + try self.buf.pushOp1(.end, 255); + return; + } + var cur_id = head; var node = self.nodes[cur_id]; diff --git a/src/config.zig b/src/config.zig index 4b99b3f79..82c202cd8 100644 --- a/src/config.zig +++ b/src/config.zig @@ -3,4 +3,10 @@ pub const Engine = enum { zig, /// Bytecode executed using C VM. c, +}; + +pub const Allocator = enum { + zig, + malloc, + mimalloc, }; \ No newline at end of file diff --git a/src/cyber.h b/src/cyber.h deleted file mode 100644 index f9f979e67..000000000 --- a/src/cyber.h +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) 2023 Cyber (See LICENSE) -#include -#include -#include - -typedef struct CyVM CyVM; -typedef struct CyModule CyModule; -typedef uint64_t CyValue; - -#define CY_NullId UINT32_MAX - -typedef enum { - CY_Success = 0, - CY_ErrorToken, - CY_ErrorParse, - CY_ErrorCompile, - CY_ErrorPanic, - CY_ErrorUnknown, -} CyResultCode; - -typedef enum { - CY_TypeNone = 0, - CY_TypeBoolean, - CY_TypeError, - CY_TypeStaticAstring, - CY_TypeStaticUstring, - CY_TypeEnum, - CY_TypeSymbol, - CY_TypeInteger, - CY_TypeFloat, - CY_TypeList, - CY_TypeListIter, - CY_TypeMap, - CY_TypeMapIter, - CY_TypeClosure, - CY_TypeLambda, - CY_TypeAstring, - CY_TypeUstring, - CY_TypeStringSlice, - CY_TypeRawString, - CY_TypeRawStringSlice, - CY_TypeFiber, - CY_TypeBox, - CY_TypeNativeFunc1, - CY_TypeTccState, - CY_TypePointer, - CY_TypeFile, - CY_TypeDir, - CY_TypeDirIter, - CY_TypeMetaType, -} CyType; - -// Null terminated string, but also includes a length. -typedef struct CStr { - const char* charz; - size_t len; -} CStr; -#define cstr(X) (CStr){ X, strlen(X) } - -typedef uint32_t CyTypeId; -typedef uint32_t CyModuleId; - -typedef CyValue (*CyFunc)(CyVM* vm, CyValue* args, uint8_t nargs); -typedef bool (*CyLoadModuleFunc)(CyVM* vm, CyModuleId modId); - -typedef union CyHeapObject { - struct { - CyTypeId typeId; - uint32_t rc; - void* ptr; - } pointer; -} CyHeapObject; - -// Top level. -CStr cyGetFullVersion(); -CStr cyGetVersion(); -CStr cyGetBuild(); -CStr cyGetCommit(); - -// VM. -CyVM* cyVmCreate(); -void cyVmDestroy(CyVM* vm); -CyResultCode cyVmEval(CyVM* vm, CStr src, CyValue* outVal); -CyResultCode cyVmValidate(CyVM* vm, CStr src); -CStr cyVmGetLastErrorReport(CyVM* vm); -void cyVmRelease(CyVM* vm, CyValue val); -void cyVmRetain(CyVM* vm, CyValue val); -void* cyVmGetUserData(CyVM* vm); -void cyVmSetUserData(CyVM* vm, void* userData); - -// Modules. -void cyVmAddModuleLoader(CyVM* vm, CStr name, CyLoadModuleFunc func); -void cyVmSetModuleFunc(CyVM* vm, CyModuleId modId, CStr name, uint32_t numParams, CyFunc func); -void cyVmSetModuleVar(CyVM* vm, CyModuleId modId, CStr name, CyValue val); - -// Intended to be used to manage accessible buffers when embedding WASM. -void* cyVmAlloc(CyVM* vm, size_t size); -void cyVmFree(CyVM* vm, void* ptr, size_t len); - -// Initialize values. -CyValue cyValueNone(); -CyValue cyValueTrue(); -CyValue cyValueFalse(); -CyValue cyValueFloat(double n); -CyValue cyValueInteger(int n); -CyValue cyValueGetOrAllocStringInfer(CyVM* vm, CStr str); -CyValue cyValueGetOrAllocAstring(CyVM* vm, CStr str); -CyValue cyValueGetOrAllocUstring(CyVM* vm, CStr str, uint32_t charLen); -CyValue cyValueAllocList(CyVM* vm); -CyValue cyValueAllocMap(CyVM* vm); -CyValue cyValueAllocNativeFunc(CyVM* vm, CyFunc func, uint32_t numParams); -CyValue cyValueAllocPointer(CyVM* vm, void* ptr); -CyValue cyValueTagLiteral(CyVM* vm, CStr str); -CyValue cyValueHeapObject(CyHeapObject* ptr); - -// Values. -CyTypeId cyValueGetTypeId(CyValue val); - -// Values to C types. -CyHeapObject* cyValueAsHeapObject(CyValue val); -double cyValueAsFloat(CyValue val); -bool cyValueToBool(CyValue val); -bool cyValueAsBool(CyValue val); -int64_t cyValueAsInteger(CyValue val); -uint32_t cyValueAsTagLiteralId(CyValue val); -CStr cyValueToTempString(CyVM* vm, CyValue val); -CStr cyValueToTempRawString(CyVM* vm, CyValue val); - -// Lists. -size_t cyListLen(CyValue list); -size_t cyListCap(CyValue list); -CyValue cyListGet(CyVM* vm, CyValue list, size_t idx); -void cyListSet(CyVM* vm, CyValue list, size_t idx, CyValue val); -void cyListAppend(CyVM* vm, CyValue list, CyValue val); -void cyListInsert(CyVM* vm, CyValue list, size_t idx, CyValue val); - -// Maps. -// size_t cyMapSize(CyValue map); -// bool cyMapContains(CyValue map, CyValue key); -// bool cyMapContainsStringKey(CyValue map, CStr key); -// CyValue cyMapGet(CyVM* vm, CyValue map, CyValue key); -// CyValue cyMapGetStringKey(CyVM* vm, CyValue map, CStr key); -// void cyMapSet(CyVM* vm, CyValue map, CyValue key, CyValue val); -// void cyMapSetStringKey(CyVM* vm, CyValue map, CStr key, CyValue val); \ No newline at end of file diff --git a/src/cyber.zig b/src/cyber.zig index 8980ffcde..d00b87e66 100644 --- a/src/cyber.zig +++ b/src/cyber.zig @@ -25,7 +25,7 @@ pub const module = @import("module.zig"); pub const ModuleId = module.ModuleId; pub const Module = module.Module; -const vm_compiler = @import("vm_compiler.zig"); +pub const vm_compiler = @import("vm_compiler.zig"); pub const VMcompiler = vm_compiler.VMcompiler; pub const CompileResultView = vm_compiler.CompileResultView; pub const CompileConfig = vm_compiler.CompileConfig; @@ -153,14 +153,14 @@ pub const simd = @import("simd.zig"); pub const isWasm = builtin.cpu.arch.isWasm(); pub const isWasmFreestanding = isWasm and builtin.os.tag == .freestanding; pub const is32Bit = build_options.is32Bit; -pub const hasJit = !isWasm; pub const hasStdFiles = !isWasm; pub const hasGC = build_options.gc; +pub const hasFFI = build_options.ffi; const build_options = @import("build_options"); pub const Trace = build_options.trace; pub const TrackGlobalRC = build_options.trackGlobalRC; -pub const UseMimalloc = build_options.useMimalloc; +pub const Malloc = build_options.malloc; const std = @import("std"); pub const NullId = std.math.maxInt(u32); @@ -171,13 +171,48 @@ pub fn Nullable(comptime T: type) type { return T; } -pub const NativeObjFuncPtr = *const fn (*UserVM, Value, [*]const Value, u8) Value; -pub const OptimizingNativeMethod = *const fn (*UserVM, pc: [*]Inst, Value, [*]const Value, u8) void; -pub const NativeObjFunc2Ptr = *const fn (*UserVM, Value, [*]const Value, u8) ValuePair; -pub const NativeFuncPtr = *const fn (*UserVM, [*]const Value, u8) Value; -pub const NativeErrorFunc = fn (*UserVM, [*]const Value, u8) anyerror!Value; -pub const ModuleLoaderFunc = *const fn (*UserVM, ModuleId) bool; - +pub const OptimizingFuncFn = *const fn (*UserVM, pc: [*]Inst, [*]const Value, u8) void; +pub const ZHostFuncFn = *const fn (*UserVM, [*]const Value, u8) Value; +pub const ZHostFuncCFn = *const fn (*UserVM, [*]const Value, u8) callconv(.C) Value; +pub const ZHostFuncPairFn = *const fn (*UserVM, [*]const Value, u8) ValuePair; + +/// Overlap with `include/cyber.h`. +pub const Str = extern struct { + buf: [*]const u8, + len: usize, + + pub fn initSlice(s: []const u8) Str { + return .{ + .buf = s.ptr, + .len = s.len, + }; + } + + pub fn slice(self: Str) []const u8 { + return self.buf[0..self.len]; + } +}; +pub const HostFuncInfo = extern struct { + modId: vmc.ModuleId, + name: Str, + funcSigId: vmc.FuncSigId, + idx: u32, +}; +pub const ModuleLoaderResult = extern struct { + src: Str, + srcIsStatic: bool, + funcLoader: ?HostFuncLoaderFn = null, + postLoad: ?PostLoadModuleFn = null, + destroy: ?ModuleDestroyFn = null, +}; +pub const ModuleResolverFn = *const fn (*UserVM, ChunkId, curUri: Str, spec: Str, outUri: *Str) callconv(.C) bool; +pub const ModuleLoaderFn = *const fn (*UserVM, Str, out: *ModuleLoaderResult) callconv(.C) bool; +pub const HostFuncLoaderFn = *const fn (*UserVM, HostFuncInfo) callconv(.C) vmc.HostFuncFn; +pub const PostLoadModuleFn = *const fn (*UserVM, modId: vmc.ModuleId) callconv(.C) void; +pub const ModuleDestroyFn = *const fn (*UserVM, modId: vmc.ModuleId) callconv(.C) void; +pub const PrintFn = *const fn (*UserVM, str: Str) callconv(.C) void; + +pub const cli = @import("cli.zig"); pub const log = @import("log.zig"); pub const utils = @import("utils.zig"); pub const IndexSlice = utils.IndexSlice; diff --git a/src/fmt.zig b/src/fmt.zig index d2dfe2742..292616a40 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -3,7 +3,7 @@ const builtin = @import("builtin"); const stdx = @import("stdx"); const t = stdx.testing; const cy = @import("cyber.zig"); -const core = @import("builtins/core.zig"); +const os = @import("std/os.zig"); const log = cy.log.scoped(.fmt); @@ -377,15 +377,15 @@ const HostFileWriter = struct { pub const Error = error{}; pub fn writeByte(self: HostFileWriter, byte: u8) linksection(cy.Section) Error!void { - core.hostFileWrite(self.fid, @ptrCast(&byte), 1); + os.hostFileWrite(self.fid, @ptrCast(&byte), 1); } pub fn writeAll(self: HostFileWriter, data: []const u8) linksection(cy.Section) Error!void { - core.hostFileWrite(self.fid, data.ptr, data.len); + os.hostFileWrite(self.fid, data.ptr, data.len); } pub fn write(self: HostFileWriter, data: []const u8) linksection(cy.Section) Error!void { - core.hostFileWrite(self.fid, data.ptr, data.len); + os.hostFileWrite(self.fid, data.ptr, data.len); } pub fn writeByteNTimes(self: HostFileWriter, byte: u8, n: usize) linksection(cy.Section) Error!void { diff --git a/src/heap.zig b/src/heap.zig index 7d48fe266..d8e406e87 100644 --- a/src/heap.zig +++ b/src/heap.zig @@ -26,13 +26,11 @@ var initedAllocator = false; fn initAllocator() void { defer initedAllocator = true; - if (build_options.useMalloc) { - return; - } else { - if (cy.UseMimalloc) { + switch (cy.Malloc) { + .zig, + .malloc => return, + .mimalloc => { miAlloc.init(); - } else { - return; } } // var traceAlloc: stdx.heap.TraceAllocator = undefined; @@ -46,18 +44,20 @@ pub fn getAllocator() std.mem.Allocator { if (!initedAllocator) { initAllocator(); } - if (build_options.useMalloc) { - return std.heap.c_allocator; - } else { - if (cy.UseMimalloc) { + switch (cy.Malloc) { + .mimalloc => { return miAlloc.allocator(); - } else { + }, + .malloc => { + return std.heap.c_allocator; + }, + .zig => { if (cy.isWasm) { return std.heap.wasm_allocator; } else { return gpa.allocator(); } - } + }, } } @@ -116,7 +116,7 @@ pub const HeapObject = extern union { object: Object, box: Box, nativeFunc1: NativeFunc1, - tccState: if (cy.hasJit) TccState else void, + tccState: if (cy.hasFFI) TccState else void, file: if (cy.hasStdFiles) File else void, dir: if (cy.hasStdFiles) Dir else void, pointer: Pointer, @@ -416,7 +416,7 @@ const Box = extern struct { const NativeFunc1 = extern struct { typeId: rt.TypeId, rc: u32, - func: *const fn (*cy.UserVM, [*]const Value, u8) Value, + func: vmc.HostFuncFn, numParams: u32, funcSigId: u32, tccState: Value, @@ -441,6 +441,7 @@ const File = extern struct { readBufEnd: u32, iterLines: bool, hasReadBuf: bool, + closeOnFree: bool, closed: bool, pub fn getStdFile(self: *const File) std.fs.File { @@ -1314,12 +1315,12 @@ pub fn allocBox(vm: *cy.VM, val: Value) !Value { return Value.initCycPtr(obj); } -pub fn allocNativeFunc1(self: *cy.VM, func: *const fn (*cy.UserVM, [*]const Value, u8) Value, numParams: u32, funcSigId: cy.sema.FuncSigId, tccState: ?Value) !Value { +pub fn allocNativeFunc1(self: *cy.VM, func: cy.ZHostFuncFn, numParams: u32, funcSigId: cy.sema.FuncSigId, tccState: ?Value) !Value { const obj = try allocPoolObject(self); obj.nativeFunc1 = .{ .typeId = rt.NativeFuncT, .rc = 1, - .func = func, + .func = @ptrCast(func), .numParams = numParams, .funcSigId = funcSigId, .tccState = undefined, @@ -1366,6 +1367,7 @@ pub fn allocFile(self: *cy.VM, fd: std.os.fd_t) linksection(cy.StdSection) !Valu .readBufCap = 0, .readBufEnd = 0, .closed = false, + .closeOnFree = true, }; return Value.initPtr(obj); } @@ -1678,7 +1680,7 @@ pub fn freeObject(vm: *cy.VM, obj: *HeapObject, } }, rt.TccStateT => { - if (cy.hasJit) { + if (cy.hasFFI) { if (free) { tcc.tcc_delete(obj.tccState.state); obj.tccState.lib.close(); @@ -1700,7 +1702,9 @@ pub fn freeObject(vm: *cy.VM, obj: *HeapObject, if (obj.file.hasReadBuf) { vm.alloc.free(obj.file.readBuf[0..obj.file.readBufCap]); } - obj.file.close(); + if (obj.file.closeOnFree) { + obj.file.close(); + } } freePoolObject(vm, obj); } @@ -1847,7 +1851,7 @@ test "heap internals." { try t.eq(@sizeOf(Box), 16); try t.eq(@sizeOf(NativeFunc1), 40); try t.eq(@sizeOf(MetaType), 16); - if (cy.hasJit) { + if (cy.hasFFI) { try t.eq(@sizeOf(TccState), 24); } if (cy.hasStdFiles) { diff --git a/src/include/cyber.h b/src/include/cyber.h new file mode 100644 index 000000000..35e115264 --- /dev/null +++ b/src/include/cyber.h @@ -0,0 +1,193 @@ +// Copyright (c) 2023 Cyber (See LICENSE) +#include +#include +#include + +typedef struct CsVM CsVM; + +typedef uint64_t CsValue; + +typedef struct CsModule CsModule; +typedef uint32_t CsModuleId; + +#define CS_NULLID UINT32_MAX + +typedef enum { + CS_SUCCESS = 0, + CS_ERROR_TOKEN, + CS_ERROR_PARSE, + CS_ERROR_COMPILE, + CS_ERROR_PANIC, + CS_ERROR_UNKNOWN, +} CsResultCode; + +typedef enum { + CS_TYPE_NONE = 0, + CS_TYPE_BOOLEAN, + CS_TYPE_ERROR, + CS_TYPE_STATICASTRING, + CS_TYPE_STATICUSTRING, + CS_TYPE_ENUM, + CS_TYPE_SYMBOL, + CS_TYPE_INTEGER, + CS_TYPE_FLOAT, + CS_TYPE_LIST, + CS_TYPE_LISTITER, + CS_TYPE_MAP, + CS_TYPE_MAPITER, + CS_TYPE_CLOSURE, + CS_TYPE_LAMBDA, + CS_TYPE_ASTRING, + CS_TYPE_USTRING, + CS_TYPE_STRINGSLICE, + CS_TYPE_RAWSTRING, + CS_TYPE_RAWSTRINGSLICE, + CS_TYPE_FIBER, + CS_TYPE_BOX, + CS_TYPE_NATIVEFUNC1, + CS_TYPE_TCCSTATE, + CS_TYPE_POINTER, + CS_TYPE_FILE, + CS_TYPE_DIR, + CS_TYPE_DIRITER, + CS_TYPE_METATYPE, +} CsType; +typedef uint32_t CsTypeId; + +// Cyber deals with string slices internally for efficiency. +// Although some API functions could accept a null terminated string, +// it's more consistent to use CsStr everywhere. +// Creating a CsStr can be simplified with a macro: +// #define str(x) ((CsStr){ x, strlen(x) }) +// Note: Returned `CsStr`s do not always end with a null char. +typedef struct CsStr { + const char* buf; + size_t len; +} CsStr; + +typedef CsValue (*CsHostFuncFn)(CsVM* vm, CsValue* args, uint8_t nargs); + +// Top level. +CsStr csGetFullVersion(); +CsStr csGetVersion(); +CsStr csGetBuild(); +CsStr csGetCommit(); + +// Given the current module's resolved URI and the "to be" imported module specifier, +// write the resolved specifier in `outUri` and return true, otherwise return false. +// Most embedders do not need a resolver and can rely on the default resolver which +// simply returns `spec` without any adjustments. +typedef bool (*CsModuleResolverFn)(CsVM* vm, uint32_t chunkId, CsStr curUri, CsStr spec, CsStr* outUri); + +typedef void (*CsPostLoadModuleFn)(CsVM* vm, uint32_t modId); +typedef void (*CsModuleDestroyFn)(CsVM* vm, uint32_t modId); + +// Info about a `hostfunc`. +typedef struct CsHostFuncInfo { + // The module it belongs to. + uint32_t modId; + // The name of the func. + CsStr name; + // The function's signature. + uint32_t funcSigId; + // A counter that tracks it's current position among all `hostfunc` in the module. + // This is useful if you want to bind an array of function pointers to `hostfunc`s. + uint32_t idx; +} CsHostFuncInfo; + +// Given info about a function, return it's function pointer or null. +typedef CsHostFuncFn (*CsHostFuncLoaderFn)(CsVM* vm, CsHostFuncInfo funcInfo); + +// Module loader config. +typedef struct CsModuleLoaderResult { + // The Cyber source code for the module. + CsStr src; + // Whether the provided `src` is from static memory or heap memory. + bool srcIsStatic; + // Callback to load `hostfunc`s or null. + CsHostFuncLoaderFn funcLoader; + // Callback after all symbols in module are loaded or null. + CsPostLoadModuleFn postLoad; + // Callback just before the module is destroyed or null. + CsModuleDestroyFn destroy; +} CsModuleLoaderResult; + +// Given the resolved import specifier of the module, write the loader details in `out` +// and return true, otherwise return false. +typedef bool (*CsModuleLoaderFn)(CsVM* vm, CsStr resolvedSpec, CsModuleLoaderResult* out); + +typedef void (*CsPrintFn)(CsVM* vm, CsStr str); + +// +// [ VM ] +// +CsVM* csCreate(); +void csDestroy(CsVM* vm); +CsModuleLoaderFn csGetModuleLoader(CsVM* vm); +void csSetModuleLoader(CsVM* vm, CsModuleLoaderFn loader); +CsResultCode csEval(CsVM* vm, CsStr src, CsValue* outVal); +CsResultCode csValidate(CsVM* vm, CsStr src); +CsStr csGetLastErrorReport(CsVM* vm); +void csRelease(CsVM* vm, CsValue val); +void csRetain(CsVM* vm, CsValue val); +void* csGetUserData(CsVM* vm); +void csSetUserData(CsVM* vm, void* userData); + +// Modules. +void csSetModuleFunc(CsVM* vm, CsModuleId modId, CsStr name, uint32_t numParams, CsHostFuncFn func); +void csSetModuleVar(CsVM* vm, CsModuleId modId, CsStr name, CsValue val); + +// For embedded, Cyber by default uses malloc (it can be configured to use the high-perf mimalloc). +// If the host uses a different allocator than Cyber, use `csAlloc` to allocate memory +// that is handed over to Cyber so it knows how to free it. +// This is also used to manage accessible buffers when embedding WASM. +void* csAlloc(CsVM* vm, size_t size); +void csFree(CsVM* vm, void* ptr, size_t len); + +// +// [ Values ] +// + +// Create values. +CsValue csNone(); +CsValue csTrue(); +CsValue csFalse(); +CsValue csFloat(double n); +CsValue csInteger(int n); +CsValue csTagLiteral(CsVM* vm, CsStr str); +CsValue csNewString(CsVM* vm, CsStr str); +CsValue csNewAstring(CsVM* vm, CsStr str); +CsValue csNewUstring(CsVM* vm, CsStr str, uint32_t charLen); +CsValue csNewList(CsVM* vm); +CsValue csNewMap(CsVM* vm); +CsValue csNewHostFunc(CsVM* vm, CsHostFuncFn func, uint32_t numParams); +CsValue csNewPointer(CsVM* vm, void* ptr); + +// Values. +CsTypeId csGetTypeId(CsValue val); + +// Values to C. +double csAsFloat(CsValue val); +bool csToBool(CsValue val); +bool csAsBool(CsValue val); +int64_t csAsInteger(CsValue val); +uint32_t csAsTagLiteralId(CsValue val); +CsStr csToTempString(CsVM* vm, CsValue val); +CsStr csToTempRawString(CsVM* vm, CsValue val); + +// Lists. +size_t csListLen(CsValue list); +size_t csListCap(CsValue list); +CsValue csListGet(CsVM* vm, CsValue list, size_t idx); +void csListSet(CsVM* vm, CsValue list, size_t idx, CsValue val); +void csListAppend(CsVM* vm, CsValue list, CsValue val); +void csListInsert(CsVM* vm, CsValue list, size_t idx, CsValue val); + +// Maps. +// size_t csMapSize(CsValue map); +// bool csMapContains(CsValue map, CsValue key); +// bool csMapContainsStringKey(CsValue map, CsStr key); +// CsValue csMapGet(CsVM* vm, CsValue map, CsValue key); +// CsValue csMapGetStringKey(CsVM* vm, CsValue map, CsStr key); +// void csMapSet(CsVM* vm, CsValue map, CsValue key, CsValue val); +// void csMapSetStringKey(CsVM* vm, CsValue map, CsStr key, CsValue val); \ No newline at end of file diff --git a/src/lib.zig b/src/lib.zig index 2286b0e72..c742f7b23 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -12,10 +12,10 @@ const log = cy.log.scoped(.lib); const bt = cy.types.BuiltinTypeSymIds; const c = @cImport({ - @cInclude("cyber.h"); + @cInclude("include/cyber.h"); }); -export fn cyVmCreate() *cy.UserVM { +export fn csCreate() *cy.UserVM { const alloc = cy.heap.getAllocator(); const vm = alloc.create(cy.VM) catch fatal(); vm.init(alloc) catch fatal(); @@ -25,7 +25,7 @@ export fn cyVmCreate() *cy.UserVM { return @ptrCast(vm); } -export fn cyVmDestroy(vm: *cy.UserVM) void { +export fn csDestroy(vm: *cy.UserVM) void { vm.deinit(); if (cy.isWasm) { cy.log.wasm.deinit(); @@ -33,435 +33,404 @@ export fn cyVmDestroy(vm: *cy.UserVM) void { } /// This is useful when calling into wasm to allocate some memory. -export fn cyVmAlloc(vm: *cy.UserVM, size: usize) [*]const align(8) u8 { +export fn csAlloc(vm: *cy.UserVM, size: usize) [*]const align(8) u8 { const slice = vm.allocator().alignedAlloc(u8, 8, size) catch fatal(); return slice.ptr; } -export fn cyVmFree(vm: *cy.UserVM, ptr: [*]const align(8) u8, size: usize) void { +export fn csFree(vm: *cy.UserVM, ptr: [*]const align(8) u8, size: usize) void { vm.allocator().free(ptr[0..size]); } -export fn cyVmEval(vm: *cy.UserVM, src: c.CStr, outVal: *cy.Value) c.CyResultCode { - outVal.* = vm.eval("main", src.charz[0..src.len], .{ +export fn csEval(vm: *cy.UserVM, src: cy.Str, outVal: *cy.Value) c.CsResultCode { + outVal.* = vm.eval("main", src.slice(), .{ .singleRun = false, }) catch |err| { switch (err) { error.TokenError => { - return c.CY_ErrorToken; + return c.CS_ERROR_TOKEN; }, error.ParseError => { - return c.CY_ErrorParse; + return c.CS_ERROR_PARSE; }, error.CompileError => { - return c.CY_ErrorCompile; + return c.CS_ERROR_COMPILE; }, error.Panic => { - return c.CY_ErrorPanic; + return c.CS_ERROR_PANIC; }, else => { - return c.CY_ErrorUnknown; + return c.CS_ERROR_UNKNOWN; }, } }; - return c.CY_Success; + return c.CS_SUCCESS; } -export fn cyVmValidate(vm: *cy.UserVM, src: c.CStr) c.CyResultCode { - const res = vm.internal().validate("main", src.charz[0..src.len], .{}) catch |err| { +export fn csValidate(vm: *cy.UserVM, src: cy.Str) c.CsResultCode { + const res = vm.internal().validate("main", src.slice(), .{}) catch |err| { log.debug("validate error: {}", .{err}); - return c.CY_ErrorUnknown; + return c.CS_ERROR_UNKNOWN; }; if (res.err) |err| { switch (err) { .tokenize => { - return c.CY_ErrorToken; + return c.CS_ERROR_TOKEN; }, .parse => { - return c.CY_ErrorParse; + return c.CS_ERROR_PARSE; }, .compile => { - return c.CY_ErrorCompile; + return c.CS_ERROR_COMPILE; }, } } - return c.CY_Success; + return c.CS_SUCCESS; } -test "cyVmValidate()" { - const vm = c.cyVmCreate(); - defer c.cyVmDestroy(vm); +test "csValidate()" { + const vm = c.csCreate(); + defer c.csDestroy(vm); cy.silentError = true; defer cy.silentError = false; - var res = c.cyVmValidate(vm, initCStr("1 + 2")); - try t.eq(res, c.CY_Success); + var res = c.csValidate(vm, initStrSlice("1 + 2")); + try t.eq(res, c.CS_SUCCESS); - res = c.cyVmValidate(vm, initCStr("1 +")); - try t.eq(res, c.CY_ErrorParse); + res = c.csValidate(vm, initStrSlice("1 +")); + try t.eq(res, c.CS_ERROR_PARSE); } var tempBuf: [1024]u8 align(8) = undefined; -export fn cyVmGetLastErrorReport(vm: *cy.UserVM) c.CStr { +export fn csGetLastErrorReport(vm: *cy.UserVM) cy.Str { const report = vm.allocLastErrorReport() catch fatal(); - defer vm.internal().alloc.free(report); - if (report.len > tempBuf.len - 1) { - cy.panic("Buffer too small."); - } - @memcpy(tempBuf[0..report.len], report); - tempBuf[report.len] = 0; - return c.CStr{ - .charz = &tempBuf, - .len = report.len, - }; + return cy.Str.initSlice(report); } -export fn cyVmAddModuleLoader(vm: *cy.UserVM, cspec: c.CStr, func: c.CyLoadModuleFunc) void { - const absSpec = cspec.charz[0..cspec.len]; - vm.addModuleLoader(absSpec, @ptrCast(func)) catch fatal(); +export fn csGetModuleLoader(vm: *cy.UserVM) c.CsModuleLoaderFn { + return @ptrCast(vm.getModuleLoader()); } -export fn cyVmSetModuleFunc(vm: *cy.UserVM, modId: cy.ModuleId, cname: c.CStr, numParams: u32, func: c.CyFunc) void { - const symName = cname.charz[0..cname.len]; +export fn csSetModuleLoader(vm: *cy.UserVM, loader: c.CsModuleLoaderFn) void { + vm.setModuleLoader(@ptrCast(loader)); +} + +export fn csSetModuleFunc(vm: *cy.UserVM, modId: cy.ModuleId, cname: cy.Str, numParams: u32, func: c.CsHostFuncFn) void { + const symName = cname.slice(); const mod = vm.internal().compiler.sema.getModulePtr(modId); mod.setNativeFuncExt(vm.internal().compiler, symName, true, numParams, @ptrCast(func)) catch fatal(); } -export fn cyVmSetModuleVar(vm: *cy.UserVM, modId: cy.ModuleId, cname: c.CStr, val: c.CyValue) void { - const symName = cname.charz[0..cname.len]; +export fn csSetModuleVar(vm: *cy.UserVM, modId: cy.ModuleId, cname: cy.Str, val: c.CsValue) void { + const symName = cname.slice(); const mod = vm.internal().compiler.sema.getModulePtr(modId); mod.setVarExt(vm.internal().compiler, symName, true, bt.Any, @bitCast(val)) catch fatal(); } -export fn cyVmRelease(vm: *cy.UserVM, val: Value) void { +export fn csRelease(vm: *cy.UserVM, val: Value) void { vm.release(val); } -export fn cyVmRetain(vm: *cy.UserVM, val: Value) void { +export fn csRetain(vm: *cy.UserVM, val: Value) void { vm.retain(val); } -export fn cyVmGetUserData(vm: *cy.UserVM) ?*anyopaque { +export fn csGetUserData(vm: *cy.UserVM) ?*anyopaque { return vm.getUserData(); } -export fn cyVmSetUserData(vm: *cy.UserVM, userData: ?*anyopaque) void { +export fn csSetUserData(vm: *cy.UserVM, userData: ?*anyopaque) void { vm.setUserData(userData); } -export fn cyValueNone() Value { +export fn csNone() Value { return Value.None; } -export fn cyValueTrue() Value { +export fn csTrue() Value { return Value.True; } -export fn cyValueFalse() Value { +export fn csFalse() Value { return Value.False; } -export fn cyValueFloat(n: f64) Value { +export fn csFloat(n: f64) Value { return Value.initF64(n); } -export fn cyValueInteger(n: i32) Value { +export fn csInteger(n: i32) Value { return Value.initInt(n); } -export fn cyValueHeapObject(o: *c.CyHeapObject) Value { - return Value.initPtr(o); -} - -export fn cyValueGetOrAllocStringInfer(vm: *cy.UserVM, cstr: c.CStr) Value { - return vm.allocStringInfer(cstr.charz[0..cstr.len]) catch fatal(); +export fn csNewString(vm: *cy.UserVM, cstr: cy.Str) Value { + return vm.allocStringInfer(cstr.slice()) catch fatal(); } -export fn cyValueGetOrAllocAstring(vm: *cy.UserVM, cstr: c.CStr) Value { - return vm.allocAstring(cstr.charz[0..cstr.len]) catch fatal(); +export fn csNewAstring(vm: *cy.UserVM, cstr: cy.Str) Value { + return vm.allocAstring(cstr.slice()) catch fatal(); } -export fn cyValueGetOrAllocUstring(vm: *cy.UserVM, cstr: c.CStr, charLen: u32) Value { - return vm.allocUstring(cstr.charz[0..cstr.len], charLen) catch fatal(); +export fn csNewUstring(vm: *cy.UserVM, cstr: cy.Str, charLen: u32) Value { + return vm.allocUstring(cstr.slice(), charLen) catch fatal(); } -export fn cyValueAllocList(vm: *cy.UserVM) Value { +export fn csNewList(vm: *cy.UserVM) Value { return vm.allocEmptyList() catch fatal(); } -export fn cyValueAllocMap(vm: *cy.UserVM) Value { +export fn csNewMap(vm: *cy.UserVM) Value { return vm.allocEmptyMap() catch fatal(); } -export fn cyValueAllocNativeFunc(vm: *cy.UserVM, func: c.CyFunc, numParams: u32) Value { +export fn csNewHostFunc(vm: *cy.UserVM, func: c.CsHostFuncFn, numParams: u32) Value { const funcSigId = vm.ensureUntypedFuncSig(numParams) catch fatal(); return cy.heap.allocNativeFunc1(vm.internal(), @ptrCast(func), numParams, funcSigId, null) catch fatal(); } -export fn cyValueTagLiteral(vm: *cy.UserVM, str: c.CStr) Value { - const id = vm.internal().ensureSymbolExt(str.charz[0..str.len], true) catch fatal(); - return Value.initSymbol(@intCast(id)); -} +test "csNewHostFunc()" { + const vm = c.csCreate(); + defer c.csDestroy(vm); -test "cyValueAllocNativeFunc()" { - const vm = c.cyVmCreate(); - defer c.cyVmDestroy(vm); + const val = c.csNewHostFunc(vm, @ptrFromInt(8), 2); + try t.eq(c.csGetTypeId(val), rt.NativeFuncT); +} - const val = c.cyValueAllocNativeFunc(vm, @ptrFromInt(8), 2); - try t.eq(c.cyValueGetTypeId(val), rt.NativeFuncT); +export fn csTagLiteral(vm: *cy.UserVM, str: cy.Str) Value { + const id = vm.internal().ensureSymbolExt(str.slice(), true) catch fatal(); + return Value.initSymbol(@intCast(id)); } -export fn cyValueAllocPointer(vm: *cy.UserVM, ptr: ?*anyopaque) Value { +export fn csNewPointer(vm: *cy.UserVM, ptr: ?*anyopaque) Value { return cy.heap.allocPointer(vm.internal(), ptr) catch fatal(); } -test "cyValueAllocPointer()" { - const vm = c.cyVmCreate(); - defer c.cyVmDestroy(vm); +test "csNewPointer()" { + const vm = c.csCreate(); + defer c.csDestroy(vm); - const val = c.cyValueAllocPointer(vm, @ptrFromInt(123)); - try t.eq(c.cyValueGetTypeId(val), rt.PointerT); + const val = c.csNewPointer(vm, @ptrFromInt(123)); + try t.eq(c.csGetTypeId(val), rt.PointerT); - const obj = c.cyValueAsHeapObject(val)[0]; + const obj = (Value{.val = val}).asHeapObject(); try t.eq(@intFromPtr(obj.pointer.ptr), 123); } -export fn cyValueAsHeapObject(val: Value) *c.CyHeapObject { - return @ptrCast(val.asHeapObject()); -} - -test "cyValueAsHeapObject()" { - const o = c.cyValueAsHeapObject(c.cyValueHeapObject(@ptrFromInt(80))); - try t.eq(@intFromPtr(o), 80); -} - -export fn cyValueAsFloat(val: Value) f64 { +export fn csAsFloat(val: Value) f64 { return val.asF64(); } -test "cyValueAsFloat()" { - try t.eq(c.cyValueAsFloat(c.cyValueFloat(123.0)), 123); +test "csAsFloat()" { + try t.eq(c.csAsFloat(c.csFloat(123.0)), 123); } -export fn cyValueToBool(val: Value) bool { +export fn csToBool(val: Value) bool { return val.toBool(); } -test "cyValueToBool()" { - try t.eq(c.cyValueToBool(c.cyValueFloat(123.0)), true); - try t.eq(c.cyValueToBool(c.cyValueFloat(0)), true); - try t.eq(c.cyValueToBool(c.cyValueTrue()), true); - try t.eq(c.cyValueToBool(c.cyValueNone()), false); - try t.eq(c.cyValueToBool(c.cyValueFalse()), false); +test "csToBool()" { + try t.eq(c.csToBool(c.csFloat(123.0)), true); + try t.eq(c.csToBool(c.csFloat(0)), true); + try t.eq(c.csToBool(c.csTrue()), true); + try t.eq(c.csToBool(c.csNone()), false); + try t.eq(c.csToBool(c.csFalse()), false); } -export fn cyValueAsBool(val: Value) bool { +export fn csAsBool(val: Value) bool { return val.asBool(); } -test "cyValueAsBool()" { - try t.eq(c.cyValueAsBool(c.cyValueTrue()), true); - try t.eq(c.cyValueAsBool(c.cyValueFalse()), false); +test "csAsBool()" { + try t.eq(c.csAsBool(c.csTrue()), true); + try t.eq(c.csAsBool(c.csFalse()), false); } -export fn cyValueAsInteger(val: Value) i64 { +export fn csAsInteger(val: Value) i64 { return val.asInteger(); } -test "cyValueAsInteger()" { - try t.eq(c.cyValueAsInteger(c.cyValueInteger(123)), 123); +test "csAsInteger()" { + try t.eq(c.csAsInteger(c.csInteger(123)), 123); } -export fn cyValueAsTagLiteralId(val: Value) u32 { +export fn csAsTagLiteralId(val: Value) u32 { return val.asSymbolId(); } -test "cyValueAsTagLiteralId()" { - const vm = c.cyVmCreate(); - defer c.cyVmDestroy(vm); +test "csAsTagLiteralId()" { + const vm = c.csCreate(); + defer c.csDestroy(vm); - var str = initCStr("foo"); - var val = c.cyValueTagLiteral(vm, str); - try t.eq(c.cyValueAsTagLiteralId(val), 0); + var str = initStrSlice("foo"); + var val = c.csTagLiteral(vm, str); + try t.eq(c.csAsTagLiteralId(val), 0); - str = initCStr("bar"); - val = c.cyValueTagLiteral(vm, str); - try t.eq(c.cyValueAsTagLiteralId(val), 1); + str = initStrSlice("bar"); + val = c.csTagLiteral(vm, str); + try t.eq(c.csAsTagLiteralId(val), 1); - str = initCStr("foo"); - val = c.cyValueTagLiteral(vm, str); - try t.eq(c.cyValueAsTagLiteralId(val), 0); + str = initStrSlice("foo"); + val = c.csTagLiteral(vm, str); + try t.eq(c.csAsTagLiteralId(val), 0); } -export fn cyValueToTempString(vm: *cy.UserVM, val: Value) c.CStr { +export fn csToTempString(vm: *cy.UserVM, val: Value) cy.Str { const str = vm.valueToTempString(val); - vm.internal().u8Buf.resize(vm.allocator(), str.len + 1) catch fatal(); - @memcpy(vm.internal().u8Buf.buf[0..str.len], str); - vm.internal().u8Buf.buf[str.len] = 0; - return .{ - .charz = vm.internal().u8Buf.buf.ptr, - .len = str.len, - }; + return cy.Str.initSlice(str); } -export fn cyValueToTempRawString(vm: *cy.UserVM, val: Value) c.CStr { +export fn csToTempRawString(vm: *cy.UserVM, val: Value) cy.Str { const str = vm.valueToTempRawString(val); - vm.internal().u8Buf.resize(vm.allocator(), str.len + 1) catch fatal(); - @memcpy(vm.internal().u8Buf.buf[0..str.len], str); - vm.internal().u8Buf.buf[str.len] = 0; - return .{ - .charz = vm.internal().u8Buf.buf.ptr, - .len = str.len, - }; + return cy.Str.initSlice(str); } test "Constants." { - try t.eq(c.CY_TypeNone, rt.NoneT); - try t.eq(c.CY_TypeBoolean, rt.BooleanT); - try t.eq(c.CY_TypeError, rt.ErrorT); - try t.eq(c.CY_TypeStaticAstring, rt.StaticAstringT); - try t.eq(c.CY_TypeStaticUstring, rt.StaticUstringT); - try t.eq(c.CY_TypeEnum, rt.EnumT); - try t.eq(c.CY_TypeSymbol, rt.SymbolT); - try t.eq(c.CY_TypeInteger, rt.IntegerT); - try t.eq(c.CY_TypeFloat, rt.FloatT); - try t.eq(c.CY_TypeList, rt.ListT); - try t.eq(c.CY_TypeListIter, rt.ListIteratorT); - try t.eq(c.CY_TypeMap, rt.MapT); - try t.eq(c.CY_TypeMapIter, rt.MapIteratorT); - try t.eq(c.CY_TypeClosure, rt.ClosureT); - try t.eq(c.CY_TypeLambda, rt.LambdaT); - try t.eq(c.CY_TypeAstring, rt.AstringT); - try t.eq(c.CY_TypeUstring, rt.UstringT); - try t.eq(c.CY_TypeStringSlice, rt.StringSliceT); - try t.eq(c.CY_TypeRawString, rt.RawstringT); - try t.eq(c.CY_TypeRawStringSlice, rt.RawstringSliceT); - try t.eq(c.CY_TypeFiber, rt.FiberT); - try t.eq(c.CY_TypeBox, rt.BoxT); - try t.eq(c.CY_TypeNativeFunc1, rt.NativeFuncT); - try t.eq(c.CY_TypeTccState, rt.TccStateT); - try t.eq(c.CY_TypePointer, rt.PointerT); - try t.eq(c.CY_TypeFile, rt.FileT); - try t.eq(c.CY_TypeDir, rt.DirT); - try t.eq(c.CY_TypeDirIter, rt.DirIteratorT); - try t.eq(c.CY_TypeMetaType, rt.MetaTypeT); -} - -export fn cyValueGetTypeId(val: Value) c.CyTypeId { + try t.eq(c.CS_TYPE_NONE, rt.NoneT); + try t.eq(c.CS_TYPE_BOOLEAN, rt.BooleanT); + try t.eq(c.CS_TYPE_ERROR, rt.ErrorT); + try t.eq(c.CS_TYPE_STATICASTRING, rt.StaticAstringT); + try t.eq(c.CS_TYPE_STATICUSTRING, rt.StaticUstringT); + try t.eq(c.CS_TYPE_ENUM, rt.EnumT); + try t.eq(c.CS_TYPE_SYMBOL, rt.SymbolT); + try t.eq(c.CS_TYPE_INTEGER, rt.IntegerT); + try t.eq(c.CS_TYPE_FLOAT, rt.FloatT); + try t.eq(c.CS_TYPE_LIST, rt.ListT); + try t.eq(c.CS_TYPE_LISTITER, rt.ListIteratorT); + try t.eq(c.CS_TYPE_MAP, rt.MapT); + try t.eq(c.CS_TYPE_MAPITER, rt.MapIteratorT); + try t.eq(c.CS_TYPE_CLOSURE, rt.ClosureT); + try t.eq(c.CS_TYPE_LAMBDA, rt.LambdaT); + try t.eq(c.CS_TYPE_ASTRING, rt.AstringT); + try t.eq(c.CS_TYPE_USTRING, rt.UstringT); + try t.eq(c.CS_TYPE_STRINGSLICE, rt.StringSliceT); + try t.eq(c.CS_TYPE_RAWSTRING, rt.RawstringT); + try t.eq(c.CS_TYPE_RAWSTRINGSLICE, rt.RawstringSliceT); + try t.eq(c.CS_TYPE_FIBER, rt.FiberT); + try t.eq(c.CS_TYPE_BOX, rt.BoxT); + try t.eq(c.CS_TYPE_NATIVEFUNC1, rt.NativeFuncT); + try t.eq(c.CS_TYPE_TCCSTATE, rt.TccStateT); + try t.eq(c.CS_TYPE_POINTER, rt.PointerT); + try t.eq(c.CS_TYPE_FILE, rt.FileT); + try t.eq(c.CS_TYPE_DIR, rt.DirT); + try t.eq(c.CS_TYPE_DIRITER, rt.DirIteratorT); + try t.eq(c.CS_TYPE_METATYPE, rt.MetaTypeT); +} + +export fn csGetTypeId(val: Value) c.CsTypeId { return val.getTypeId(); } -test "cyValueGetType()" { - try t.eq(c.cyValueGetTypeId(c.cyValueFloat(123)), rt.FloatT); +test "csGetTypeId()" { + try t.eq(c.csGetTypeId(c.csFloat(123)), rt.FloatT); } -export fn cyListLen(list: Value) usize { +export fn csListLen(list: Value) usize { return list.asHeapObject().list.list.len; } -export fn cyListCap(list: Value) usize { +export fn csListCap(list: Value) usize { return list.asHeapObject().list.list.cap; } -export fn cyListGet(vm: *cy.UserVM, list: Value, idx: usize) Value { +export fn csListGet(vm: *cy.UserVM, list: Value, idx: usize) Value { const res = list.asHeapObject().list.list.ptr[idx]; vm.retain(res); return res; } -export fn cyListSet(vm: *cy.UserVM, list: Value, idx: usize, val: Value) void { +export fn csListSet(vm: *cy.UserVM, list: Value, idx: usize, val: Value) void { vm.retain(val); vm.release(list.asHeapObject().list.list.ptr[idx]); list.asHeapObject().list.list.ptr[idx] = val; } -export fn cyListInsert(vm: *cy.UserVM, list: Value, idx: usize, val: Value) void { +export fn csListInsert(vm: *cy.UserVM, list: Value, idx: usize, val: Value) void { vm.retain(val); const inner = cy.ptrAlignCast(*cy.List(Value), &list.asHeapObject().list.list); inner.growTotalCapacity(vm.allocator(), inner.len + 1) catch cy.fatal(); inner.insertAssumeCapacity(idx, val); } -export fn cyListAppend(vm: *cy.UserVM, list: Value, val: Value) void { +export fn csListAppend(vm: *cy.UserVM, list: Value, val: Value) void { vm.retain(val); return list.asHeapObject().list.append(vm.allocator(), val); } test "List ops." { - const vm = c.cyVmCreate(); - defer c.cyVmDestroy(vm); + const vm = c.csCreate(); + defer c.csDestroy(vm); - var list: c.CyValue = undefined; - _ = c.cyVmEval(vm, initCStr("[1, 2, 3]"), &list); + var list: c.CsValue = undefined; + _ = c.csEval(vm, initStrSlice("[1, 2, 3]"), &list); // Initial cap. - try t.eq(c.cyListLen(list), 3); - try t.eq(c.cyListCap(list), 3); + try t.eq(c.csListLen(list), 3); + try t.eq(c.csListCap(list), 3); // Append. - c.cyListAppend(vm, list, c.cyValueFloat(4)); - var res = c.cyListGet(vm, list, 3); - try t.eq(c.cyValueAsFloat(res), 4); - try t.eq(c.cyListLen(list), 4); - try t.eq(c.cyListCap(list), 12); + c.csListAppend(vm, list, c.csFloat(4)); + var res = c.csListGet(vm, list, 3); + try t.eq(c.csAsFloat(res), 4); + try t.eq(c.csListLen(list), 4); + try t.eq(c.csListCap(list), 12); // Get. - res = c.cyListGet(vm, list, 1); - try t.eq(c.cyValueAsInteger(res), 2); + res = c.csListGet(vm, list, 1); + try t.eq(c.csAsInteger(res), 2); // Set. - c.cyListSet(vm, list, 1, c.cyValueFloat(100)); - res = c.cyListGet(vm, list, 1); - try t.eq(c.cyValueAsFloat(res), 100); + c.csListSet(vm, list, 1, c.csFloat(100)); + res = c.csListGet(vm, list, 1); + try t.eq(c.csAsFloat(res), 100); // Insert. - c.cyListInsert(vm, list, 0, c.cyValueFloat(123)); - res = c.cyListGet(vm, list, 0); - try t.eq(c.cyValueAsFloat(res), 123); - try t.eq(c.cyListLen(list), 5); + c.csListInsert(vm, list, 0, c.csFloat(123)); + res = c.csListGet(vm, list, 0); + try t.eq(c.csAsFloat(res), 123); + try t.eq(c.csListLen(list), 5); } -export fn cyGetFullVersion() c.CStr { - return initCStr(build_options.full_version.ptr[0..build_options.full_version.len :0]); +export fn csGetFullVersion() cy.Str { + return cy.Str.initSlice(build_options.full_version.ptr[0..build_options.full_version.len]); } -test "cyGetFullVersion()" { - const str = c.cyGetFullVersion(); - try t.eqStr(str.charz[0..str.len], build_options.full_version); +test "csGetFullVersion()" { + const str = c.csGetFullVersion(); + try t.eqStr(str.buf[0..str.len], build_options.full_version); } -export fn cyGetVersion() c.CStr { - return initCStr(build_options.version.ptr[0..build_options.version.len :0]); +export fn csGetVersion() cy.Str { + return cy.Str.initSlice(build_options.version.ptr[0..build_options.version.len]); } -test "cyGetVersion()" { - const str = c.cyGetVersion(); - try t.eqStr(str.charz[0..str.len], build_options.version); +test "csGetVersion()" { + const str = c.csGetVersion(); + try t.eqStr(str.buf[0..str.len], build_options.version); } -export fn cyGetBuild() c.CStr { - return initCStr(build_options.build.ptr[0..build_options.build.len :0]); +export fn csGetBuild() cy.Str { + return cy.Str.initSlice(build_options.build.ptr[0..build_options.build.len]); } -test "cyGetBuild()" { - const str = c.cyGetBuild(); - try t.eqStr(str.charz[0..str.len], build_options.build); +test "csGetBuild()" { + const str = c.csGetBuild(); + try t.eqStr(str.buf[0..str.len], build_options.build); } -export fn cyGetCommit() c.CStr { - return initCStr(build_options.commit.ptr[0..build_options.commit.len :0]); +export fn csGetCommit() cy.Str { + return cy.Str.initSlice(build_options.commit.ptr[0..build_options.commit.len]); } -test "cyGetCommit()" { - const str = c.cyGetCommit(); - try t.eqStr(str.charz[0..str.len], build_options.commit); +test "csGetCommit()" { + const str = c.csGetCommit(); + try t.eqStr(str.buf[0..str.len], build_options.commit); } -fn initCStr(str: [:0]const u8) c.CStr { - return c.CStr{ - .charz = str.ptr, - .len = str.len, +fn initStrSlice(s: []const u8) c.CsStr { + return .{ + .buf = s.ptr, + .len = s.len, }; } \ No newline at end of file diff --git a/src/main.zig b/src/main.zig index fada8217f..7f387563e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -130,6 +130,7 @@ fn compilePath(alloc: std.mem.Allocator, path: []const u8) !void { defer alloc.free(src); try vm.init(alloc); + cy.cli.setupVMForCLI(@ptrCast(&vm)); defer vm.deinit(false); const res = vm.compile(path, src, .{ @@ -162,6 +163,7 @@ fn evalPath(alloc: std.mem.Allocator, path: []const u8) !void { cy.verbose = verbose; try vm.init(alloc); + cy.cli.setupVMForCLI(@ptrCast(&vm)); defer vm.deinit(false); _ = vm.eval(path, src, .{ diff --git a/src/module.zig b/src/module.zig index 2ad1f5c13..e19651813 100644 --- a/src/module.zig +++ b/src/module.zig @@ -9,6 +9,7 @@ const types = cy.types; const bt = types.BuiltinTypeSymIds; const fmt = cy.fmt; const v = fmt.v; +const vmc = cy.vmc; pub const ModuleSymKey = cy.hash.KeyU64; @@ -31,12 +32,12 @@ pub const Module = struct { absSpec: []const u8, pub fn setNativeTypedFunc(self: *Module, c: *cy.VMcompiler, name: []const u8, - sig: []const sema.SymbolId, retSymId: sema.SymbolId, func: cy.NativeFuncPtr) !void { + sig: []const sema.SymbolId, retSymId: sema.SymbolId, func: cy.ZHostFuncFn) !void { return self.setNativeTypedFuncExt(c, name, false, sig, retSymId, func); } pub fn setNativeTypedFuncExt(self: *Module, c: *cy.VMcompiler, name: []const u8, dupeName: bool, - sig: []const sema.SymbolId, retSymId: sema.SymbolId, func: cy.NativeFuncPtr) !void { + sig: []const sema.SymbolId, retSymId: sema.SymbolId, func: cy.ZHostFuncFn) !void { const nameId = try sema.ensureNameSymExt(c, name, dupeName); // AnyType for params and return. @@ -49,10 +50,10 @@ pub const Module = struct { }; const res = try self.syms.getOrPut(c.alloc, key); res.value_ptr.* = .{ - .symT = .nativeFunc1, + .symT = .hostFunc, .inner = .{ - .nativeFunc1 = .{ - .func = func, + .hostFunc = .{ + .func = @ptrCast(func), }, }, }; @@ -61,11 +62,11 @@ pub const Module = struct { } } - pub fn setNativeFunc(self: *Module, c: *cy.VMcompiler, name: []const u8, numParams: u32, func: cy.NativeFuncPtr) !void { + pub fn setNativeFunc(self: *Module, c: *cy.VMcompiler, name: []const u8, numParams: u32, func: cy.ZHostFuncFn) !void { return self.setNativeFuncExt(c, name, false, numParams, func); } - pub fn setNativeFuncExt(self: *Module, c: *cy.VMcompiler, name: []const u8, dupeName: bool, numParams: u32, func: cy.NativeFuncPtr) !void { + pub fn setNativeFuncExt(self: *Module, c: *cy.VMcompiler, name: []const u8, dupeName: bool, numParams: u32, func: cy.ZHostFuncFn) !void { const nameId = try sema.ensureNameSymExt(c, name, dupeName); // AnyType for params and return. @@ -78,10 +79,10 @@ pub const Module = struct { }; const res = try self.syms.getOrPut(c.alloc, key); res.value_ptr.* = .{ - .symT = .nativeFunc1, + .symT = .hostFunc, .inner = .{ - .nativeFunc1 = .{ - .func = func, + .hostFunc = .{ + .func = @ptrCast(func), }, }, }; @@ -269,7 +270,7 @@ pub const Module = struct { const ModuleSymType = enum { variable, - nativeFunc1, + hostFunc, /// Symbol that points to one function signature. symToOneFunc, @@ -294,8 +295,8 @@ const ModuleSym = struct { } } = undefined, inner: union { - nativeFunc1: struct { - func: cy.NativeFuncPtr, + hostFunc: struct { + func: vmc.HostFuncFn, }, variable: struct { val: cy.Value, @@ -370,6 +371,35 @@ pub fn declareTypeObject(c: *cy.VMcompiler, modId: ModuleId, name: []const u8, c return objModId; } +pub fn declareHostFunc( + c: *cy.VMcompiler, modId: ModuleId, name: []const u8, funcSigId: sema.FuncSigId, + declId: sema.FuncDeclId, funcPtr: vmc.HostFuncFn, +) !void { + _ = declId; + const nameId = try sema.ensureNameSym(c, name); + const key = ModuleSymKey{ + .moduleSymKey = .{ + .nameId = nameId, + .funcSigId = funcSigId, + }, + }; + const mod = c.sema.getModulePtr(modId); + if (mod.syms.contains(key)) { + return error.DuplicateFuncSig; + } + + try mod.syms.put(c.alloc, key, .{ + .symT = .hostFunc, + .inner = .{ + .hostFunc = .{ + .func = funcPtr, + }, + }, + }); + + try declareFuncForNameSym(c, mod, nameId, funcSigId); +} + pub fn declareUserFunc( c: *cy.VMcompiler, modId: ModuleId, name: []const u8, funcSigId: sema.FuncSigId, declId: sema.FuncDeclId, hasStaticInitializer: bool @@ -471,7 +501,7 @@ pub fn findDistinctModuleSym(chunk: *cy.Chunk, modId: ModuleId, nameId: sema.Nam .typeAlias, .symToOneFunc, .userObject, - .nativeFunc1 => { + .hostFunc => { return true; }, .symToManyFuncs => { diff --git a/src/parser.zig b/src/parser.zig index 71c2283e7..9f2217728 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -51,6 +51,7 @@ const keywords = std.ComptimeStringMap(TokenType, .{ .{ "type", .type_k }, .{ "var", .var_k }, .{ "while", .while_k }, + .{ "hostfunc", .hostfunc_k }, }); const BlockState = struct { @@ -220,7 +221,12 @@ pub const Parser = struct { defer self.popBlock(); const indent = (try self.consumeIndentBeforeStmt()) orelse { - return self.reportParseError("Expected one statement.", &.{}); + self.nodes.items[root_id].head = .{ + .root = .{ + .headStmt = cy.NullId, + }, + }; + return 0; }; if (indent != 0) { return self.reportParseError("Unexpected indentation.", &.{}); @@ -811,6 +817,64 @@ pub const Parser = struct { } } + fn parseHostFuncDecl(self: *Parser) !NodeId { + const start = self.next_pos; + // Assumes first token is the `func` keyword. + self.advanceToken(); + + // Parse function name. + var token = self.peekToken(); + const left_pos = self.next_pos; + _ = left_pos; + if (token.tag() != .ident) { + return self.reportParseError("Expected function name identifier.", &.{}); + } + const name = try self.pushIdentNode(self.next_pos); + self.advanceToken(); + + token = self.peekToken(); + if (token.tag() == .left_paren) { + const paramHead = try self.parseFuncParams(); + const ret = try self.parseFuncReturn(); + try self.consumeNewLineOrEnd(); + + const nameToken = self.tokens.items[self.nodes.items[name].start_token]; + const nameStr = self.src[nameToken.pos()..nameToken.data.end_pos]; + const block = &self.block_stack.items[self.block_stack.items.len-1]; + try block.vars.put(self.alloc, nameStr, {}); + + const header = try self.pushNode(.funcHeader, start); + self.nodes.items[header].head = .{ + .funcHeader = .{ + .name = name, + .paramHead = paramHead orelse cy.NullId, + .ret = ret orelse cy.NullId, + }, + }; + + const id = try self.pushNode(.hostFuncDecl, start); + self.nodes.items[id].head = .{ + .func = .{ + .header = header, + .bodyHead = cy.NullId, + }, + }; + + if (!self.inObjectDecl) { + try self.staticDecls.append(self.alloc, .{ + .declT = .hostFunc, + .inner = .{ + .hostFunc = id, + }, + }); + } + + return id; + } else { + return self.reportParseError("Expected left paren.", &.{}); + } + } + fn parseFuncDecl(self: *Parser) !NodeId { const start = self.next_pos; // Assumes first token is the `func` keyword. @@ -1605,6 +1669,9 @@ pub const Parser = struct { .func_k => { return try self.parseFuncDecl(); }, + .hostfunc_k => { + return try self.parseHostFuncDecl(); + }, .if_k => { return try self.parseIfStatement(); }, @@ -3196,6 +3263,7 @@ pub const TokenType = enum(u8) { error_k, symbol, func_k, + hostfunc_k, is_k, coinit_k, coyield_k, @@ -3281,6 +3349,7 @@ pub const NodeType = enum { range_clause, eachClause, label_decl, + hostFuncDecl, funcDecl, funcDeclInit, funcHeader, @@ -4623,6 +4692,7 @@ const ParseExprOptions = struct { const StaticDeclType = enum { variable, typeAlias, + hostFunc, func, funcInit, import, @@ -4635,6 +4705,7 @@ const StaticDecl = struct { inner: extern union { variable: NodeId, typeAlias: NodeId, + hostFunc: NodeId, func: NodeId, funcInit: NodeId, import: NodeId, @@ -4653,6 +4724,6 @@ test "parser internals." { } try t.eq(@sizeOf(TokenizeState), 4); - try t.eq(std.enums.values(TokenType).len, 60); - try t.eq(keywords.kvs.len, 33); + try t.eq(std.enums.values(TokenType).len, 61); + try t.eq(keywords.kvs.len, 34); } \ No newline at end of file diff --git a/src/runtime.zig b/src/runtime.zig index 69f56e20d..309dfa3b7 100644 --- a/src/runtime.zig +++ b/src/runtime.zig @@ -69,21 +69,21 @@ pub const MethodType = enum { pub const MethodData = extern union { typedNative: extern struct { - ptr: cy.NativeObjFuncPtr, + ptr: vmc.HostFuncFn, funcSigId: sema.FuncSigId, /// Includes self param. numParams: u8, }, untypedNative1: extern struct { - ptr: cy.NativeObjFuncPtr, + ptr: vmc.HostFuncFn, numParams: u8, }, untypedNative2: extern struct { - ptr: cy.NativeObjFunc2Ptr, + ptr: cy.ZHostFuncPairFn, numParams: u8, }, optimizing: extern struct { - ptr: cy.OptimizingNativeMethod, + ptr: cy.OptimizingFuncFn, numParams: u8, }, typed: extern struct { @@ -124,12 +124,12 @@ pub const MethodInit = struct { }; } - pub fn initUntypedNative1(funcSigId: sema.FuncSigId, func: cy.NativeObjFuncPtr, numParams: u8) MethodInit { + pub fn initUntypedNative1(funcSigId: sema.FuncSigId, func: cy.ZHostFuncFn, numParams: u8) MethodInit { return .{ .type = .untypedNative1, .data = .{ .untypedNative1 = .{ - .ptr = func, + .ptr = @ptrCast(func), .numParams = numParams, }, }, @@ -137,7 +137,7 @@ pub const MethodInit = struct { }; } - pub fn initUntypedNative2(funcSigId: sema.FuncSigId, func: cy.NativeObjFunc2Ptr, numParams: u8) MethodInit { + pub fn initUntypedNative2(funcSigId: sema.FuncSigId, func: cy.ZHostFuncPairFn, numParams: u8) MethodInit { return .{ .type = .untypedNative2, .data = .{ @@ -165,13 +165,13 @@ pub const MethodInit = struct { }; } - pub fn initTypedNative(funcSigId: sema.FuncSigId, func: cy.NativeObjFuncPtr, numParams: u8) MethodInit { + pub fn initTypedNative(funcSigId: sema.FuncSigId, func: cy.ZHostFuncFn, numParams: u8) MethodInit { return .{ .type = .typedNative, .data = .{ .typedNative = .{ .funcSigId = funcSigId, - .ptr = func, + .ptr = @ptrCast(func), .numParams = numParams, }, }, @@ -179,7 +179,7 @@ pub const MethodInit = struct { }; } - pub fn initOptimizing(funcSigId: sema.FuncSigId, func: cy.OptimizingNativeMethod, numParams: u8) MethodInit { + pub fn initOptimizing(funcSigId: sema.FuncSigId, func: cy.OptimizingFuncFn, numParams: u8) MethodInit { return .{ .type = .optimizing, .data = .{ @@ -276,7 +276,7 @@ pub const FuncSymbolEntry = extern struct { }, } = undefined, inner: extern union { - nativeFunc1: cy.NativeFuncPtr, + nativeFunc1: vmc.HostFuncFn, func: extern struct { pc: u32, /// Stack size required by the func. @@ -287,7 +287,7 @@ pub const FuncSymbolEntry = extern struct { closure: *cy.Closure, }, - pub fn initNativeFunc1(func: cy.NativeFuncPtr, isTyped: bool, numParams: u32, funcSigId: sema.FuncSigId) FuncSymbolEntry { + pub fn initNativeFunc1(func: vmc.HostFuncFn, isTyped: bool, numParams: u32, funcSigId: sema.FuncSigId) FuncSymbolEntry { const isTypedMask: u16 = if (isTyped) 1 << 15 else 0; return .{ .entryT = @intFromEnum(FuncSymbolEntryType.nativeFunc1), diff --git a/src/sema.zig b/src/sema.zig index bc32d14c8..3ddbeaffe 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -536,6 +536,9 @@ pub fn semaStmt(c: *cy.Chunk, nodeId: cy.NodeId) !void { .funcDecl => { try funcDecl(c, nodeId); }, + .hostFuncDecl => { + return; + }, .whileCondStmt => { try pushIterSubBlock(c); @@ -673,6 +676,35 @@ pub fn declareTypeAlias(c: *cy.Chunk, nodeId: cy.NodeId) !void { }); } +pub fn getOrInitModule(self: *cy.Chunk, spec: []const u8, nodeId: cy.NodeId) !cy.ModuleId { + var resUri: cy.Str = undefined; + self.compiler.hasApiError = false; + if (!self.compiler.moduleResolver(@ptrCast(self.compiler.vm), self.id, cy.Str.initSlice(self.srcUri), cy.Str.initSlice(spec), &resUri)) { + if (self.compiler.hasApiError) { + return self.reportError(self.compiler.apiError, &.{}); + } else { + return self.reportError("Failed to resolve module.", &.{}); + } + } + + if (self.compiler.sema.moduleMap.get(resUri.slice())) |modId| { + return modId; + } else { + // resUri is duped. + const modId = try appendResolvedRootModule(self.compiler, resUri.slice()); + const dupedAbsSpec = self.compiler.sema.getModule(modId).absSpec; + + // Queue import task. + try self.compiler.importTasks.append(self.alloc, .{ + .chunkId = self.id, + .nodeId = nodeId, + .absSpec = dupedAbsSpec, + .modId = modId, + }); + return modId; + } +} + pub fn declareImport(chunk: *cy.Chunk, nodeId: cy.NodeId) !void { const node = chunk.nodes[nodeId]; const ident = chunk.nodes[node.head.left_right.left]; @@ -682,7 +714,8 @@ pub fn declareImport(chunk: *cy.Chunk, nodeId: cy.NodeId) !void { const spec = chunk.nodes[node.head.left_right.right]; const specPath = chunk.getNodeTokenString(spec); - const modId = try getOrLoadModule(chunk, specPath, nodeId); + const modId = try getOrInitModule(chunk, specPath, nodeId); + const mod = chunk.compiler.sema.getModule(modId); try setLocalSym(chunk, nameId, .{ .symId = mod.resolvedRootSymId, @@ -973,6 +1006,38 @@ fn funcDeclInit(c: *cy.Chunk, nodeId: cy.NodeId) !void { }; } +pub fn declareHostFunc(c: *cy.Chunk, nodeId: cy.NodeId) !void { + const declId = try appendFuncDecl(c, nodeId, true); + const func = &c.semaFuncDecls.items[declId]; + const name = func.getName(c); + const nameId = try ensureNameSym(c.compiler, name); + c.nodes[nodeId].head.func.semaDeclId = declId; + + const mod = c.getModule(); + try checkForDuplicateUsingSym(c, mod.resolvedRootSymId, nameId, func.getNameNode(c)); + + const info = cy.HostFuncInfo{ + .modId = c.modId, + .name = cy.Str.initSlice(name), + .funcSigId = func.funcSigId, + .idx = c.curHostFuncIdx, + }; + c.curHostFuncIdx += 1; + if (c.funcLoader.?(@ptrCast(c.compiler.vm), info)) |funcPtr| { + cy.module.declareHostFunc(c.compiler, c.modId, name, func.funcSigId, declId, funcPtr) catch |err| { + if (err == error.DuplicateSymName) { + return c.reportErrorAt("The symbol `{}` already exists.", &.{v(name)}, nodeId); + } else if (err == error.DuplicateFuncSig) { + return c.reportErrorAt("The function `{}` with the same signature already exists.", &.{v(name)}, nodeId); + } else return err; + }; + const key = ResolvedSymKey.initResolvedSymKey(mod.resolvedRootSymId, nameId); + _ = try resolveHostFunc(c, key, func.funcSigId, funcPtr); + } else { + return c.reportErrorAt("Host func `{}` failed to load.", &.{v(name)}, nodeId); + } +} + pub fn declareFunc(c: *cy.Chunk, nodeId: cy.NodeId) !void { const declId = try appendFuncDecl(c, nodeId, true); const func = &c.semaFuncDecls.items[declId]; @@ -2581,6 +2646,33 @@ fn getOrResolveDistinctSym(chunk: *cy.Chunk, parentSymId: SymbolId, nameId: Name } } + // Look in using modules. + for (chunk.usingModules.items) |modId| { + if (try cy.module.findDistinctModuleSym(chunk, modId, nameId)) { + const mod = chunk.compiler.sema.getModule(modId); + // Cache to local syms. + try setLocalSym(chunk, nameId, .{ + .symId = cy.NullId, + .funcSymId = cy.NullId, + .parentSymId = mod.resolvedRootSymId, + }); + + const csymId = (try resolveSymFromModule(chunk, modId, nameId, cy.NullId)).?; + if (csymId.isFuncSymId) { + const rFuncSym = chunk.compiler.sema.getFuncSym(csymId.id); + return SymbolResult{ + .symId = rFuncSym.key.resolvedFuncSymKey.symId, + .funcSymId = csymId.id, + }; + } else { + return SymbolResult{ + .symId = csymId.id, + .funcSymId = cy.NullId, + }; + } + } + } + return null; } @@ -2802,7 +2894,9 @@ fn getOrResolveSymForFuncCall( cy.panic("unexpected"); } } else { - try reportIncompatibleFuncSig(chunk, nameId, funcSigId, modId); + if (rParentSymId != chunk.semaRootSymId) { + try reportIncompatibleFuncSig(chunk, nameId, funcSigId, modId); + } } } @@ -2853,6 +2947,67 @@ fn getOrResolveSymForFuncCall( } } } + + // Look in using modules. + for (chunk.usingModules.items) |modId| { + if (try cy.module.findModuleSymForFuncCall(chunk, modId, nameId, args, ret, hasDynamicArg)) |modRes| { + const mod = chunk.compiler.sema.getModule(modId); + // Cache to local syms. + try setLocalSym(chunk, nameId, .{ + .symId = cy.NullId, + .funcSymId = cy.NullId, + .parentSymId = mod.resolvedRootSymId, + }); + + key = ResolvedSymKey.initResolvedSymKey(mod.resolvedRootSymId, nameId); + symId = chunk.compiler.sema.resolvedSymMap.get(key) orelse cy.NullId; + if (symId != cy.NullId) { + key = ResolvedFuncSymKey{ + .resolvedFuncSymKey = .{ + .symId = symId, + .funcSigId = modRes.funcSigId, + }, + }; + if (chunk.compiler.sema.resolvedFuncSymMap.get(key)) |funcSymId| { + const rFuncSym = chunk.compiler.sema.getFuncSym(funcSymId); + return FuncCallSymResult{ + .csymId = CompactSymbolId.initFuncSymId(funcSymId), + .retType = rFuncSym.retType, + .funcSigId = modRes.funcSigId, + .typeChecked = modRes.typeChecked, + }; + } + } + + if (try resolveSymFromModule(chunk, modId, nameId, modRes.funcSigId)) |csymId| { + if (csymId.isFuncSymId) { + const rFuncSym = chunk.compiler.sema.getFuncSym(csymId.id); + return FuncCallSymResult{ + .csymId = csymId, + .retType = rFuncSym.retType, + .funcSigId = modRes.funcSigId, + .typeChecked = modRes.typeChecked, + }; + } else { + return FuncCallSymResult{ + .csymId = csymId, + .retType = bt.Any, + .funcSigId = modRes.funcSigId, + .typeChecked = modRes.typeChecked, + }; + } + } else { + cy.panic("unexpected"); + } + } + } + + if (rParentSymId != cy.NullId) { + const parentSym = chunk.compiler.sema.getSymbol(rParentSymId); + const modId = parentSym.getModuleId().?; + try reportIncompatibleFuncSig(chunk, nameId, funcSigId, modId); + } + return null; } @@ -3009,8 +3164,8 @@ fn resolveSymFromModule(chunk: *cy.Chunk, modId: cy.ModuleId, nameId: NameSymId, }; switch (modSym.symT) { - .nativeFunc1 => { - const res = try resolveNativeFunc(chunk, key, funcSigId, modSym.inner.nativeFunc1.func); + .hostFunc => { + const res = try resolveHostFunc(chunk, key, funcSigId, modSym.inner.hostFunc.func); return CompactSymbolId.initFuncSymId(res.funcSymId); }, .variable => { @@ -3437,6 +3592,10 @@ fn endIterSubBlock(self: *cy.Chunk) !void { try endSubBlock(self); } +pub fn declareUsingModule(chunk: *cy.Chunk, modId: cy.ModuleId) !void { + try chunk.usingModules.append(chunk.alloc, modId); +} + pub fn importAllFromModule(self: *cy.Chunk, modId: cy.ModuleId) !void { const mod = self.compiler.sema.modules.items[modId]; var iter = mod.syms.iterator(); @@ -3453,7 +3612,7 @@ pub fn importAllFromModule(self: *cy.Chunk, modId: cy.ModuleId) !void { .parentSymId = mod.resolvedRootSymId, }); }, - .nativeFunc1 => { + .hostFunc => { // Skip exact func syms. }, else => { @@ -3463,57 +3622,6 @@ pub fn importAllFromModule(self: *cy.Chunk, modId: cy.ModuleId) !void { } } -/// Writes resolved spec to temp buf. -fn resolveSpecTemp(self: *cy.Chunk, spec: []const u8, outBuiltin: *bool) ![]const u8 { - if (self.compiler.moduleLoaders.contains(spec)) { - outBuiltin.* = true; - return spec; - } - - if (cy.isWasm) { - return error.NotSupported; - } - - if (std.mem.startsWith(u8, spec, "http://") or std.mem.startsWith(u8, spec, "https://")) { - outBuiltin.* = false; - const uri = try std.Uri.parse(spec); - if (std.mem.endsWith(u8, uri.host.?, "github.com")) { - if (std.mem.count(u8, uri.path, "/") == 2 and uri.path[uri.path.len-1] != '/') { - self.tempBufU8.clearRetainingCapacity(); - try self.tempBufU8.appendSlice(self.alloc, uri.scheme); - try self.tempBufU8.appendSlice(self.alloc, "://raw.githubusercontent.com"); - try self.tempBufU8.appendSlice(self.alloc, uri.path); - try self.tempBufU8.appendSlice(self.alloc, "/master/mod.cy"); - std.debug.print("{s}\n", .{self.tempBufU8.items}); - return self.tempBufU8.items; - } - } - return spec; - } - - self.tempBufU8.clearRetainingCapacity(); - - // Create path from the current script. - // There should always be a parent directory since `srcUri` should be absolute when dealing with file modules. - const dir = std.fs.path.dirname(self.srcUri) orelse return error.NoParentDir; - try self.tempBufU8.ensureTotalCapacity(self.alloc, dir.len + 1 + spec.len + std.fs.MAX_PATH_BYTES); - try self.tempBufU8.appendSlice(self.alloc, dir); - try self.tempBufU8.append(self.alloc, '/'); - try self.tempBufU8.appendSlice(self.alloc, spec); - const path = self.tempBufU8.items; - - // Get canonical path. - self.tempBufU8.items.len += std.fs.MAX_PATH_BYTES; - outBuiltin.* = false; - return std.fs.cwd().realpath(path, self.tempBufU8.items[path.len..]) catch |err| { - if (err == error.FileNotFound) { - return self.reportError("Import path does not exist: `{}`", &.{v(path)}); - } else { - return err; - } - }; -} - pub fn resolveEnumSym(c: *cy.VMcompiler, parentSymId: SymbolId, name: []const u8, modId: cy.ModuleId) !SymbolId { const nameId = try ensureNameSym(c, name); const key = ResolvedSymKey{ @@ -3701,8 +3809,8 @@ const SymbolResult = struct { } }; -fn resolveNativeFunc( - c: *cy.Chunk, key: ResolvedSymKey, funcSigId: FuncSigId, func: cy.NativeFuncPtr, +fn resolveHostFunc( + c: *cy.Chunk, key: ResolvedSymKey, funcSigId: FuncSigId, func: vmc.HostFuncFn, ) !SymbolResult { const parentSymId = key.resolvedSymKey.parentSymId; const nameId = key.resolvedSymKey.nameId; @@ -4246,28 +4354,6 @@ pub fn appendResolvedRootModule(c: *cy.VMcompiler, absSpec: []const u8) !cy.Modu return modId; } -pub fn getOrLoadModule(self: *cy.Chunk, spec: []const u8, nodeId: cy.NodeId) !cy.ModuleId { - var isBuiltin: bool = undefined; - const absSpec = try resolveSpecTemp(self, spec, &isBuiltin); - - if (self.compiler.sema.moduleMap.get(absSpec)) |modId| { - return modId; - } else { - const modId = try appendResolvedRootModule(self.compiler, absSpec); - const dupedAbsSpec = self.compiler.sema.getModule(modId).absSpec; - - // Queue import task. - try self.compiler.importTasks.append(self.alloc, .{ - .chunkId = self.id, - .nodeId = nodeId, - .absSpec = dupedAbsSpec, - .modId = modId, - .builtin = isBuiltin, - }); - return modId; - } -} - test "sema internals." { if (builtin.mode == .Debug) { if (cy.is32Bit) { diff --git a/src/std/os.cy b/src/std/os.cy new file mode 100644 index 000000000..3345a1d00 --- /dev/null +++ b/src/std/os.cy @@ -0,0 +1,36 @@ +hostfunc access(path any, mode symbol) any +hostfunc args() List +hostfunc bindLib(path any, decls List) any +hostfunc bindLib(path any, decls List, config Map) any +hostfunc cacheUrl(url any) any +hostfunc copyFile(srcPath any, dstPath any) any +hostfunc createDir(path any) any +hostfunc createFile(path any, truncate boolean) any +hostfunc cstr(s any) pointer +hostfunc cwd() string +hostfunc dirName(path any) any +hostfunc execCmd(args List) any +hostfunc exePath() string +hostfunc exit(status int) none +hostfunc fetchUrl(url any) any +hostfunc free(ptr pointer) none +hostfunc fromCstr(ptr pointer) rawstring +hostfunc getEnv(key any) any +hostfunc getEnvAll() Map +hostfunc getInput() any +hostfunc malloc(size int) pointer +hostfunc milliTime() float +hostfunc openDir(path any) any +hostfunc openDir(path any, iterable boolean) any +hostfunc openFile(path any, mode symbol) any +hostfunc parseArgs(options List) Map +hostfunc readAll() any +hostfunc readFile(path any) any +hostfunc readLine() any +hostfunc realPath(path any) any +hostfunc removeDir(path any) any +hostfunc removeFile(path any) any +hostfunc setEnv(key any, val any) none +hostfunc sleep(ms float) none +hostfunc unsetEnv(key any) none +hostfunc writeFile(path any, contents any) any \ No newline at end of file diff --git a/src/builtins/os.zig b/src/std/os.zig similarity index 63% rename from src/builtins/os.zig rename to src/std/os.zig index 1995230c0..9d564b20d 100644 --- a/src/builtins/os.zig +++ b/src/std/os.zig @@ -3,16 +3,19 @@ const stdx = @import("stdx"); const fatal = cy.fatal; const builtin = @import("builtin"); const cy = @import("../cyber.zig"); +const vmc = cy.vmc; const rt = cy.rt; const Value = cy.Value; const vm_ = @import("../vm.zig"); const fmt = @import("../fmt.zig"); -const bindings = @import("bindings.zig"); +const bindings = @import("../builtins/bindings.zig"); const Symbol = bindings.Symbol; const prepareThrowSymbol = bindings.prepareThrowSymbol; const fromUnsupportedError = bindings.fromUnsupportedError; const bt = cy.types.BuiltinTypeSymIds; const ffi = @import("os_ffi.zig"); +const http = @import("../http.zig"); +const cache = @import("../cache.zig"); const log = cy.log.scoped(.os); @@ -20,7 +23,61 @@ pub var CFuncT: rt.TypeId = undefined; pub var CStructT: rt.TypeId = undefined; pub var CArrayT: rt.TypeId = undefined; -pub fn initModule(self: *cy.VMcompiler, modId: cy.ModuleId) linksection(cy.InitSection) anyerror!void { +pub const Src = @embedFile("os.cy"); +pub fn defaultFuncLoader(_: *cy.UserVM, func: cy.HostFuncInfo) callconv(.C) vmc.HostFuncFn { + if (std.mem.eql(u8, funcs[func.idx].@"0", func.name.slice())) { + return @ptrCast(funcs[func.idx].@"1"); + } + return null; +} + +const NameHostFunc = struct { []const u8, cy.ZHostFuncFn }; +const funcs = [_]NameHostFunc{ + .{"access", access}, + .{"args", osArgs}, + .{"bindLib", bindLib}, + .{"bindLib", bindLibExt}, + .{"cacheUrl", cacheUrl}, + .{"copyFile", copyFile}, + .{"createDir", createDir}, + .{"createFile", createFile}, + .{"cstr", cstr}, + .{"cwd", cwd}, + .{"dirName", dirName}, + .{"execCmd", execCmd}, + .{"exePath", exePath}, + .{"exit", exit}, + .{"fetchUrl", fetchUrl}, + .{"free", free}, + .{"fromCstr", fromCstr}, + .{"getEnv", getEnv}, + .{"getEnvAll", getEnvAll}, + .{"getInput", getInput}, + .{"malloc", malloc}, + .{"milliTime", milliTime}, + .{"openDir", openDir}, + .{"openDir", openDir2}, + .{"openFile", openFile}, + .{"parseArgs", parseArgs}, + .{"readAll", readAll}, + .{"readFile", readFile}, + .{"readLine", readLine}, + .{"realPath", realPath}, + .{"removeDir", removeDir}, + .{"removeFile", removeFile}, + .{"setEnv", setEnv}, + .{"sleep", sleep}, + .{"unsetEnv", unsetEnv}, + .{"writeFile", writeFile}, +}; + +pub fn postLoad(vm: *cy.UserVM, modId: cy.ModuleId) callconv(.C) void { + zPostLoad(vm.internal().compiler, modId) catch |err| { + cy.panicFmt("os module: {}", .{err}); + }; +} + +fn zPostLoad(self: *cy.VMcompiler, modId: cy.ModuleId) linksection(cy.InitSection) anyerror!void { const b = bindings.ModuleBuilder.init(self, modId); // Object Types. @@ -37,10 +94,13 @@ pub fn initModule(self: *cy.VMcompiler, modId: cy.ModuleId) linksection(cy.InitS } if (cy.hasStdFiles) { const stdin = try cy.heap.allocFile(self.vm, std.io.getStdIn().handle); + stdin.asHeapObject().file.closeOnFree = false; try b.setVar("stdin", bt.Any, stdin); const stdout = try cy.heap.allocFile(self.vm, std.io.getStdOut().handle); + stdout.asHeapObject().file.closeOnFree = false; try b.setVar("stdout", bt.Any, stdout); const stderr = try cy.heap.allocFile(self.vm, std.io.getStdErr().handle); + stderr.asHeapObject().file.closeOnFree = false; try b.setVar("stderr", bt.Any, stderr); } else { try b.setVar("stdin", bt.Any, Value.None); @@ -54,111 +114,30 @@ pub fn initModule(self: *cy.VMcompiler, modId: cy.ModuleId) linksection(cy.InitS } else { try b.setVar("vecBitSize", bt.Integer, cy.Value.initI32(0)); } - - // Functions. - if (cy.isWasm) { - try b.setFunc("access", &.{bt.Any, bt.Symbol}, bt.Any, bindings.nop1); - } else { - try b.setFunc("access", &.{bt.Any, bt.Symbol}, bt.Any, access); - } - if (cy.isWasm) { - try b.setFunc("args", &.{}, bt.List, bindings.nop0); - } else { - try b.setFunc("args", &.{}, bt.List, osArgs); - } - if (cy.hasJit) { - try b.setFunc("bindLib", &.{bt.Any, bt.List}, bt.Any, bindLib); - try b.setFunc("bindLib", &.{bt.Any, bt.List, bt.Map}, bt.Any, bindLibExt); - } else { - try b.setFunc("bindLib", &.{bt.Any, bt.List}, bt.Any, bindings.nop2); - try b.setFunc("bindLib", &.{bt.Any, bt.List, bt.Map}, bt.Any, bindings.nop3); - } - if (cy.isWasm) { - try b.setFunc("copyFile", &.{bt.Any, bt.Any}, bt.Any, bindings.nop2); - try b.setFunc("createDir", &.{bt.Any}, bt.Any, bindings.nop1); - try b.setFunc("createFile", &.{bt.Any, bt.Boolean}, bt.Any, bindings.nop2); - try b.setFunc("cstr", &.{bt.Any}, bt.Pointer, bindings.nop1); - try b.setFunc("cwd", &.{}, bt.String, bindings.nop0); - try b.setFunc("dirName", &.{ bt.Any }, bt.Any, bindings.nop1); - try b.setFunc("exePath", &.{}, bt.String, bindings.nop0); - try b.setFunc("free", &.{bt.Pointer}, bt.None, bindings.nop1); - try b.setFunc("fromCstr", &.{bt.Pointer}, bt.Rawstring, bindings.nop1); - try b.setFunc("getEnv", &.{ bt.Any }, bt.Any, bindings.nop1); - try b.setFunc("getEnvAll", &.{}, bt.Map, bindings.nop0); - try b.setFunc("malloc", &.{bt.Integer}, bt.Pointer, bindings.nop1); - } else { - try b.setFunc("copyFile", &.{bt.Any, bt.Any}, bt.Any, copyFile); - try b.setFunc("createDir", &.{bt.Any}, bt.Any, createDir); - try b.setFunc("createFile", &.{bt.Any, bt.Boolean}, bt.Any, createFile); - try b.setFunc("cstr", &.{bt.Any}, bt.Pointer, cstr); - try b.setFunc("cwd", &.{}, bt.String, cwd); - try b.setFunc("dirName", &.{ bt.Any }, bt.Any, dirName); - try b.setFunc("exePath", &.{}, bt.String, exePath); - try b.setFunc("free", &.{bt.Pointer}, bt.None, free); - try b.setFunc("fromCstr", &.{bt.Pointer}, bt.Rawstring, fromCstr); - if (builtin.os.tag == .windows) { - try b.setFunc("getEnv", &.{ bt.Any }, bt.Any, bindings.nop1); - try b.setFunc("getEnvAll", &.{}, bt.Map, bindings.nop0); - } else { - try b.setFunc("getEnv", &.{ bt.Any }, bt.Any, getEnv); - try b.setFunc("getEnvAll", &.{}, bt.Map, getEnvAll); - } - try b.setFunc("malloc", &.{bt.Integer}, bt.Pointer, malloc); - } - try b.setFunc("milliTime", &.{}, bt.Float, milliTime); - if (cy.isWasm) { - try b.setFunc("openDir", &.{bt.Any}, bt.Any, bindings.nop1); - try b.setFunc("openDir", &.{bt.Any, bt.Boolean}, bt.Any, bindings.nop2); - try b.setFunc("openFile", &.{bt.Any, bt.Symbol}, bt.Any, bindings.nop2); - try b.setFunc("parseArgs", &.{ bt.List }, bt.Map, bindings.nop1); - try b.setFunc("realPath", &.{bt.Any}, bt.Any, bindings.nop1); - try b.setFunc("removeDir", &.{bt.Any}, bt.Any, bindings.nop1); - try b.setFunc("removeFile", &.{bt.Any}, bt.Any, bindings.nop1); - try b.setFunc("setEnv", &.{bt.Any, bt.Any}, bt.None, bindings.nop2); - } else { - try b.setFunc("openDir", &.{bt.Any}, bt.Any, openDir); - try b.setFunc("openDir", &.{bt.Any, bt.Boolean}, bt.Any, openDir2); - try b.setFunc("openFile", &.{bt.Any, bt.Symbol}, bt.Any, openFile); - try b.setFunc("parseArgs", &.{ bt.List }, bt.Map, parseArgs); - try b.setFunc("realPath", &.{bt.Any}, bt.Any, realPath); - try b.setFunc("removeDir", &.{bt.Any}, bt.Any, removeDir); - try b.setFunc("removeFile", &.{bt.Any}, bt.Any, removeFile); - if (builtin.os.tag == .windows) { - try b.setFunc("setEnv", &.{bt.Any, bt.Any}, bt.None, bindings.nop2); - } else { - try b.setFunc("setEnv", &.{bt.Any, bt.Any}, bt.None, setEnv); - } - } - try b.setFunc("sleep", &.{bt.Float}, bt.None, sleep); - if (cy.isWasm or builtin.os.tag == .windows) { - try b.setFunc("unsetEnv", &.{bt.Any}, bt.None, bindings.nop1); - } else { - try b.setFunc("unsetEnv", &.{bt.Any}, bt.None, unsetEnv); - } } -pub fn deinitModule(c: *cy.VMcompiler, mod: cy.Module) !void { +pub fn destroy(vm: *cy.UserVM, modId: cy.ModuleId) callconv(.C) void { + const c = vm.internal().compiler; + const mod = c.sema.getModulePtr(modId); if (cy.hasStdFiles) { - // Mark as closed to avoid closing. - const stdin = (try mod.getVarVal(c, "stdin")).?; - stdin.asHeapObject().file.closed = true; + const stdin = (mod.getVarVal(c, "stdin") catch cy.fatal()).?; cy.arc.release(c.vm, stdin); - const stdout = (try mod.getVarVal(c, "stdout")).?; - stdout.asHeapObject().file.closed = true; + const stdout = (mod.getVarVal(c, "stdout") catch cy.fatal()).?; cy.arc.release(c.vm, stdout); - const stderr = (try mod.getVarVal(c, "stderr")).?; - stderr.asHeapObject().file.closed = true; + const stderr = (mod.getVarVal(c, "stderr") catch cy.fatal()).?; cy.arc.release(c.vm, stderr); } } fn openDir(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); return openDir2(vm, &[_]Value{ args[0], Value.False }, nargs); } fn openDir2(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); const path = vm.valueToTempRawString(args[0]); const iterable = args[1].asBool(); var fd: std.os.fd_t = undefined; @@ -185,6 +164,7 @@ fn openDir2(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSecti } fn removeDir(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); const path = vm.valueToTempRawString(args[0]); std.fs.cwd().deleteDir(path) catch |err| { if (err == error.FileNotFound) { @@ -197,6 +177,9 @@ fn removeDir(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSect } fn copyFile(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (cy.isWasm) { + return vm.returnPanic("Unsupported."); + } const src = vm.valueToTempRawString(args[0]); const alloc = vm.allocator(); const srcDupe = alloc.dupe(u8, src) catch fatal(); @@ -213,6 +196,7 @@ fn copyFile(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSecti } fn removeFile(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); const path = vm.valueToTempRawString(args[0]); std.fs.cwd().deleteFile(path) catch |err| { if (err == error.FileNotFound) { @@ -225,6 +209,7 @@ fn removeFile(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSec } fn createDir(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); const path = vm.valueToTempRawString(args[0]); std.fs.cwd().makeDir(path) catch |err| { return fromUnsupportedError(vm, "createDir", err, @errorReturnTrace()); @@ -233,6 +218,7 @@ fn createDir(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSect } fn createFile(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); const path = vm.valueToTempRawString(args[0]); const truncate = args[1].asBool(); const file = std.fs.cwd().createFile(path, .{ .truncate = truncate }) catch |err| { @@ -242,6 +228,10 @@ fn createFile(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSec } pub fn access(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { + if (cy.isWasm) { + return vm.returnPanic("Unsupported."); + } + const path = vm.valueToTempRawString(args[0]); const mode: Symbol = @enumFromInt(args[1].asSymbolId()); @@ -266,6 +256,7 @@ pub fn access(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { } fn openFile(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); const path = vm.valueToTempRawString(args[0]); const mode: Symbol = @enumFromInt(args[1].asSymbolId()); const zmode: std.fs.File.OpenMode = switch (mode) { @@ -287,6 +278,7 @@ fn openFile(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSecti } fn parseArgs(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); const ivm = vm.internal(); const alloc = vm.allocator(); @@ -410,6 +402,9 @@ fn parseArgs(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSect } fn osArgs(vm: *cy.UserVM, _: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (cy.isWasm) { + return vm.returnPanic("Unsupported."); + } const alloc = vm.allocator(); var iter = std.process.argsWithAllocator(alloc) catch fatal(); defer iter.deinit(); @@ -423,6 +418,7 @@ fn osArgs(vm: *cy.UserVM, _: [*]const Value, _: u8) linksection(cy.StdSection) V } pub fn cwd(vm: *cy.UserVM, _: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); const res = std.process.getCwdAlloc(vm.allocator()) catch fatal(); defer vm.allocator().free(res); // TODO: Use allocOwnedString @@ -430,6 +426,7 @@ pub fn cwd(vm: *cy.UserVM, _: [*]const Value, _: u8) linksection(cy.StdSection) } pub fn exePath(vm: *cy.UserVM, _: [*]const Value, _: u8) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); const path = std.fs.selfExePathAlloc(vm.allocator()) catch fatal(); defer vm.allocator().free(path); // TODO: Use allocOwnedString @@ -437,12 +434,14 @@ pub fn exePath(vm: *cy.UserVM, _: [*]const Value, _: u8) Value { } pub fn getEnv(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { + if (cy.isWasm or builtin.os.tag == .windows) return vm.returnPanic("Unsupported."); const key = vm.valueToTempRawString(args[0]); const res = std.os.getenv(key) orelse return Value.None; return vm.allocStringInfer(res) catch cy.fatal(); } pub fn getEnvAll(vm: *cy.UserVM, _: [*]const Value, _: u8) Value { + if (cy.isWasm or builtin.os.tag == .windows) return vm.returnPanic("Unsupported."); var env = std.process.getEnvMap(vm.allocator()) catch cy.fatal(); defer env.deinit(); @@ -460,24 +459,28 @@ pub fn getEnvAll(vm: *cy.UserVM, _: [*]const Value, _: u8) Value { return map; } -pub fn free(_: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { +pub fn free(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); const ptr = args[0].asHeapObject().pointer.ptr; std.c.free(ptr); return Value.None; } pub fn malloc(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); const size: usize = @intCast(args[0].asInteger()); const ptr = std.c.malloc(size); return cy.heap.allocPointer(vm.internal(), ptr) catch cy.fatal(); } fn fromCstr(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); const bytes = std.mem.span(@as([*:0]const u8, @ptrCast(args[0].asHeapObject().pointer.ptr))); return vm.allocRawString(bytes) catch fatal(); } fn cstr(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); const bytes = vm.valueToTempRawString(args[0]); const new: [*]u8 = @ptrCast(std.c.malloc(bytes.len + 1)); @memcpy(new[0..bytes.len], bytes); @@ -490,6 +493,7 @@ pub fn milliTime(_: *cy.UserVM, _: [*]const Value, _: u8) linksection(cy.StdSect } pub fn dirName(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); const path = vm.valueToTempRawString(args[0]); if (std.fs.path.dirname(path)) |res| { return vm.allocStringInfer(res) catch cy.fatal(); @@ -499,6 +503,7 @@ pub fn dirName(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { } pub fn realPath(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); const path = vm.valueToTempRawString(args[0]); const res = std.fs.cwd().realpathAlloc(vm.allocator(), path) catch |err| { return fromUnsupportedError(vm, "realPath", err, @errorReturnTrace()); @@ -509,6 +514,7 @@ pub fn realPath(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { } pub fn setEnv(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { + if (cy.isWasm or builtin.os.tag == .windows) return vm.returnPanic("Unsupported."); const key = vm.valueToTempRawString(args[0]); const keyz = vm.allocator().dupeZ(u8, key) catch cy.fatal(); defer vm.allocator().free(keyz); @@ -541,6 +547,7 @@ pub fn sleep(_: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSecti extern fn hostSleep(secs: u64, nsecs: u64) void; pub fn unsetEnv(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { + if (cy.isWasm or builtin.os.tag == .windows) return vm.returnPanic("Unsupported."); const key = vm.valueToTempRawString(args[0]); const keyz = vm.allocator().dupeZ(u8, key) catch cy.fatal(); defer vm.allocator().free(keyz); @@ -550,6 +557,9 @@ pub fn unsetEnv(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { pub extern "c" fn unsetenv(name: [*:0]const u8) c_int; pub fn bindLib(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (!cy.hasFFI) { + return vm.returnPanic("Unsupported."); + } return @call(.never_inline, ffi.bindLib, .{vm, args, .{}}) catch |err| { if (builtin.mode == .Debug) { std.debug.dumpStackTrace(@errorReturnTrace().?.*); @@ -563,6 +573,9 @@ pub fn bindLib(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSe } pub fn bindLibExt(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (!cy.hasFFI) { + return vm.returnPanic("Unsupported."); + } var configV = args[2]; const ivm = vm.internal(); const genMapV = vm.allocAstring("genMap") catch cy.fatal(); @@ -582,4 +595,177 @@ pub fn bindLibExt(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.St return fromUnsupportedError(vm, "bindLib", err, @errorReturnTrace()); } }; +} + +pub extern fn hostFileWrite(fid: u32, str: [*]const u8, strLen: usize) void; + +fn cacheUrl(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); + const alloc = vm.allocator(); + const url = vm.valueToTempString(args[0]); + + const specGroup = cache.getSpecHashGroup(alloc, url) catch cy.fatal(); + defer specGroup.deinit(alloc); + + if (vm.internal().config.reload) { + specGroup.markEntryBySpecForRemoval(url) catch cy.fatal(); + } else { + // First check local cache. + if (specGroup.findEntryBySpec(url) catch cy.fatal()) |entry| { + const path = cache.allocSpecFilePath(alloc, entry) catch cy.fatal(); + defer alloc.free(path); + return vm.allocStringInfer(path) catch cy.fatal(); + } + } + + const resp = http.get(alloc, vm.internal().httpClient, url) catch |err| { + log.debug("cacheUrl error: {}", .{err}); + return prepareThrowSymbol(vm, .UnknownError); + }; + defer alloc.free(resp.body); + if (resp.status != .ok) { + log.debug("cacheUrl response status: {}", .{resp.status}); + return prepareThrowSymbol(vm, .UnknownError); + } else { + const entry = cache.saveNewSpecFile(alloc, specGroup, url, resp.body) catch cy.fatal(); + defer entry.deinit(alloc); + const path = cache.allocSpecFilePath(alloc, entry) catch cy.fatal(); + defer alloc.free(path); + return vm.allocStringInfer(path) catch cy.fatal(); + } +} + +pub fn execCmd(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); + const alloc = vm.allocator(); + const ivm = vm.internal(); + + const obj = args[0].asHeapObject(); + var buf: std.ArrayListUnmanaged([]const u8) = .{}; + defer { + for (buf.items) |arg| { + alloc.free(arg); + } + buf.deinit(alloc); + } + for (obj.list.items()) |arg| { + buf.append(alloc, vm.valueToString(arg) catch cy.fatal()) catch cy.fatal(); + } + + const res = std.ChildProcess.exec(.{ + .allocator = alloc, + .argv = buf.items, + .max_output_bytes = 1024 * 1024 * 10, + }) catch |err| { + switch (err) { + error.FileNotFound => + return prepareThrowSymbol(vm, .FileNotFound), + error.StdoutStreamTooLong => + return prepareThrowSymbol(vm, .StreamTooLong), + error.StderrStreamTooLong => + return prepareThrowSymbol(vm, .StreamTooLong), + else => cy.panicFmt("exec err {}\n", .{err}), + } + }; + + const map = vm.allocEmptyMap() catch cy.fatal(); + const outKey = vm.allocAstring("out") catch cy.fatal(); + const errKey = vm.allocAstring("err") catch cy.fatal(); + defer { + vm.release(outKey); + vm.release(errKey); + } + + // TODO: Use allocOwnedString + defer alloc.free(res.stdout); + const out = vm.allocStringInfer(res.stdout) catch cy.fatal(); + defer vm.release(out); + map.asHeapObject().map.set(ivm, outKey, out) catch cy.fatal(); + // TODO: Use allocOwnedString + defer alloc.free(res.stderr); + const err = vm.allocStringInfer(res.stderr) catch cy.fatal(); + defer vm.release(err); + map.asHeapObject().map.set(ivm, errKey, err) catch cy.fatal(); + if (res.term == .Exited) { + const exitedKey = vm.allocAstring("exited") catch cy.fatal(); + defer vm.release(exitedKey); + map.asHeapObject().map.set(ivm, exitedKey, Value.initF64(@floatFromInt(res.term.Exited))) catch cy.fatal(); + } + return map; +} + +pub fn exit(_: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + const status: u8 = @intCast(args[0].asInteger()); + std.os.exit(status); +} + +pub fn fetchUrl(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { + if (cy.isWasm) return vm.returnPanic("Unsupported."); + const alloc = vm.allocator(); + const url = vm.valueToTempString(args[0]); + if (cy.isWasm) { + hostFetchUrl(url.ptr, url.len); + return Value.None; + } else { + const resp = http.get(alloc, vm.internal().httpClient, url) catch |err| { + log.debug("fetchUrl error: {}", .{err}); + return prepareThrowSymbol(vm, .UnknownError); + }; + defer alloc.free(resp.body); + // TODO: Use allocOwnedString + return vm.allocRawString(resp.body) catch cy.fatal(); + } +} + +extern fn hostFetchUrl(url: [*]const u8, urlLen: usize) void; + +pub fn getInput(vm: *cy.UserVM, _: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (!cy.hasStdFiles) return vm.returnPanic("Unsupported."); + const input = std.io.getStdIn().reader().readUntilDelimiterAlloc(vm.allocator(), '\n', 10e8) catch |err| { + if (err == error.EndOfStream) { + return prepareThrowSymbol(vm, .EndOfStream); + } else cy.fatal(); + }; + defer vm.allocator().free(input); + // TODO: Use allocOwnedString + return vm.allocRawString(input) catch cy.fatal(); +} + +pub fn readAll(vm: *cy.UserVM, _: [*]const Value, _: u8) Value { + if (!cy.hasStdFiles) return vm.returnPanic("Unsupported."); + const input = std.io.getStdIn().readToEndAlloc(vm.allocator(), 10e8) catch cy.fatal(); + defer vm.allocator().free(input); + // TODO: Use allocOwnString. + return vm.allocRawString(input) catch cy.fatal(); +} + +pub fn readFile(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { + if (!cy.hasStdFiles) return vm.returnPanic("Unsupported."); + const path = vm.valueToTempRawString(args[0]); + const content = std.fs.cwd().readFileAlloc(vm.allocator(), path, 10e8) catch |err| { + if (err == error.FileNotFound) { + return prepareThrowSymbol(vm, .FileNotFound); + } + log.debug("readFile {}", .{err}); + return prepareThrowSymbol(vm, .UnknownError); + }; + defer vm.allocator().free(content); + // TODO: Use allocOwnedString. + return vm.allocRawString(content) catch cy.fatal(); +} + +pub fn readLine(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { + if (!cy.hasStdFiles) return vm.returnPanic("Unsupported."); + fmt.printDeprecated("readLine", "0.1", "Use getInput() instead.", &.{}); + return getInput(vm, args, nargs); +} + +pub fn writeFile(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { + if (!cy.hasStdFiles) return vm.returnPanic("Unsupported."); + const path = vm.valueToTempRawString(args[0]); + const pathDupe = vm.allocator().dupe(u8, path) catch fatal(); + defer vm.allocator().free(pathDupe); + const content = vm.valueToTempRawString(args[1]); + std.fs.cwd().writeFile(path, content) catch cy.fatal(); + return Value.None; } \ No newline at end of file diff --git a/src/builtins/os_ffi.zig b/src/std/os_ffi.zig similarity index 97% rename from src/builtins/os_ffi.zig rename to src/std/os_ffi.zig index 6d6feaba6..e322d3eb3 100644 --- a/src/builtins/os_ffi.zig +++ b/src/std/os_ffi.zig @@ -5,7 +5,7 @@ const tcc = @import("tcc"); const cy = @import("../cyber.zig"); const rt = cy.rt; const Value = cy.Value; -const bindings = @import("bindings.zig"); +const bindings = @import("../builtins/bindings.zig"); const prepareThrowSymbol = bindings.prepareThrowSymbol; const Symbol = bindings.Symbol; const os = @import("os.zig"); @@ -597,12 +597,13 @@ pub fn bindLib(vm: *cy.UserVM, args: [*]const Value, config: BindLibConfig) !Val try w.print("}}\n", .{}); // Generate ptrTo[Object]. - if (config.genMap) { - try w.print("uint64_t cyPtrTo{s}(UserVM* vm, uint64_t* args, char numArgs) {{\n", .{ivm.getTypeName(objSymId)}); + const isMethod = !config.genMap; + try w.print("uint64_t cyPtrTo{s}(UserVM* vm, uint64_t* args, char numArgs) {{\n", .{ivm.getTypeName(objSymId)}); + if (isMethod) { + try w.print(" uint64_t ptr = *((uint64_t*)(args[1] & ~PointerMask) + 1);\n", .{}); } else { - try w.print("uint64_t cyPtrTo{s}(UserVM* vm, uint64_t recv, uint64_t* args, char numArgs) {{\n", .{ivm.getTypeName(objSymId)}); + try w.print(" uint64_t ptr = *((uint64_t*)(args[0] & ~PointerMask) + 1);\n", .{}); } - try w.print(" uint64_t ptr = *((uint64_t*)(args[0] & ~PointerMask) + 1);\n", .{}); try w.print(" uint64_t res = fromStruct{}(vm, *(Struct{}*)ptr);\n", .{objSymId, objSymId}); try w.print(" return res;\n", .{}); try w.print("}}\n", .{}); @@ -771,7 +772,7 @@ pub fn bindLib(vm: *cy.UserVM, args: [*]const Value, config: BindLibConfig) !Val const cargsv = try ivm.getField2(cfunc.decl, argsf); const cargs = cargsv.asPointer(*cy.CyList).items(); - const func = cy.ptrAlignCast(cy.NativeObjFuncPtr, funcPtr); + const func = cy.ptrAlignCast(cy.ZHostFuncFn, funcPtr); const mgId = try ivm.ensureMethodGroup(sym); try @call(.never_inline, cy.VM.addMethod, .{ivm, sid, mgId, rt.MethodInit.initTypedNative(cfunc.funcSigId, func, @as(u8, @intCast(cargs.len)) + 1) }); @@ -786,7 +787,7 @@ pub fn bindLib(vm: *cy.UserVM, args: [*]const Value, config: BindLibConfig) !Val const funcPtr = tcc.tcc_get_symbol(state, symGen.ptr) orelse { cy.panic("Failed to get symbol."); }; - const func = cy.ptrAlignCast(cy.NativeObjFuncPtr, funcPtr); + const func = cy.ptrAlignCast(cy.ZHostFuncFn, funcPtr); const methodName = try std.fmt.allocPrint(alloc, "ptrTo{s}", .{rtTypeName}); defer alloc.free(methodName); @@ -831,27 +832,28 @@ fn genCFunc(ctx: *Context, vm: *cy.UserVM, w: anytype, cfunc: CFuncData) !void { } try w.print(");\n", .{}); - if (ctx.config.genMap) { - try w.print("uint64_t cy{s}(UserVM* vm, uint64_t* args, char numArgs) {{\n", .{sym}); - } else { - try w.print("uint64_t cy{s}(UserVM* vm, uint64_t recv, uint64_t* args, char numArgs) {{\n", .{sym}); + const isMethod = !ctx.config.genMap; + try w.print("uint64_t cy{s}(UserVM* vm, uint64_t* args, char numArgs) {{\n", .{sym}); + if (isMethod) { + try w.print(" uint64_t recv = args[0];\n", .{}); } // w.print(" printF64(*(double*)&args[0]);\n", .{}) catch cy.fatal(); // Temps. if (cargs.len > 0) { for (cargs, 0..) |carg, i| { + const argIdx = if (isMethod) i + 1 else i; if (carg.isObjectType(os.CArrayT)) { const n: u32 = @intCast((try ivm.getField(carg, ctx.nField)).asInteger()); const elem = try ivm.getField(carg, ctx.elemField); const elemName = try ctx.getTempBaseTypeName(elem); try w.print(" ", .{}); - const cval = try std.fmt.bufPrint(&Context.buf, "arr{}", .{i}); + const cval = try std.fmt.bufPrint(&Context.buf, "arr{}", .{argIdx}); try ctx.genNamedCType(w, carg, cval); try w.print(";\n", .{}); - try w.print(" to{s}Array{}(vm, args[{}], &arr{}[0]);", .{elemName, n, i, i}); + try w.print(" to{s}Array{}(vm, args[{}], &arr{}[0]);", .{elemName, n, argIdx, argIdx}); } else { continue; } @@ -905,10 +907,11 @@ fn genCFunc(ctx: *Context, vm: *cy.UserVM, w: anytype, cfunc: CFuncData) !void { if (cargs.len > 0) { const lastArg = cargs.len-1; for (cargs, 0..) |carg, i| { + const argIdx = if (isMethod) i + 1 else i; if (carg.isObjectType(os.CArrayT)) { - try w.print("arr{}", .{i}); + try w.print("arr{}", .{argIdx}); } else { - try ctx.genToCValueFromArg(w, carg, i); + try ctx.genToCValueFromArg(w, carg, argIdx); } if (i != lastArg) { try w.print(", ", .{}); diff --git a/src/std/test.cy b/src/std/test.cy new file mode 100644 index 000000000..bc93503e0 --- /dev/null +++ b/src/std/test.cy @@ -0,0 +1,4 @@ +hostfunc eq(a any, b any) any +hostfunc eqList(a any, b any) any +hostfunc eqNear(a any, b any) any +hostfunc fail() any \ No newline at end of file diff --git a/src/builtins/test.zig b/src/std/test.zig similarity index 91% rename from src/builtins/test.zig rename to src/std/test.zig index 07a76e617..a93dda138 100644 --- a/src/builtins/test.zig +++ b/src/std/test.zig @@ -2,26 +2,38 @@ const std = @import("std"); const builtin = @import("builtin"); const stdx = @import("stdx"); const cy = @import("../cyber.zig"); +const vmc = cy.vmc; const Value = cy.Value; const vm_ = @import("../vm.zig"); const fmt = @import("../fmt.zig"); -const bindings = @import("bindings.zig"); +const bindings = @import("../builtins/bindings.zig"); const Symbol = bindings.Symbol; const prepareThrowSymbol = bindings.prepareThrowSymbol; const bt = cy.types.BuiltinTypeSymIds; const v = fmt.v; -pub fn initModule(c: *cy.VMcompiler, modId: cy.ModuleId) linksection(cy.InitSection) anyerror!void { - const b = bindings.ModuleBuilder.init(c, modId); +pub const Src = @embedFile("test.cy"); +pub fn defaultFuncLoader(_: *cy.UserVM, func: cy.HostFuncInfo) callconv(.C) vmc.HostFuncFn { + if (std.mem.eql(u8, funcs[func.idx].@"0", func.name.slice())) { + return @ptrCast(funcs[func.idx].@"1"); + } + return null; +} + +const NameHostFunc = struct { []const u8, cy.ZHostFuncFn }; +const funcs = [_]NameHostFunc{ + .{"eq", eq}, + .{"eqList", eqList}, + .{"eqNear", eqNear}, + .{"fail", fail}, +}; - try b.setFunc("eq", &.{bt.Any, bt.Any}, bt.Any, eq); - try b.setFunc("eqList", &.{bt.Any, bt.Any}, bt.Any, eqList); - try b.setFunc("eqNear", &.{bt.Any, bt.Any}, bt.Any, eqNear); +pub fn postLoad(vm: *cy.UserVM, modId: cy.ModuleId) callconv(.C) void { + const b = bindings.ModuleBuilder.init(vm.internal().compiler, modId); if (builtin.is_test) { // Only available for zig test, until `any` local type specifier is implemented. - try b.setFunc("erase", &.{bt.Any}, bt.Any, erase); + b.setFunc("erase", &.{bt.Any}, bt.Any, erase) catch cy.fatal(); } - try b.setFunc("fail", &.{}, bt.Any, fail); } fn fail(vm: *cy.UserVM, _: [*]const Value, _: u8) Value { diff --git a/src/stdx/stdx.zig b/src/stdx/stdx.zig index a64fdfdce..4f17faa06 100644 --- a/src/stdx/stdx.zig +++ b/src/stdx/stdx.zig @@ -1,4 +1,5 @@ // Copied only what is used from the cosmic project. +// TODO: Remove this package. pub const testing = @import("testing.zig"); diff --git a/src/vm.c b/src/vm.c index 317e2e27c..2c64c1d9c 100644 --- a/src/vm.c +++ b/src/vm.c @@ -175,10 +175,10 @@ static inline void releaseObject(VM* vm, HeapObject* obj) { } static inline void retainObject(VM* vm, HeapObject* obj) { + VLOG("retain {} rc={}\n", FMT_STR(getTypeName(vm, OBJ_TYPEID(obj))), FMT_U32(obj->head.rc)); obj->head.rc += 1; #if TRACE zCheckRetainDanglingPointer(vm, obj); - VLOG("retain {} rc={}\n", FMT_STR(getTypeName(vm, OBJ_TYPEID(obj))), FMT_U32(obj->head.rc)); #endif #if TRACK_GLOBAL_RC vm->refCounts += 1; @@ -1079,8 +1079,8 @@ ResultCode execBytecode(VM* vm) { if (typeId == cachedTypeId) { // const newFramePtr = framePtr + startLocal; vm->curStack = stack; - MethodPtr fn = (MethodPtr)READ_U48(8); - Value res = fn(vm, recv, stack + startLocal + 5, numArgs); + HostFuncFn fn = (HostFuncFn)READ_U48(8); + Value res = fn(vm, stack + startLocal + 4, numArgs); if (res == VALUE_INTERRUPT) { RETURN(RES_CODE_PANIC); } @@ -1201,7 +1201,7 @@ ResultCode execBytecode(VM* vm) { Value* newStack = stack + startLocal; vm->curStack = newStack; - FuncPtr fn = (FuncPtr)READ_U48(6); + HostFuncFn fn = (HostFuncFn)READ_U48(6); Value res = fn(vm, newStack + 4, numArgs); if (res == VALUE_INTERRUPT) { RETURN(RES_CODE_PANIC); @@ -2021,8 +2021,8 @@ ResultCode execBytecode(VM* vm) { NEXT(); } CASE(Sym): { - uint8_t symType = pc[1]; - uint32_t symId = READ_U32(2); + u8 symType = pc[1]; + u32 symId = READ_U32(2); ValueResult res = allocMetaType(vm, symType, symId); if (LIKELY(res.code == RES_CODE_SUCCESS)) { stack[pc[6]] = res.val; diff --git a/src/vm.h b/src/vm.h index f7e4ce852..f75a7fc31 100644 --- a/src/vm.h +++ b/src/vm.h @@ -44,7 +44,7 @@ typedef struct IndexSlice { // 1111111111111100: TaggedMask + Sign bit indicates a pointer value. #define NOCYC_POINTER_MASK (TAGGED_VALUE_MASK | SIGN_MASK) -// 1111111111111110: Extra bit indicating cycable pointer. +// 1111111111111110: Extra bit indicating cyclable pointer. #define CYC_POINTER_MASK (NOCYC_POINTER_MASK | ((u64)1 << 49)) #define POINTER_MASK (CYC_POINTER_MASK) @@ -332,6 +332,7 @@ typedef u32 MethodGroupId; typedef u32 TypeMethodGroupId; typedef u32 SymbolId; typedef u32 FuncSigId; +typedef u32 ModuleId; typedef u32 NameId; typedef struct Name { @@ -789,6 +790,7 @@ typedef struct VM { #endif Compiler* compiler; void* userData; + void* print; #if TRACE ZHashMap objectTraceMap; #endif @@ -870,8 +872,7 @@ typedef struct PcSpResult { ResultCode code; } PcSpResult; -typedef Value (*FuncPtr)(VM* vm, Value* args, uint8_t nargs); -typedef Value (*MethodPtr)(VM* vm, Value recv, Value* args, uint8_t nargs); +typedef Value (*HostFuncFn)(VM* vm, const Value* args, uint8_t nargs); // C API. ResultCode execBytecode(VM* vm); @@ -915,4 +916,4 @@ void zCheckDoubleFree(VM* vm, HeapObject* obj); void zCheckRetainDanglingPointer(VM* vm, HeapObject* obj); void zPanicFmt(VM* vm, const char* format, FmtValue* args, size_t numArgs); Value zValueMapGet(VM* vm, ValueMap* map, Value key, bool* found); -ResultCode zMapSet(VM* vm, Map* map, Value key, Value val); +ResultCode zMapSet(VM* vm, Map* map, Value key, Value val); \ No newline at end of file diff --git a/src/vm.zig b/src/vm.zig index 38cdb84d7..10d0467da 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -138,6 +138,9 @@ pub const VM = struct { /// User data ptr. Useful for embedders. userData: ?*anyopaque, + /// Host print callback. + print: cy.PrintFn, + /// Object to pc of instruction that allocated it. objectTraceMap: if (cy.Trace) std.AutoHashMapUnmanaged(*HeapObject, debug.ObjectTrace) else void, @@ -254,6 +257,7 @@ pub const VM = struct { .userData = null, .expGlobalRC = 0, .varSymExtras = .{}, + .print = defaultPrint, }; self.mainFiber.panicType = vmc.PANIC_NONE; self.curFiber = &self.mainFiber; @@ -614,9 +618,9 @@ pub const VM = struct { fmt.printStderr("global rc: {}\n", &.{v(self.refCounts)}); } - // Dump object symbols. + // Dump func symbols. { - fmt.printStderr("obj syms:\n", &.{}); + fmt.printStderr("func syms:\n", &.{}); var iter = self.funcSymSigs.iterator(); while (iter.next()) |it| { const key = it.key_ptr.*; @@ -1262,12 +1266,12 @@ pub const VM = struct { self.pc = pc; self.framePtr = framePtr; - const res = sym.inner.nativeFunc1(@ptrCast(self), @ptrCast(newFramePtr + 4), numArgs); + const res: Value = @bitCast(sym.inner.nativeFunc1.?(@ptrCast(self), @ptrCast(newFramePtr + 4), numArgs)); if (res.isInterrupt()) { return error.Panic; } if (reqNumRetVals == 1) { - newFramePtr[0] = res; + newFramePtr[0] = @bitCast(res); } else { switch (reqNumRetVals) { 0 => { @@ -1938,6 +1942,7 @@ test "vm internals." { try t.eq(@offsetOf(VM, "throwTrace"), @offsetOf(vmc.VM, "throwTrace")); try t.eq(@offsetOf(VM, "compiler"), @offsetOf(vmc.VM, "compiler")); try t.eq(@offsetOf(VM, "userData"), @offsetOf(vmc.VM, "userData")); + try t.eq(@offsetOf(VM, "print"), @offsetOf(vmc.VM, "print")); try t.eq(@offsetOf(VM, "iteratorMGID"), @offsetOf(vmc.VM, "padding")); try t.eq(@offsetOf(VM, "httpClient"), @offsetOf(vmc.VM, "httpClient")); try t.eq(@offsetOf(VM, "stdHttpClient"), @offsetOf(vmc.VM, "stdHttpClient")); @@ -3407,7 +3412,7 @@ fn evalLoop(vm: *VM) linksection(cy.HotSection) error{StackOverflow, OutOfMemory if (try vm.getCachedMethodGroupForType(typeId, mgId)) |mg| { if (try @call(.never_inline, callMethodGroup, .{ - vm, pc, framePtr, mgId, mg, recv, typeId, startLocal, numArgs, numRet, anySelfFuncSigId, + vm, pc, framePtr, mgId, mg, typeId, startLocal, numArgs, numRet, anySelfFuncSigId, })) |res| { pc = res.pc; framePtr = res.sp; @@ -3648,8 +3653,8 @@ pub fn call(vm: *VM, pc: [*]cy.Inst, framePtr: [*]Value, callee: Value, startLoc vm.pc = pc; const newFramePtr = framePtr + startLocal; vm.framePtr = newFramePtr; - const res = obj.nativeFunc1.func(@ptrCast(vm), newFramePtr + 4, numArgs); - newFramePtr[0] = res; + const res = obj.nativeFunc1.func.?(@ptrCast(vm), @ptrCast(newFramePtr + 4), numArgs); + newFramePtr[0] = @bitCast(res); return cy.fiber.PcSp{ .pc = pc + cy.bytecode.CallInstLen, .sp = framePtr, @@ -3708,8 +3713,8 @@ pub fn callNoInline(vm: *VM, pc: *[*]cy.Inst, framePtr: *[*]Value, callee: Value vm.pc = pc.*; const newFramePtr = framePtr.* + startLocal; vm.framePtr = newFramePtr; - const res = obj.nativeFunc1.func(@ptrCast(vm), newFramePtr + 4, numArgs); - newFramePtr[0] = res; + const res = obj.nativeFunc1.func.?(@ptrCast(vm), @ptrCast(newFramePtr + 4), numArgs); + newFramePtr[0] = @bitCast(res); cy.arc.releaseObject(vm, obj); pc.* += 14; }, @@ -4266,14 +4271,14 @@ fn getFuncSigIdOfSym(vm: *const VM, symId: SymbolId) sema.FuncSigId { } fn callMethodNoInline( - vm: *VM, pc: [*]cy.Inst, sp: [*]cy.Value, methodType: rt.MethodType, data: rt.MethodData, recv: Value, + vm: *VM, pc: [*]cy.Inst, sp: [*]cy.Value, methodType: rt.MethodType, data: rt.MethodData, typeId: vmc.TypeId, startLocal: u8, numArgs: u8, reqNumRetVals: u8, anySelfFuncSigId: sema.FuncSigId, ) !?cy.fiber.PcSp { - return @call(.always_inline, callMethod, .{vm, pc, sp, methodType, data, recv, typeId, startLocal, numArgs, reqNumRetVals, anySelfFuncSigId}); + return @call(.always_inline, callMethod, .{vm, pc, sp, methodType, data, typeId, startLocal, numArgs, reqNumRetVals, anySelfFuncSigId}); } fn callMethod( - vm: *VM, pc: [*]cy.Inst, sp: [*]cy.Value, methodType: rt.MethodType, data: rt.MethodData, recv: Value, + vm: *VM, pc: [*]cy.Inst, sp: [*]cy.Value, methodType: rt.MethodType, data: rt.MethodData, typeId: vmc.TypeId, startLocal: u8, numArgs: u8, reqNumRetVals: u8, anySelfFuncSigId: sema.FuncSigId, ) !?cy.fiber.PcSp { switch (methodType) { @@ -4310,7 +4315,7 @@ fn callMethod( @as(*align(1) u16, @ptrCast(pc + 14)).* = @intCast(typeId); vm.framePtr = sp; - const res = data.untypedNative1.ptr(@ptrCast(vm), recv, @ptrCast(sp + startLocal + 5), numArgs); + const res: Value = @bitCast(data.untypedNative1.ptr.?(@ptrCast(vm), @ptrCast(sp + startLocal + 4), numArgs)); if (res.isInterrupt()) { return error.Panic; } @@ -4337,7 +4342,7 @@ fn callMethod( return null; } vm.framePtr = sp; - const res = data.untypedNative2.ptr(@ptrCast(vm), recv, @ptrCast(sp + startLocal + 5), numArgs); + const res = data.untypedNative2.ptr(@ptrCast(vm), @ptrCast(sp + startLocal + 4), numArgs); if (res.left.isInterrupt()) { return error.Panic; } @@ -4374,7 +4379,7 @@ fn callMethod( @as(*align(1) u48, @ptrCast(pc + 8)).* = @intCast(@intFromPtr(data.optimizing.ptr)); @as(*align(1) u16, @ptrCast(pc + 14)).* = @intCast(typeId); - data.optimizing.ptr(@ptrCast(vm), pc, recv, @ptrCast(sp + startLocal + 5), numArgs); + data.optimizing.ptr(@ptrCast(vm), pc, @ptrCast(sp + startLocal + 4), numArgs); return cy.fiber.PcSp{ .pc = pc, .sp = sp, @@ -4439,7 +4444,7 @@ fn callMethod( } vm.framePtr = sp; - const res = data.typedNative.ptr(@ptrCast(vm), recv, @ptrCast(sp + startLocal + 5), numArgs); + const res: Value = @bitCast(data.typedNative.ptr.?(@ptrCast(vm), @ptrCast(sp + startLocal + 4), numArgs)); if (res.isInterrupt()) { return error.Panic; } @@ -4467,7 +4472,7 @@ fn callMethod( /// Assumes there are overloaded methods. fn callFirstOverloadedMethod( vm: *VM, pc: [*]cy.Inst, sp: [*]Value, mgId: vmc.MethodGroupId, - recv: Value, typeId: vmc.TypeId, startLocal: u8, numArgs: u8, reqNumRetVals: u8, anySelfFuncSigId: sema.FuncSigId, + typeId: vmc.TypeId, startLocal: u8, numArgs: u8, reqNumRetVals: u8, anySelfFuncSigId: sema.FuncSigId, ) !?cy.fiber.PcSp { const mgExt = &vm.methodGroupExts.buf[mgId]; std.debug.assert(mgExt.mruTypeMethodGroupId != cy.NullId); @@ -4483,7 +4488,7 @@ fn callFirstOverloadedMethod( methodId = method.next; continue; } - if (try @call(.never_inline, callMethodNoInline, .{vm, pc, sp, method.type, method.data, recv, typeId, startLocal, numArgs, reqNumRetVals, anySelfFuncSigId})) |res| { + if (try @call(.never_inline, callMethodNoInline, .{vm, pc, sp, method.type, method.data, typeId, startLocal, numArgs, reqNumRetVals, anySelfFuncSigId})) |res| { // Update MethodGroup cache. // TypeMethodGroup is updated on MethodGroup cache miss. const mgPtr = &vm.methodGroups.buf[mgId]; @@ -4501,13 +4506,13 @@ fn callFirstOverloadedMethod( /// Assumes cache hit on TypeMethodGroup. fn callMethodGroup( vm: *VM, pc: [*]cy.Inst, sp: [*]Value, mgId: vmc.MethodGroupId, mg: rt.MethodGroup, - recv: Value, typeId: vmc.TypeId, startLocal: u8, numArgs: u8, reqNumRetVals: u8, anySelfFuncSigId: sema.FuncSigId, + typeId: vmc.TypeId, startLocal: u8, numArgs: u8, reqNumRetVals: u8, anySelfFuncSigId: sema.FuncSigId, ) linksection(cy.HotSection) !?cy.fiber.PcSp { - if (try @call(.always_inline, callMethod, .{vm, pc, sp, mg.mruMethodType, mg.mruMethodData, recv, typeId, startLocal, numArgs, reqNumRetVals, anySelfFuncSigId})) |res| { + if (try @call(.always_inline, callMethod, .{vm, pc, sp, mg.mruMethodType, mg.mruMethodData, typeId, startLocal, numArgs, reqNumRetVals, anySelfFuncSigId})) |res| { return res; } if (mg.mruTypeMethodOverloaded) { - return @call(.never_inline, callFirstOverloadedMethod, .{vm, pc, sp, mgId, recv, typeId, startLocal, numArgs, reqNumRetVals, anySelfFuncSigId}); + return @call(.never_inline, callFirstOverloadedMethod, .{vm, pc, sp, mgId, typeId, startLocal, numArgs, reqNumRetVals, anySelfFuncSigId}); } return null; } @@ -4606,7 +4611,7 @@ export fn zCallObjSym( }; if (mbMethodGroup) |sym| { const mb_res = @call(.always_inline, callMethodGroup, .{ - vm, pc, stack, mgId, sym, recv, typeId, startLocal, numArgs, numRet, anySelfFuncSigId, + vm, pc, stack, mgId, sym, typeId, startLocal, numArgs, numRet, anySelfFuncSigId, }) catch |err| { if (err == error.Panic) { return .{ @@ -4952,4 +4957,8 @@ pub var dummyCyclableHead = DummyCyclableNode{ .next = null, // This will be marked automatically before sweep, so it's never considered as a cyc object. .typeId = vmc.GC_MARK_MASK | rt.NoneT, -}; \ No newline at end of file +}; + +pub fn defaultPrint(_: *UserVM, _: cy.Str) callconv(.C) void { + // Default print is a nop. +} \ No newline at end of file diff --git a/src/vm_compiler.zig b/src/vm_compiler.zig index 8750663fb..f4aa58c17 100644 --- a/src/vm_compiler.zig +++ b/src/vm_compiler.zig @@ -12,11 +12,8 @@ const sema = cy.sema; const types = cy.types; const bt = types.BuiltinTypeSymIds; const gen = cy.codegen; -const cache = @import("cache.zig"); -const core_mod = @import("builtins/core.zig"); +const cy_mod = @import("builtins/builtins.zig"); const math_mod = @import("builtins/math.zig"); -const os_mod = @import("builtins/os.zig"); -const test_mod = @import("builtins/test.zig"); const log = cy.log.scoped(.vm_compiler); @@ -43,8 +40,11 @@ pub const VMcompiler = struct { /// Used to return additional info for an error. errorPayload: cy.NodeId, - /// Absolute specifier to additional loaders. - moduleLoaders: std.StringHashMapUnmanaged(std.ArrayListUnmanaged(cy.ModuleLoaderFunc)), + /// Determines how modules are loaded. + moduleLoader: cy.ModuleLoaderFn, + + /// Determines how module uris are resolved. + moduleResolver: cy.ModuleResolverFn, /// Compilation units indexed by their id. chunks: std.ArrayListUnmanaged(cy.Chunk), @@ -58,10 +58,15 @@ pub const VMcompiler = struct { /// Reused for SymIds. tempSyms: std.ArrayListUnmanaged(sema.SymbolId), - deinitedRtObjects: bool, - config: CompileConfig, + /// Tracks whether an error was set from the API. + hasApiError: bool, + apiError: []const u8, // Duped so Cyber owns the msg. + + /// Whether builtins should be imported. + importBuiltins: bool = true, + pub fn init(self: *VMcompiler, vm: *cy.VM) !void { self.* = .{ .alloc = vm.alloc, @@ -72,32 +77,19 @@ pub const VMcompiler = struct { .lastErrChunk = undefined, .errorPayload = undefined, .sema = sema.Model.init(vm.alloc), + .moduleLoader = defaultModuleLoader, + .moduleResolver = defaultModuleResolver, .chunks = .{}, .importTasks = .{}, - .moduleLoaders = .{}, - .deinitedRtObjects = false, .tempSyms = .{}, .typeStack = .{}, .config = .{}, + .hasApiError = false, + .apiError = "", }; - try self.addModuleLoader("core", initModuleCompat("core", core_mod.initModule)); - try self.addModuleLoader("math", initModuleCompat("math", math_mod.initModule)); - try self.addModuleLoader("os", initModuleCompat("os", os_mod.initModule)); - try self.addModuleLoader("test", initModuleCompat("test", test_mod.initModule)); - try self.reinit(); } - pub fn deinitRtObjects(self: *VMcompiler) void { - if (self.deinitedRtObjects) { - return; - } - if (self.sema.moduleMap.get("os")) |id| { - os_mod.deinitModule(self, self.sema.modules.items[id]) catch cy.fatal(); - } - self.deinitedRtObjects = true; - } - pub fn deinit(self: *VMcompiler, comptime reset: bool) void { self.alloc.free(self.lastErr); @@ -107,10 +99,10 @@ pub const VMcompiler = struct { self.buf.deinit(); } - self.deinitRtObjects(); - self.sema.deinit(self.alloc, reset); - for (self.chunks.items) |*chunk| { + if (chunk.destroy) |destroy| { + destroy(@ptrCast(self.vm), chunk.modId); + } chunk.deinit(); } if (reset) { @@ -121,16 +113,8 @@ pub const VMcompiler = struct { self.importTasks.deinit(self.alloc); } - if (reset) { - // `moduleLoaders` persists. - } else { - var iter = self.moduleLoaders.iterator(); - while (iter.next()) |e| { - self.alloc.free(e.key_ptr.*); - e.value_ptr.deinit(self.alloc); - } - self.moduleLoaders.deinit(self.alloc); - } + // Chunks depends on modules. + self.sema.deinit(self.alloc, reset); if (reset) { self.typeStack.clearRetainingCapacity(); @@ -139,10 +123,12 @@ pub const VMcompiler = struct { self.typeStack.deinit(self.alloc); self.tempSyms.deinit(self.alloc); } + + self.alloc.free(self.apiError); + self.apiError = ""; } pub fn reinit(self: *VMcompiler) !void { - self.deinitedRtObjects = false; self.lastErrNode = cy.NullId; self.lastErrChunk = cy.NullId; @@ -280,15 +266,17 @@ pub const VMcompiler = struct { try self.chunks.append(self.alloc, mainChunk); // Load core module first since the members are imported into each user module. - const coreModId = try sema.appendResolvedRootModule(self, "core"); - const importCore = ImportTask{ - .chunkId = nextId, - .nodeId = cy.NullId, - .absSpec = "core", - .modId = coreModId, - .builtin = true, - }; - try performImportTask(self, importCore); + var builtinModId: cy.ModuleId = undefined; + if (self.importBuiltins) { + builtinModId = try sema.appendResolvedRootModule(self, "builtins"); + const importCore = ImportTask{ + .chunkId = nextId, + .nodeId = cy.NullId, + .absSpec = "builtins", + .modId = builtinModId, + }; + try performImportTask(self, importCore); + } // All modules and data types are loaded first. var id: u32 = 0; @@ -301,6 +289,11 @@ pub const VMcompiler = struct { const mod = self.sema.getModule(chunk.modId); chunk.semaRootSymId = mod.resolvedRootSymId; + if (self.importBuiltins) { + // Import builtin module into local namespace. + try sema.declareUsingModule(chunk, builtinModId); + } + // Process static declarations. for (chunk.parser.staticDecls.items) |decl| { switch (decl.declT) { @@ -317,6 +310,7 @@ pub const VMcompiler = struct { try sema.declareTypeAlias(chunk, decl.inner.typeAlias); }, .variable, + .hostFunc, .func, .funcInit => {}, } @@ -336,11 +330,7 @@ pub const VMcompiler = struct { // Declare static vars and funcs after types have been resolved. id = 0; while (id < self.chunks.items.len) : (id += 1) { - const chunk = &self.chunks.items[id]; - - // Import core module into local namespace. - const modId = try sema.getOrLoadModule(chunk, "core", cy.NullId); - try sema.importAllFromModule(chunk, modId); + var chunk = &self.chunks.items[id]; // Process static declarations. for (chunk.parser.staticDecls.items) |decl| { @@ -354,6 +344,9 @@ pub const VMcompiler = struct { .funcInit => { try sema.declareFuncInit(chunk, decl.inner.funcInit); }, + .hostFunc => { + try sema.declareHostFunc(chunk, decl.inner.hostFunc); + }, .object => { try sema.declareObjectMembers(chunk, decl.inner.object); }, @@ -362,6 +355,10 @@ pub const VMcompiler = struct { .typeAlias => {}, } } + + if (chunk.postLoad) |postLoad| { + postLoad(@ptrCast(self.vm), chunk.modId); + } } // Perform sema on all chunks. @@ -513,129 +510,39 @@ pub const VMcompiler = struct { } fn performImportTask(self: *VMcompiler, task: ImportTask) !void { - if (task.builtin) { - if (self.moduleLoaders.get(task.absSpec)) |loaders| { - for (loaders.items) |loader| { - if (!loader(@ptrCast(self.vm), task.modId)) { - return error.LoadModuleError; - } - } - } else { - const chunk = &self.chunks.items[task.chunkId]; - return chunk.reportErrorAt("Unsupported builtin. {}", &.{fmt.v(task.absSpec)}, task.nodeId); - } - } else { - // Default loader. - - if (cy.isWasm) { - return error.Unsupported; - } - - var src: []const u8 = undefined; - if (std.mem.startsWith(u8, task.absSpec, "http://") or std.mem.startsWith(u8, task.absSpec, "https://")) { - src = try self.importUrl(task); - } else { - src = try std.fs.cwd().readFileAlloc(self.alloc, task.absSpec, 1e10); - } - + var res: cy.ModuleLoaderResult = undefined; + self.hasApiError = false; + if (self.moduleLoader(@ptrCast(self.vm), cy.Str.initSlice(task.absSpec), &res)) { // Push another chunk. const newChunkId: u32 = @intCast(self.chunks.items.len); + + const src = res.src.slice(); var newChunk = try cy.Chunk.init(self, newChunkId, task.absSpec, src); - newChunk.srcOwned = true; + newChunk.funcLoader = res.funcLoader; + newChunk.postLoad = res.postLoad; + newChunk.srcOwned = !res.srcIsStatic; + newChunk.destroy = res.destroy; newChunk.modId = task.modId; try self.chunks.append(self.alloc, newChunk); self.sema.modules.items[task.modId].chunkId = newChunkId; - } - } - - pub fn addModuleLoader(self: *VMcompiler, absSpec: []const u8, func: cy.ModuleLoaderFunc) !void { - const res = try self.moduleLoaders.getOrPut(self.alloc, absSpec); - if (res.found_existing) { - const list = res.value_ptr; - try list.append(self.alloc, func); - } else { - const keyDupe = try self.alloc.dupe(u8, absSpec); - // Start with initial cap = 1. - res.value_ptr.* = try std.ArrayListUnmanaged(cy.ModuleLoaderFunc).initCapacity(self.alloc, 1); - res.key_ptr.* = keyDupe; - const list = res.value_ptr; - list.items.len = 1; - list.items[0] = func; - } - } - - fn importUrl(self: *VMcompiler, task: ImportTask) ![]const u8 { - const specGroup = try cache.getSpecHashGroup(self.alloc, task.absSpec); - defer specGroup.deinit(self.alloc); - - if (self.vm.config.reload) { - // Remove cache entry. - try specGroup.markEntryBySpecForRemoval(task.absSpec); } else { - // First check local cache. - if (try specGroup.findEntryBySpec(task.absSpec)) |entry| { - var found = true; - const src = cache.allocSpecFileContents(self.alloc, entry) catch |err| b: { - if (err == error.FileNotFound) { - // Fallthrough. - found = false; - break :b ""; - } else { - return err; - } - }; - if (found) { - log.debug("Using cached {s}", .{task.absSpec}); - return src; + const chunk = &self.chunks.items[task.chunkId]; + if (task.nodeId == cy.NullId) { + if (self.hasApiError) { + return chunk.reportError(self.apiError, &.{}); + } else { + return chunk.reportError("Failed to load module: {}", &.{v(task.absSpec)}); } - } - } - - const client = self.vm.httpClient; - - const uri = try std.Uri.parse(task.absSpec); - var req = client.request(.GET, uri, .{ .allocator = self.alloc }) catch |err| { - if (err == error.UnknownHostName) { - const chunk = &self.chunks.items[task.chunkId]; - const stmt = chunk.nodes[task.nodeId]; - return chunk.reportErrorAt("Can not connect to `{}`.", &.{v(uri.host.?)}, stmt.head.left_right.right); } else { - return err; - } - }; - defer client.deinitRequest(&req); - - try client.startRequest(&req); - try client.waitRequest(&req); - - switch (req.response.status) { - .ok => { - // Whitelisted status codes. - }, - else => { - // Stop immediately. - const chunk = &self.chunks.items[task.chunkId]; const stmt = chunk.nodes[task.nodeId]; - return chunk.reportErrorAt("Can not load `{}`. Response code: {}", &.{v(task.absSpec), v(req.response.status)}, stmt.head.left_right.right); - }, - } - - var buf: std.ArrayListUnmanaged(u8) = .{}; - errdefer buf.deinit(self.alloc); - var readBuf: [4096]u8 = undefined; - var read: usize = readBuf.len; - - while (read == readBuf.len) { - read = try client.readAll(&req, &readBuf); - try buf.appendSlice(self.alloc, readBuf[0..read]); + if (self.hasApiError) { + return chunk.reportErrorAt(self.apiError, &.{}, stmt.head.left_right.right); + } else { + return chunk.reportErrorAt("Failed to load module: {}", &.{v(task.absSpec)}, stmt.head.left_right.right); + } + } } - - // Cache to local. - const entry = try cache.saveNewSpecFile(self.alloc, specGroup, task.absSpec, buf.items); - entry.deinit(self.alloc); - - return try buf.toOwnedSlice(self.alloc); } }; @@ -689,7 +596,6 @@ const ImportTask = struct { nodeId: cy.NodeId, absSpec: []const u8, modId: cy.ModuleId, - builtin: bool, }; pub fn initModuleCompat(comptime name: []const u8, comptime initFn: fn (vm: *VMcompiler, modId: cy.ModuleId) anyerror!void) cy.ModuleLoaderFunc { @@ -714,6 +620,32 @@ pub const ValidateConfig = struct { enableFileModules: bool = false, }; +pub fn defaultModuleResolver(_: *cy.UserVM, _: cy.ChunkId, _: cy.Str, spec_: cy.Str, outUri: *cy.Str) callconv(.C) bool { + outUri.* = spec_; + return true; +} + +pub fn defaultModuleLoader(_: *cy.UserVM, spec: cy.Str, out: *cy.ModuleLoaderResult) callconv(.C) bool { + if (std.mem.eql(u8, spec.slice(), "builtins")) { + out.* = .{ + .src = cy.Str.initSlice(cy_mod.Src), + .srcIsStatic = true, + .funcLoader = cy_mod.defaultFuncLoader, + .postLoad = cy_mod.postLoad, + }; + return true; + } else if (std.mem.eql(u8, spec.slice(), "math")) { + out.* = .{ + .src = cy.Str.initSlice(math_mod.Src), + .srcIsStatic = true, + .funcLoader = math_mod.defaultFuncLoader, + .postLoad = math_mod.postLoad, + }; + return true; + } + return false; +} + test "vm compiler internals." { try t.eq(@offsetOf(VMcompiler, "buf"), @offsetOf(vmc.Compiler, "buf")); try t.eq(@offsetOf(VMcompiler, "sema"), @offsetOf(vmc.Compiler, "sema")); diff --git a/test/behavior_test.zig b/test/behavior_test.zig index 38966931c..3163b0a2d 100644 --- a/test/behavior_test.zig +++ b/test/behavior_test.zig @@ -7,6 +7,7 @@ const t = stdx.testing; const vm_ = @import("../src/vm.zig"); const cy = @import("../src/cyber.zig"); +const vmc = cy.vmc; const http = @import("../src/http.zig"); const bindings = @import("../src/builtins/bindings.zig"); const log = cy.log.scoped(.behavior_test); @@ -568,28 +569,45 @@ test "Custom modules." { } }; - try run.vm.addModuleLoader("mod1", struct { - fn loader(vm: *cy.UserVM, modId: cy.ModuleId) bool { + run.vm.internal().compiler.importBuiltins = false; + run.vm.setModuleResolver(cy.vm_compiler.defaultModuleResolver); + run.vm.setModuleLoader(struct { + fn postLoadMod2(vm: *cy.UserVM, modId: vmc.ModuleId) callconv(.C) void { // Test dangling pointer. const s1 = allocString("test"); - const s2 = allocString("test2"); defer t.alloc.free(s1); - defer t.alloc.free(s2); const mod = vm.internal().compiler.sema.getModulePtr(modId); - mod.setNativeFuncExt(vm.internal().compiler, s1, true, 0, S.test1) catch fatal(); - mod.setNativeFuncExt(vm.internal().compiler, s2, true, 0, S.test2) catch fatal(); - return true; + mod.setNativeFuncExt(vm.internal().compiler, s1, true, 0, S.test3) catch fatal(); } - }.loader); - - try run.vm.addModuleLoader("mod2", struct { - fn loader(vm: *cy.UserVM, modId: cy.ModuleId) bool { + fn postLoadMod1(vm: *cy.UserVM, modId: vmc.ModuleId) callconv(.C) void { // Test dangling pointer. const s1 = allocString("test"); + const s2 = allocString("test2"); defer t.alloc.free(s1); + defer t.alloc.free(s2); const mod = vm.internal().compiler.sema.getModulePtr(modId); - mod.setNativeFuncExt(vm.internal().compiler, s1, true, 0, S.test3) catch fatal(); - return true; + mod.setNativeFuncExt(vm.internal().compiler, s1, true, 0, S.test1) catch fatal(); + mod.setNativeFuncExt(vm.internal().compiler, s2, true, 0, S.test2) catch fatal(); + } + fn loader(_: *cy.UserVM, spec: cy.Str, out: *cy.ModuleLoaderResult) callconv(.C) bool { + if (std.mem.eql(u8, spec.slice(), "mod1")) { + out.* = .{ + .src = cy.Str.initSlice(""), + .srcIsStatic = true, + .funcLoader = null, + .postLoad = postLoadMod1, + }; + return true; + } else if (std.mem.eql(u8, spec.slice(), "mod2")) { + out.* = .{ + .src = cy.Str.initSlice(""), + .srcIsStatic = true, + .funcLoader = null, + .postLoad = postLoadMod2, + }; + return true; + } + return false; } }.loader); @@ -600,9 +618,7 @@ test "Custom modules." { \\m.test2() \\n.test() ); - _ = try run.evalExtNoReset(.{}, - src1 - ); + _ = try run.evalExtNoReset(.{}, src1); // Test dangling pointer. t.alloc.free(src1); @@ -630,12 +646,23 @@ test "Multiple evals persisting state." { defer run.vm.release(global); run.vm.setUserData(&global); - try run.vm.addModuleLoader("core", struct { - fn loader(vm: *cy.UserVM, modId: cy.ModuleId) bool { + run.vm.setModuleLoader(struct { + fn postLoad(vm: *cy.UserVM, modId: vmc.ModuleId) callconv(.C) void { const g = cy.ptrAlignCast(*cy.Value, vm.getUserData()).*; const mod = vm.internal().compiler.sema.getModulePtr(modId); mod.setVar(vm.internal().compiler, "g", g) catch fatal(); - return true; + } + fn loader(vm: *cy.UserVM, spec: cy.Str, out: *cy.ModuleLoaderResult) callconv(.C) bool { + if (std.mem.eql(u8, spec.slice(), "builtins")) { + out.* = .{ + .src = cy.Str.initSlice(""), + .srcIsStatic = true, + .postLoad = postLoad, + }; + return true; + } else { + return cy.cli.loader(vm, spec, out); + } } }.loader); @@ -1578,7 +1605,7 @@ test "Stack trace unwinding." { try t.eq(trace.frames.len, 1); try eqStackFrame(trace.frames[0], .{ .name = "main", - .chunkId = 1, + .chunkId = 2, .line = 0, .col = 9, .lineStartPos = 0, @@ -1604,7 +1631,7 @@ test "Stack trace unwinding." { try t.eq(trace_.frames.len, 1); try eqStackFrame(trace_.frames[0], .{ .name = "main", - .chunkId = 1, + .chunkId = 2, .line = 0, .col = 9, .lineStartPos = 0, @@ -1788,18 +1815,8 @@ test "Statements." { ); }}.func); - // Expects one statement. - try eval(.{ .silent = true }, " ", - struct { fn func(run: *VMrunner, res: EvalResult) !void { - try run.expectErrorReport(res, error.ParseError, - \\ParseError: Expected one statement. - \\ - \\main:1:4: - \\ - \\ ^ - \\ - ); - }}.func); + // Allows compiling without any statements. + try evalPass(.{}, " "); } test "Indentation." { @@ -2061,23 +2078,14 @@ test "Static variable declaration." { \\t.eq(b, 444) \\t.eq(c, 567) ); + run.deinit(); - // Declaration for an existing alias symbol. - res = run.evalExt(.{ .silent = true }, - \\var print: 123 - ); - try t.expectError(res, error.CompileError); - const err = try run.vm.allocLastUserCompileError(); - try t.eqStrFree(t.alloc, err, - \\CompileError: The symbol `print` was already declared. - \\ - \\main:1:5: + // Declaration over using builtin module. + try evalPass(.{}, \\var print: 123 - \\ ^ - \\ ); - _ = try run.eval(@embedFile("staticvar_decl_test.cy")); + try evalPass(.{}, @embedFile("staticvar_decl_test.cy")); } test "Static variable assignment." { @@ -2511,19 +2519,13 @@ test "Static functions." { ); }}.func); - // Declaration initializer for a function already imported from core. - try eval(.{ .silent = true }, - \\func print(val) = float - , struct { fn func(run: *VMrunner, res: EvalResult) !void { - try run.expectErrorReport(res, error.CompileError, - \\CompileError: The symbol `print` was already declared. - \\ - \\main:1:6: - \\func print(val) = float - \\ ^ - \\ - ); - }}.func); + // Allow declaring over using builtins namespace. + try evalPass(.{}, + \\import t 'test' + \\func print(v): + \\ return v + 2 + \\t.eq(print(1), 3) + ); // Capture local from static function is not allowed. try eval(.{ .silent = true }, diff --git a/test/core_test.cy b/test/core_test.cy index 87927ee8c..5e7f0999d 100644 --- a/test/core_test.cy +++ b/test/core_test.cy @@ -207,12 +207,4 @@ t.eq(typeof({}), Map) t.eq(typesym(123), #int) t.eq(typesym(123.0), #float) t.eq(typesym('abc'), #string) -t.eq(typesym(pointer(0)), #pointer) - --- writeFile() rawstring -if os.cpu != 'wasm32': - s = rawstring('').insertByte(0, 255) - writeFile('test.txt', s) - var read = readFile('test.txt') - t.eq(read.len(), 1) - t.eq(read.byteAt(0), 255) \ No newline at end of file +t.eq(typesym(pointer(0)), #pointer) \ No newline at end of file diff --git a/test/os_test.cy b/test/os_test.cy index 34e9178b5..6ec5b5654 100644 --- a/test/os_test.cy +++ b/test/os_test.cy @@ -27,13 +27,13 @@ t.eq(dir.stat().type, #dir) try os.removeFile('test/assets/write.txt') var file = os.createFile('test/assets/write.txt', false) file.write('foobar') -t.eq(readFile('test/assets/write.txt'), rawstring('foobar')) +t.eq(os.readFile('test/assets/write.txt'), rawstring('foobar')) -- createFile() no truncate. file = os.createFile('test/assets/write.txt', false) -t.eq(readFile('test/assets/write.txt'), rawstring('foobar')) +t.eq(os.readFile('test/assets/write.txt'), rawstring('foobar')) -- createFile() truncate. file = os.createFile('test/assets/write.txt', true) -t.eq(readFile('test/assets/write.txt'), rawstring('')) +t.eq(os.readFile('test/assets/write.txt'), rawstring('')) -- dirName() t.eq(os.dirName('.'), none) @@ -103,13 +103,13 @@ t.eq(file.read(3), rawstring('foo')) -- File.write() from create file = os.createFile('test/assets/write.txt', true) t.eq(file.write('foobar'), 6) -t.eq(readFile('test/assets/write.txt'), rawstring('foobar')) +t.eq(os.readFile('test/assets/write.txt'), rawstring('foobar')) -- File.write() from open file = os.openFile('test/assets/write.txt', #write) file.seekFromEnd(0) t.eq(file.write('abcxyz'), 6) -t.eq(readFile('test/assets/write.txt'), rawstring('foobarabcxyz')) +t.eq(os.readFile('test/assets/write.txt'), rawstring('foobarabcxyz')) -- Dir.iterator() dir = os.openDir('test/assets/dir', true) @@ -140,4 +140,12 @@ if os.system == 'windows': else: t.eq(entries[1].path, rawstring('dir2/file.txt')) t.eq(entries[2].path, rawstring('file.txt')) -t.eq(entries[3].path, rawstring('file2.txt')) \ No newline at end of file +t.eq(entries[3].path, rawstring('file2.txt')) + +-- writeFile() rawstring +if os.cpu != 'wasm32': + var s = rawstring('').insertByte(0, 255) + os.writeFile('test.txt', s) + var read = os.readFile('test.txt') + t.eq(read.len(), 1) + t.eq(read.byteAt(0), 255) \ No newline at end of file diff --git a/test/setup.zig b/test/setup.zig index d9a7ad4e2..7cdf67b54 100644 --- a/test/setup.zig +++ b/test/setup.zig @@ -66,6 +66,7 @@ pub const VMrunner = struct { .vm = @ptrCast(&testVm), }; self.vm.init(t.alloc) catch fatal(); + cy.cli.setupVMForCLI(self.vm); } pub fn deinit(self: *VMrunner) void { @@ -184,6 +185,7 @@ pub const VMrunner = struct { return error.UnreleasedObjects; } try self.vm.init(t.alloc); + cy.cli.setupVMForCLI(self.vm); } fn evalNoReset(self: *VMrunner, src: []const u8) !cy.Value { @@ -241,11 +243,9 @@ pub const EvalResult = anyerror!cy.Value; pub fn eval(config: Config, src: []const u8, optCb: ?*const fn (*VMrunner, EvalResult) anyerror!void) !void { const run = VMrunner.create(); + defer t.alloc.destroy(run); + errdefer run.vm.deinit(); var checkGlobalRC = true; - defer { - run.deinitExt(checkGlobalRC); - t.alloc.destroy(run); - } if (config.silent) { cy.silentError = true; @@ -289,13 +289,14 @@ pub fn eval(config: Config, src: []const u8, optCb: ?*const fn (*VMrunner, EvalR } // Deinit, so global objects from builtins are released. - run.vm.internal().compiler.deinitRtObjects(); run.vm.internal().deinitRtObjects(); if (config.cleanupGC) { _ = try cy.arc.performGC(run.vm.internal()); } + run.vm.deinit(); + if (config.checkGlobalRc) { try cy.arc.checkGlobalRC(run.vm.internal()); } diff --git a/test/trace_test.zig b/test/trace_test.zig index cf557d8bd..d54d75e98 100644 --- a/test/trace_test.zig +++ b/test/trace_test.zig @@ -312,6 +312,7 @@ const VMrunner = struct { .vm = @ptrCast(&testVm), }; self.vm.init(t.alloc) catch cy.fatal(); + cy.cli.setupVMForCLI(self.vm); } fn deinit(self: *VMrunner) void {