From f2c635cd996e43cec836b645a28e31496059d7a6 Mon Sep 17 00:00:00 2001 From: fubark Date: Sun, 1 Oct 2023 11:13:39 -0400 Subject: [PATCH] Module doc gen. Add math.frac/math.isInt. Handle rawstring for concat/interp. Remove deprecated. --- docs/gen-modules.cy | 60 ++ docs/hugo/content/docs/toc/data-types.md | 174 +---- docs/hugo/content/docs/toc/ffi.md | 11 +- docs/hugo/content/docs/toc/modules.md | 838 ++++++++++++++++++----- src/api.zig | 8 + src/builtins/bindings.zig | 80 +-- src/builtins/builtins.cy | 119 +++- src/builtins/builtins.zig | 379 +++++++--- src/builtins/math.cy | 107 ++- src/builtins/math.zig | 18 + src/cbindgen.cy | 2 +- src/codegen.zig | 3 +- src/cyber.zig | 2 +- src/heap.zig | 14 +- src/parser.zig | 21 +- src/sema.zig | 3 +- src/std/os.cy | 176 ++++- src/std/os.zig | 60 +- src/std/test.cy | 8 + src/vm.zig | 10 + src/web.zig | 1 + test/behavior_test.zig | 20 +- test/core_test.cy | 9 +- test/math_test.cy | 19 +- 24 files changed, 1535 insertions(+), 607 deletions(-) create mode 100644 docs/gen-modules.cy diff --git a/docs/gen-modules.cy b/docs/gen-modules.cy new file mode 100644 index 000000000..65ec72a8b --- /dev/null +++ b/docs/gen-modules.cy @@ -0,0 +1,60 @@ +#!cyber + +import os + +type ModulePair object: + path string + section string + +var modules = [ + ModulePair{ path: '../src/builtins/builtins.cy', section: 'builtins' } + ModulePair{ path: '../src/builtins/math.cy', section: 'math' } + ModulePair{ path: '../src/std/os.cy', section: 'os' } + ModulePair{ path: '../src/std/test.cy', section: 'test' } +] + +var curDir = os.dirName(#ModUri) +-- var md = os.readFile('{curDir}/../modules.md') +var md = os.readFile('{curDir}/hugo/content/docs/toc/modules.md') +print md.len() + +for modules each mod: + var src = os.readFile('{curDir}/{mod.path}') + var decls = parseCyber(src)['decls'] + var gen = '\n' + for decls each decl: + match decl.type: + 'funcInit': + -- Two spaces for new line. + var docLine = if decl.docs then '> {decl.docs}\n\n' else '\n' + var params = [] + for decl.header.params each param: + var typeSpec = if param.typeSpec != '' then param.typeSpec else 'any' + params.append('{param.name} {typeSpec}') + var paramsStr = params.joinString(', ') + gen = gen + '> `func {decl.header.name}({paramsStr}) {decl.header.ret}` \n{docLine}' + 'variable': + var docLine = if decl.docs then '> {decl.docs}\n\n' else '\n' + var typeSpec = if decl.typeSpec != '' then decl.typeSpec else 'any' + gen = gen + '> `var {decl.name} {typeSpec}` \n{docLine}' + 'object': + gen = gen + '### `type {decl.name}`\n\n' + for decl.children each child: + if child.type == 'funcInit': + var docLine = if child.docs then '> {child.docs}\n\n' else '\n' + var params = [] + for child.header.params each param: + var typeSpec = if param.typeSpec != '' then param.typeSpec else 'any' + params.append('{param.name} {typeSpec}') + var paramsStr = params.joinString(', ') + gen = gen + '> `func {child.header.name}({paramsStr}) {child.header.ret}` \n{docLine}' + + -- Replace section in modules.md. + var needle = '' + var startIdx = md.find(needle) + needle.len() + var endIdx = md.find('') + md = md[0..startIdx] + gen + md[endIdx..] + +print md.len() +os.writeFile('{curDir}/hugo/content/docs/toc/modules.md', md) +print 'Done.' \ No newline at end of file diff --git a/docs/hugo/content/docs/toc/data-types.md b/docs/hugo/content/docs/toc/data-types.md index 0413bad44..a2cece0e9 100644 --- a/docs/hugo/content/docs/toc/data-types.md +++ b/docs/hugo/content/docs/toc/data-types.md @@ -9,7 +9,7 @@ In Cyber, there are primitive types and object types. Primitives are copied arou The `none` value represents an empty value. This is similar to null in other languages. ## Booleans. -Booleans can be `true` or `false`. +Booleans can be `true` or `false`. See [`type boolean`]({{}}). ```cy var a = true if a: @@ -22,7 +22,7 @@ When other value types are coerced to the boolean type, the truthy value is dete ## Numbers. ### Integers. -`int` is the default integer type. It has 48-bits and can represent integers in the range -(2{{}}) to 2{{}}-1. +`int` is the default integer type. It has 48-bits and can represent integers in the range -(2{{}}) to 2{{}}-1. See [`type int`]({{}}). When a numeric literal is used and the type can not be inferred, it will default to the `int` type: ```cy @@ -46,7 +46,7 @@ var b = int(a) In addition to arithmetic operations, integers can also perform [bitwise operations]({{}}). ### Floats. -`float` is the default floating point type. It has a (IEEE 754) 64-bit floating point format. +`float` is the default floating point type. It has a (IEEE 754) 64-bit floating point format. See [`type float`]({{}}). Although a `float` represents a decimal number, it can also represent integers between -(2{{}}-1) and (2{{}}-1). Any integers beyond the safe integer range is not guaranteed to have a unique representation. @@ -71,7 +71,9 @@ var b = float(a) > _Planned Feature_ ## Strings. -The `string` type represents a sequence of UTF-8 codepoints, also known as `runes`. Each rune is stored internally as 1-4 bytes and can be represented as an `int`. Under the hood, Cyber implements 6 different internal string types to optimize string operations, but the user just sees them as one type and doesn't need to care about this detail under normal usage. +The `string` type represents a sequence of UTF-8 codepoints, also known as `runes`. Each rune is stored internally as 1-4 bytes and can be represented as an `int`. See [`type string`]({{}}). + +Under the hood, Cyber implements 5 different internal string types to optimize string operations. Strings are **immutable**, so operations that do string manipulation return a new string. By default, small strings are interned to reduce memory footprint. @@ -91,6 +93,12 @@ var apple = 'Bob\'s fruit' apple = "Bob's fruit" ``` +Concatenate two strings together with the `+` operator or the method `concat`. +```cy +var res = 'abc' + 'xyz' +res = res.concat('end') +``` + Strings are UTF-8 encoded. ```cy var str = 'abc🦊xyz🐶' @@ -127,6 +135,7 @@ The following escape sequences are supported: | \t | 0x09 | Horizontal tab character. | The boundary of each line can be set with a vertical line character. This makes it easier to see the whitespace. +> _Planned Feature_ ```cy var poem = "line a | two spaces from the left @@ -156,66 +165,6 @@ i += 1 print(i + str[i..].findRune(0u'c')) -- "5" ``` -### `type string` -```cy -func concat(self, str string) string --- Returns a new string that concats this string and `str`. - -func endsWith(self, suffix string) bool --- Returns whether the string ends with `suffix`. - -func find(self, needle string) int? --- Returns the first index of substring `needle` in the string or `none` if not found. - -func findAnyRune(self, set string) int? --- Returns the first index of any UTF-8 rune in `set` or `none` if not found. - -func findRune(self, needle int) int? --- Returns the first index of UTF-8 rune `needle` in the string or `none` if not found. - -func insert(self, idx int, str string) string --- Returns a new string with `str` inserted at index `idx`. - -func isAscii(self) bool --- Returns whether the string contains all ASCII runes. - -func len(self) int --- Returns the number of UTF-8 runes in the string. - -func less(self, str string) bool --- Returns whether this string is lexicographically before `str`. - -func lower(self) string --- Returns this string in lowercase. - -func replace(self, needle string, replacement string) string --- Returns a new string with all occurrences of `needle` replaced with `replacement`. | - -func repeat(self, n int) string --- Returns a new string with this string repeated `n` times. - -func runeAt(self, idx int) int --- Returns the UTF-8 rune at index `idx`. - -func slice(self, start int, end int) string --- Returns a slice into this string from `start` to `end` (exclusive) indexes. This is equivalent to using the slice index operator `[start..end]`. - -func sliceAt(self, idx int) string --- Returns the UTF-8 rune at index `idx` as a single rune string. - -func split(self, delim string) List --- Returns a list of UTF-8 strings split at occurrences of `delim`. - -func startsWith(self, prefix string) bool --- Returns whether the string starts with `prefix`. - -func trim(self, mode symbol, trimRunes any) string --- Returns the string with ends trimmed from runes in `trimRunes`. `mode` can be #left, #right, or #ends. - -func upper(self) string --- Returns this string in uppercase. -``` - ### String Interpolation. You can embed expressions into string templates using braces. @@ -233,7 +182,7 @@ var str = 'Scoreboard: \{ Bob \} {points}' String templates can not contain nested string templates. ### rawstring. -A `rawstring` does not automatically validate the string and is indexed by bytes and not UTF-8 runes. +A `rawstring` does not automatically validate the string and is indexed by bytes and not UTF-8 runes. See [`type rawstring`]({{}}). Using the index operator will return the UTF-8 rune starting at the given byte index as a slice. If the index does not begin a valid UTF-8 rune, `error.InvalidRune` is returned. This is equivalent to calling the method `sliceAt()`. ```cy @@ -243,77 +192,8 @@ print str[1] -- error.InvalidRune print str[-1] -- "d" ``` -### `type rawstring` -```cy -func byteAt(self, idx int) int --- Returns the byte value (0-255) at the given index `idx`. - -func concat(self, str string) string --- Returns a new string that concats this string and `str`. - -func endsWith(self, suffix string) bool --- Returns whether the string ends with `suffix`. - -func find(self, needle string) int? --- Returns the first index of substring `needle` in the string or `none` if not found. - -func findAnyRune(self, set string) int? --- Returns the first index of any UTF-8 rune in `set` or `none` if not found. - -func findRune(self, needle int) int? --- Returns the first index of UTF-8 rune `needle` in the string or `none` if not found. - -func insert(self, idx int, str string) string --- Returns a new string with `str` inserted at index `idx`. - -func insertByte(self, idx int, byte int) string --- Returns a new string with `byte` inserted at index `idx`. - -func isAscii(self) bool --- Returns whether the string contains all ASCII runes. - -func len(self) int --- Returns the number of bytes in the string. - -func less(self, str rawstring) bool --- Returns whether this rawstring is lexicographically before `str`. - -func lower(self) string --- Returns this string in lowercase. - -func repeat(self, n int) rawstring --- Returns a new rawstring with this rawstring repeated `n` times. - -func replace(self, needle string, replacement string) string --- Returns a new string with all occurrences of `needle` replaced with `replacement`. - -func runeAt(self, idx int) int --- Returns the UTF-8 rune at index `idx`. If the index does not begin a UTF-8 rune, `error.InvalidRune` is returned. - -func slice(self, start int, end int) rawstring --- Returns a slice into this string from `start` to `end` (exclusive) indexes. This is equivalent to using the slice index operator `[start..end]`. - -func sliceAt(self, idx int) string --- Returns the UTF-8 rune at index `idx` as a single rune string. If the index does not begin a UTF-8 rune, `error.InvalidRune` is returned. - -func split(self, delim string) List --- Returns a list of rawstrings split at occurrences of `delim`. - -func startsWith(self, prefix string) bool --- Returns whether the string starts with `prefix`. - -func upper(self) string --- Returns this string in uppercase. - -func trim(self, mode symbol, trimRunes any) rawstring --- Returns the string with ends trimmed from runes in `trimRunes`. `mode` can be #left, #right, or #ends. - -func utf8(self) string --- Returns a valid UTF-8 string or returns `error.InvalidRune`. -``` - ## Lists. -Lists are a builtin type that holds an ordered collection of elements. Lists grow or shrink as you insert or remove elements. +Lists are a builtin type that holds an ordered collection of elements. Lists grow or shrink as you insert or remove elements. See [`type List`]({{}}). ```cy -- Construct a new list. var list = [1, 2, 3] @@ -359,25 +239,11 @@ for list each it: list.remove(1) ``` -### `type List` -| Method | Summary | -| ------------- | ----- | -| `append(val any) none` | Appends a value to the end of the list. | -| `concat(val any) none` | Concats the elements of another list to the end of this list. | -| `insert(idx int, val any) none` | Inserts a value at index `idx`. | -| `iterator() Iterator` | Returns a new iterator over the list elements. | -| `joinString(separator any) string` | Returns a new string that joins the elements with `separator`. | -| `len() int` | Returns the number of elements in the list. | -| `seqIterator() SequenceIterator` | Returns a new sequence iterator over the list elements. | -| `remove(idx int) none` | Removes an element at index `idx`. | -| `resize(len int) none` | Resizes the list to `len` elements. If the new size is bigger, `none` values are appended to the list. If the new size is smaller, elements at the end of the list are removed. | -| `sort(less func (a, b) bool) none` | Sorts the list with the given `less` function. If element `a` should be ordered before `b`, the function should return `true` otherwise `false`. | - ## Tuples. > _Incomplete: Tuples can only be created from @host funcs at the moment._ ## Maps. -Maps are a builtin type that store key value pairs in dictionaries. +Maps are a builtin type that store key value pairs in dictionaries. See [`type Map`]({{}}). ```cy var map = { a: 123, b: () => 5 } print map['a'] @@ -428,14 +294,6 @@ for map each [val, key]: print '{key} -> {value}' ``` -### `type Map` -| Method | Summary | -| ------------- | ----- | -| `iterator() Iterator` | Returns a new iterator over the map elements. | -| `seqIterator() SequenceIterator` | Returns a new sequence iterator over the map elements. | -| `remove(key any) none` | Removes the element with the given key `key`. | -| `size() int` | Returns the number of key-value pairs in the map. | - ## Objects. Any value that isn't a primitive is an object. You can declare your own object types using the `type object` declaration. Object types are similar to structs and classes in other languages. You can declare members and methods. Unlike classes, there is no concept of inheritance at the language level. ```cy diff --git a/docs/hugo/content/docs/toc/ffi.md b/docs/hugo/content/docs/toc/ffi.md index 9bcff93f4..6ee0f091d 100644 --- a/docs/hugo/content/docs/toc/ffi.md +++ b/docs/hugo/content/docs/toc/ffi.md @@ -105,13 +105,10 @@ var res = lib.ptrToMyObject(ptr) ``` ## Pointers -A `pointer` is used to read or write to an exact memory address. This is typically used for FFI to manually map Cyber types to C, and back. A new pointer can be created with the builtin `pointer`. +A `pointer` is used to read or write to an exact memory address. This is typically used for FFI to manually map Cyber types to C, and back. See [`type pointer`]({{}}). + +A new pointer can be created with the builtin `pointer`. ```cy var ptr = pointer(0xDEADBEEF) print ptr.value() --'3735928559' -``` - -### `type pointer` -| Method | Summary | -| ------------- | ----- | -| `value() int` | Returns the memory address as an `int`. The value may be negative since it's bitcasted from an unsigned 48-bit integer but it retains the original pointer bits. | \ No newline at end of file +``` \ No newline at end of file diff --git a/docs/hugo/content/docs/toc/modules.md b/docs/hugo/content/docs/toc/modules.md index f4d20a02c..752aaca51 100644 --- a/docs/hugo/content/docs/toc/modules.md +++ b/docs/hugo/content/docs/toc/modules.md @@ -93,13 +93,13 @@ import os print os.dirName(#ModUri) -- Prints '/some/path' ``` -The annotation `@hide` provides a hint to editors that the static symbol should not appear in the auto-complete. Despite this, the symbol is still reachable. +## Visibility. +The annotation `@hide` provides a hint to editors that a static symbol should not appear in the auto-complete. Despite this, the symbol is still reachable. ## 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._ ## builtins. The `builtins` module contains functions related to Cyber and common utilities. It is automatically imported into each script's namespace. @@ -111,182 +111,706 @@ print 'hello' var id = typeid('my str') print id ``` + +> `func arrayFill(val any, n int) none` +> 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. -| 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`. | -| `copy(val any) any` | Copies a primitive value or creates a shallow copy of an object value. | -| `dump(val any) none` | Prints the result of `toCyon` on a value. | -| `error(e (enum \| symbol)) error` | Create an error from an enum or symbol. | -| `evalJS(val string) none` | Evals JS from the host environment. This is only available in a web WASM build of Cyber. | -| `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. | -| `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. | -| `performGC() map` | Runs the garbage collector once to detect reference cycles and abandoned objects. Returns the statistics of the run in a map value. | -| `pointer(val any) pointer` | Converts a `int` to a `pointer` value, or casts to a `pointer`. This is usually used with FFI. | -| `print(s string) none` | Prints a value. The host determines how it is printed. | -| `rawstring(str string) rawstring` | Converts a string to a `rawstring`. | -| `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 | +> `func copy(val any) none` +> Copies a primitive value or creates a shallow copy of an object value. + +> `func dump(val any) none` +> Prints the result of `toCyon` on a value. + +> `func errorReport() none` + +> `func isAlpha(val int) none` +> Returns whether a rune is an alphabetic letter. + +> `func isDigit(val int) none` +> Returns whether a rune is a digit. + +> `func must(val any) none` +> If `val` is an error, `panic(val)` is invoked. Otherwise, `val` is returned. + +> `func panic(err any) none` +> Stop execution in the current fiber and starts unwinding the call stack. See [Unexpected Errors]({{}}). + +> `func parseCyber(src any) none` +> 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. + +> `func parseCyon(src any) none` +> Parses a CYON string into a value. + +> `func performGC() none` +> Runs the garbage collector once to detect reference cycles and abandoned objects. Returns the statistics of the run in a map value. + +> `func print(str any) none` +> Prints a value. The host determines how it is printed. + +> `func runestr(val int) none` +> Converts a rune to a string. + +> `func toCyon(val any) none` +> Encodes a value to CYON string. + +> `func typeof(val any) none` +> Returns the value's type as a `metatype` object. + +> `func typesym(val any) none` +> 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 + +### `type boolean` + +> `func $call(val any) none` +> Converts a value to either `true` or `false`. + +### `type error` + +> `func $call(val any) none` +> Create an error from an enum or symbol. + +> `func value(self any) none` + +### `type int` + +> `func $call(val any) none` +> Converts a value to an 48-bit integer. + +> `func $prefix~(self any) none` + +> `func $prefix-(self any) none` + +> `func $infix<(self any, o any) none` + +> `func $infix<=(self any, o any) none` + +> `func $infix>(self any, o any) none` + +> `func $infix>=(self any, o any) none` + +> `func $infix+(self any, o any) none` + +> `func $infix-(self any, o any) none` + +> `func $infix*(self any, o any) none` + +> `func $infix/(self any, o any) none` + +> `func $infix%(self any, o any) none` + +> `func $infix^(self any, o any) none` + +> `func $infix&(self any, o any) none` + +> `func $infix|(self any, o any) none` + +> `func $infix||(self any, o any) none` + +> `func $infix<<(self any, o any) none` + +> `func $infix>>(self any, o any) none` + +### `type float` + +> `func $call(val any) none` +> Converts the value to a `float`. Panics if type conversion fails. + +> `func $prefix-(self any) none` + +> `func $infix<(self any, o any) none` + +> `func $infix<=(self any, o any) none` + +> `func $infix>(self any, o any) none` + +> `func $infix>=(self any, o any) none` + +> `func $infix+(self any, o any) none` + +> `func $infix-(self any, o any) none` + +> `func $infix*(self any, o any) none` + +> `func $infix/(self any, o any) none` + +> `func $infix%(self any, o any) none` + +> `func $infix^(self any, o any) none` + +### `type List` + +> `func $index(self any, idx any) none` + +> `func $setIndex(self any, idx any, val any) none` + +> `func add(self any, val any) none` + +> `func append(self any, val any) none` +> Appends a value to the end of the list. + +> `func concat(self any, list List) none` +> Concats the elements of another list to the end of this list. + +> `func insert(self any, idx int, val any) none` +> Inserts a value at index `idx`. + +> `func iterator(self any) none` +> Returns a new iterator over the list elements. + +> `func joinString(self any, sep any) none` +> Returns a new string that joins the elements with `separator`. + +> `func len(self any) none` +> Returns the number of elements in the list. + +> `func seqIterator(self any) none` +> Returns a new sequence iterator over the list elements. + +> `func remove(self any, idx int) none` +> Removes an element at index `idx`. + +> `func resize(self any, size int) none` +> Resizes the list to `len` elements. If the new size is bigger, `none` values are appended to the list. If the new size is smaller, elements at the end of the list are removed. + +> `func sort(self any, lessFn any) none` +> Sorts the list with the given `less` function. If element `a` should be ordered before `b`, the function should return `true` otherwise `false`. + +### `type ListIterator` + +> `func next(self any) none` + +> `func nextSeq(self any) none` + +### `type Map` + +> `func $index(self any, key any) none` + +> `func $setIndex(self any, key any, val any) none` + +> `func remove(self any, key any) none` +> Removes the element with the given key `key`. + +> `func size(self any) none` +> Returns the number of key-value pairs in the map. + +> `func iterator(self any) none` +> Returns a new iterator over the map elements. + +> `func seqIterator(self any) none` +> Returns a new sequence iterator over the map elements. + +### `type MapIterator` + +> `func next(self any) none` + +> `func nextSeq(self any) none` + +### `type pointer` + +> `func $call(val any) none` +> Converts a `int` to a `pointer` value, or casts to a `pointer`. This is usually used with FFI. + +> `func value(self any) none` +> Returns the memory address as an `int`. The value may be negative since it's bitcasted from an unsigned 48-bit integer but it retains the original pointer bits. + + + +### `type string trait` +> `func $call(val any) string` +> Converts a value to a string. + +> `func concat(self, str string) string | rawstring` +> Returns a new string that concats this string and `str`. + +> `func endsWith(self, suffix string) bool` +> Returns whether the string ends with `suffix`. + +> `func find(self, needle string) int?` +> Returns the first index of substring `needle` in the string or `none` if not found. + +> `func findAnyRune(self, set string) int?` +> Returns the first index of any UTF-8 rune in `set` or `none` if not found. + +> `func findRune(self, needle int) int?` +> Returns the first index of UTF-8 rune `needle` in the string or `none` if not found. + +> `func insert(self, idx int, str string) string` +> Returns a new string with `str` inserted at index `idx`. + +> `func isAscii(self) bool` +> Returns whether the string contains all ASCII runes. + +> `func len(self) int` +> Returns the number of UTF-8 runes in the string. + +> `func less(self, str string) bool` +> Returns whether this string is lexicographically before `str`. + +> `func lower(self) string` +> Returns this string in lowercase. + +> `func replace(self, needle string, replacement string) string` +> Returns a new string with all occurrences of `needle` replaced with `replacement`. + +> `func repeat(self, n int) string` +> Returns a new string with this string repeated `n` times. + +> `func runeAt(self, idx int) int` +> Returns the UTF-8 rune at index `idx`. + +> `func slice(self, start int, end int) string` +> Returns a slice into this string from `start` to `end` (exclusive) indexes. This is equivalent to using the slice index operator `[start..end]`. + +> `func sliceAt(self, idx int) string` +> Returns the UTF-8 rune at index `idx` as a single rune string. + +> `func split(self, delim string) List` +> Returns a list of UTF-8 strings split at occurrences of `delim`. + +> `func startsWith(self, prefix string) bool` +> Returns whether the string starts with `prefix`. + +> `func trim(self, mode symbol, trimRunes any) string` +> Returns the string with ends trimmed from runes in `trimRunes`. `mode` can be .left, .right, or .ends. + +> `func upper(self) string` +> Returns this string in uppercase. + +### `type rawstring trait` +> `func $call(str string) rawstring` +> Converts a string to a `rawstring`. + +> `func byteAt(self, idx int) int` +> Returns the byte value (0-255) at the given index `idx`. + +> `func concat(self, str string | rawstring) rawstring` +> Returns a new rawstring that concats this rawstring and `str`. + +> `func endsWith(self, suffix string | rawstring) bool` +> Returns whether the string ends with `suffix`. + +> `func find(self, needle string | rawstring) int?` +> Returns the first index of substring `needle` in the string or `none` if not found. + +> `func findAnyRune(self, set string | rawstring) int?` +> Returns the first index of any UTF-8 rune in `set` or `none` if not found. + +> `func findRune(self, needle int) int?` +> Returns the first index of UTF-8 rune `needle` in the string or `none` if not found. + +> `func insert(self, idx int, str string | rawstring) rawstring` +> Returns a new string with `str` inserted at index `idx`. + +> `func insertByte(self, idx int, byte int) rawstring` +> Returns a new string with `byte` inserted at index `idx`. + +> `func isAscii(self) bool` +> Returns whether the string contains all ASCII runes. + +> `func len(self) int` +> Returns the number of bytes in the string. + +> `func less(self, str rawstring) bool` +> Returns whether this rawstring is lexicographically before `str`. + +> `func lower(self) rawstring` +> Returns this string in lowercase. + +> `func repeat(self, n int) rawstring` +> Returns a new rawstring with this rawstring repeated `n` times. + +> `func replace(self, needle string | rawstring, replacement string | rawstring) rawstring` +> Returns a new string with all occurrences of `needle` replaced with `replacement`. + +> `func runeAt(self, idx int) int` +> Returns the UTF-8 rune at index `idx`. If the index does not begin a UTF-8 rune, `error.InvalidRune` is returned. + +> `func slice(self, start int, end int) rawstring` +> Returns a slice into this string from `start` to `end` (exclusive) indexes. This is equivalent to using the slice index operator `[start..end]`. + +> `func sliceAt(self, idx int) rawstring` +> Returns the UTF-8 rune at index `idx` as a single rune string. If the index does not begin a UTF-8 rune, `error.InvalidRune` is returned. + +> `func split(self, delim string) List` +> Returns a list of rawstrings split at occurrences of `delim`. + +> `func startsWith(self, prefix string | rawstring) bool` +> Returns whether the string starts with `prefix`. + +> `func upper(self) rawstring` +> Returns this string in uppercase. + +> `func trim(self, mode symbol, trimRunes any) rawstring` +> Returns the string with ends trimmed from runes in `trimRunes`. `mode` can be .left, .right, or .ends. + +> `func utf8(self) string` +> Returns a valid UTF-8 string or returns `error.InvalidRune`. ## math. The math module contains commonly used math constants and functions. Sample usage: ```cy -import m 'math' +import math var r = 10 -print(m.pi * r^2) +print(math.pi * r^2) ``` -| Variable | Type | Summary | -| ------------- | ------------- | ----- | -| e | float | Euler's number and the base of natural logarithms; approximately 2.718. | -| inf | float | Infinity. | -| log10e | float | Base-10 logarithm of E; approximately 0.434. | -| log2e | float | Base-2 logarithm of E; approximately 1.443. | -| ln10 | float | Natural logarithm of 10; approximately 2.303. | -| ln2 | float | Natural logarithm of 2; approximately 0.693. | -| nan | float | Not a number. Note that nan == nan, however, if a nan came from an arithmetic operation, the comparison is undefined (it may be true or false, so it is not reliable). | -| neginf | float | Negative infinity. | -| pi | float | Ratio of a circle's circumference to its diameter; approximately 3.14159. | -| sqrt1_2 | float | Square root of ½; approximately 0.707. | -| sqrt2 | float | Square root of 2; approximately 1.414. | - -| Function | Summary | -| -- | -- | -| abs(float) float | Returns the absolute value of x. | -| acos(float) float | Returns the arccosine of x. | -| acosh(float) float | Returns the hyperbolic arccosine of x. | -| asin(float) float | Returns the arcsine of x. | -| asinh(float) float | Returns the hyperbolic arcsine of a number. | -| atan(float) float | Returns the arctangent of x. | -| atan2(float, float) float | Returns the arctangent of the quotient of its arguments. | -| atanh(float) float | Returns the hyperbolic arctangent of x. | -| cbrt(float) float | Returns the cube root of x. | -| ceil(float) float | Returns the smallest integer greater than or equal to x. | -| clz32(float) float | Returns the number of leading zero bits of the 32-bit integer x. | -| cos(float) float | Returns the cosine of x. | -| cosh(float) float | Returns the hyperbolic cosine of x. | -| exp(float) float | Returns e^x, where x is the argument, and e is Euler's number (2.718…, the base of the natural logarithm). | -| expm1(float) float | Returns subtracting 1 from exp(x). | -| floor(float) float | Returns the largest integer less than or equal to x. | -| hypot(float, float) float | Returns the square root of the sum of squares of its arguments. | -| isNaN(float) bool | Returns whether x is not a number. | -| ln(float) float | Returns the natural logarithm (㏒e; also, ㏑) of x. | -| log(float, float) float | Returns the logarithm of y with base x. | -| log10(float) float | Returns the base-10 logarithm of x. | -| log1p(float) float | Returns the natural logarithm (㏒e; also ㏑) of 1 + x for the number x. | -| log2(float) float | Returns the base-2 logarithm of x. | -| max(float, float) float | Returns the largest of two numbers. | -| min(float, float) float | Returns the smallest of two numbers. | -| mul32(float, float) float | Returns the result of the 32-bit integer multiplication of x and y. Integer overflow is allowed. | -| pow(float, float) float | Returns base x to the exponent power y (that is, x^y). | -| random() float | Returns a pseudo-random number between 0 and 1. | -| round(float) float | Returns the value of the number x rounded to the nearest integer. | -| sign(float) float | Returns the sign of the x, indicating whether x is positive, negative, or zero. | -| sin(float) float | Returns the sine of x. | -| sinh(float) float | Returns the hyperbolic sine of x. | -| sqrt(float) float | Returns the positive square root of x. | -| tan(float) float | Returns the tangent of x. | -| tanh(float) float | Returns the hyperbolic tangent of x. | -| trunc(float) float | Returns the integer portion of x, removing any fractional digits. | + + +> `var e float` +> Euler's number and the base of natural logarithms; approximately 2.718. + +> `var inf float` +> Infinity. + +> `var log10e float` +> Base-10 logarithm of E; approximately 0.434. + +> `var log2e float` +> Base-2 logarithm of E; approximately 1.443. + +> `var ln10 float` +> Natural logarithm of 10; approximately 2.303. + +> `var ln2 float` +> Natural logarithm of 2; approximately 0.693. + +> `var maxSafeInt float` +> The maximum integer value that can be safely represented as a float. 2^53-1 or 9007199254740991. + +> `var minSafeInt float` +> The minumum integer value that can be safely represented as a float. -(2^53-1) or -9007199254740991. + +> `var nan float` +> Not a number. Note that nan == nan. However, if a nan came from an arithmetic operation, the comparison is undefined. Use `isNaN` instead. + +> `var neginf float` +> Negative infinity. + +> `var pi float` +> Ratio of a circle's circumference to its diameter; approximately 3.14159. + +> `var sqrt1_2 float` +> Square root of ½; approximately 0.707. + +> `var sqrt2 float` +> Square root of 2; approximately 1.414. + +> `func abs(a float) none` +> Returns the absolute value of x. + +> `func acos(a float) none` +> Returns the arccosine of x. + +> `func acosh(a float) none` +> Returns the hyperbolic arccosine of x. + +> `func asin(a float) none` +> Returns the arcsine of x. + +> `func asinh(a float) none` +> Returns the hyperbolic arcsine of a number. + +> `func atan(a float) none` +> Returns the arctangent of x. + +> `func atan2(a float, b float) none` +> Returns the arctangent of the quotient of its arguments. + +> `func atanh(a float) none` +> Returns the hyperbolic arctangent of x. + +> `func cbrt(a float) none` +> Returns the cube root of x. + +> `func ceil(a float) none` +> Returns the smallest integer greater than or equal to x. + +> `func clz32(a float) none` +> Returns the number of leading zero bits of the 32-bit integer x. + +> `func cos(a float) none` +> Returns the cosine of x. + +> `func cosh(a float) none` +> Returns the hyperbolic cosine of x. + +> `func exp(a float) none` +> Returns e^x, where x is the argument, and e is Euler's number (2.718…, the base of the natural logarithm). + +> `func expm1(a float) none` +> Returns subtracting 1 from exp(x). + +> `func floor(a float) none` +> Returns the largest integer less than or equal to x. + +> `func frac(a float) none` +> Returns the fractional or decimal part of a float value. + +> `func hypot(a float, b float) none` +> Returns the square root of the sum of squares of its arguments. + +> `func isInt(a float) none` +> Returns true if the float has no fractional part, otherwise false. + +> `func isNaN(a float) none` +> Returns whether x is not a number. + +> `func ln(a float) none` +> Returns the natural logarithm (㏒e; also, ㏑) of x. + +> `func log(a float, b float) none` +> Returns the logarithm of y with base x. + +> `func log10(a float) none` +> Returns the base-10 logarithm of x. + +> `func log1p(a float) none` +> Returns the natural logarithm (㏒e; also ㏑) of 1 + x for the number x. + +> `func log2(a float) none` +> Returns the base-2 logarithm of x. + +> `func max(a float, b float) none` +> Returns the largest of two numbers. + +> `func min(a float, b float) none` +> Returns the smallest of two numbers. + +> `func mul32(a float, b float) none` +> Returns the result of the 32-bit integer multiplication of x and y. Integer overflow is allowed. + +> `func pow(a float, b float) none` +> Returns base x to the exponent power y (that is, x^y). + +> `func random() none` +> Returns a pseudo-random number between 0 and 1. + +> `func round(a float) none` +> Returns the value of the number x rounded to the nearest integer. + +> `func sign(a float) none` +> Returns the sign of the x, indicating whether x is positive, negative, or zero. + +> `func sin(a float) none` +> Returns the sine of x. + +> `func sinh(a float) none` +> Returns the hyperbolic sine of x. + +> `func sqrt(a float) none` +> Returns the positive square root of x. + +> `func tan(a float) none` +> Returns the tangent of x. + +> `func tanh(a float) none` +> Returns the hyperbolic tangent of x. + +> `func trunc(a float) none` +> Returns the integer portion of x, removing any fractional digits. + + ## 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: ```cy -import os 'os' +import os var map = os.getEnvAll() -for map each k, v: +for map each [k, v]: print '{k} -> {v}' ``` -| Variable | Type | Summary | -| -- | -- | -- | -| cpu | string | The current cpu arch's tag name. | -| endian | #little, #big | The current arch's endianness. | -| stderr | File | Standard error file descriptor. | -| stdin | File | Standard input file descriptor. | -| stdout | File | Standard output file descriptor. | -| system | string | The current operating system's tag name. | -| vecBitSize | int | Default SIMD vector bit size. | - -| Function | Summary | -| -- | -- | -| `access(path any, mode (#read \| #write \| #readWrite)) true \| error` | Attempts to access a file at the given `path` with the `#read`, `#write`, or `#readWrite` mode. Return true or an error. | -| `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. | + + +> `var cpu string` +> The current cpu arch's tag name. + +> `var endian symbol` +> The current arch's endianness: .little, .big + +> `var stderr any` +> Standard error file descriptor. + +> `var stdin any` +> Standard input file descriptor. + +> `var stdout any` +> Standard output file descriptor. + +> `var system string` +> The current operating system's tag name. + +> `var vecBitSize int` +> Default SIMD vector bit size. + +> `func access(path any, mode symbol) none` +> Attempts to access a file at the given `path` with the `.read`, `.write`, or `.readWrite` mode. Return true or an error. + +> `func args() none` +> 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. + +> `func bindLib(path any, decls List) none` +> Calls `bindLib(path, decls, {})`. + +> `func bindLib(path any, decls List, config Map) none` +> 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. + +> `func cacheUrl(url any) none` +> Returns the path of a locally cached file of `url`. If no such file exists locally, it's fetched from `url`. + +> `func copyFile(srcPath any, dstPath any) none` +> Copies a file to a destination path. + +> `func createDir(path any) none` +> Creates the directory at `path`. Returns `true` if successful. + +> `func createFile(path any, truncate boolean) none` +> Creates and opens the file at `path`. If `truncate` is true, an existing file will be truncated. + +> `func cstr(s any) none` +> Returns a null terminated C string. + +> `func cwd() none` +> Returns the current working directory. + +> `func dirName(path any) none` +> Returns the given path with its last component removed. + +> `func execCmd(args List) none` +> Runs a shell command and returns the stdout/stderr. + +> `func exePath() none` +> Returns the current executable's path. + +> `func exit(status int) none` +> Exits the program with a status code. + +> `func fetchUrl(url any) none` +> Fetches the contents at `url` using the HTTP GET request method. + +> `func free(ptr pointer) none` +> Frees the memory located at `ptr`. + +> `func fromCstr(ptr pointer) none` +> Returns a `rawstring` from a null terminated C string. + +> `func getEnv(key any) none` +> Returns an environment value by key. + +> `func getEnvAll() none` +> Returns all environment entries as a `Map`. + +> `func getInput() none` +> 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`. + +> `func malloc(size int) none` +> Allocates `size` bytes of memory and returns a pointer. + +> `func milliTime() none` +> Return the calendar timestamp, in milliseconds, relative to UTC 1970-01-01. + +> `func openDir(path any) none` +> Invokes `openDir(path, false)`. + +> `func openDir(path any, iterable boolean) none` +> Opens a directory at the given `path`. `iterable` indicates that the directory's entries can be iterated. + +> `func openFile(path any, mode symbol) none` +> Opens a file at the given `path` with the `.read`, `.write`, or `.readWrite` mode. + +> `func parseArgs(options List) none` +> Given expected `ArgOption`s, returns a map of the options and a `rest` entry which contains the non-option arguments. | + +> `func readAll() none` +> Reads stdin to the EOF as a `rawstring`. + +> `func readFile(path any) none` +> Reads the file contents into a `rawstring` value. + +> `func readLine() none` + +> `func realPath(path any) none` +> Returns the absolute path of the given path. + +> `func removeDir(path any) none` +> Removes an empty directory at `path`. Returns `true` if successful. + +> `func removeFile(path any) none` +> Removes the file at `path`. Returns `true` if successful. + +> `func setEnv(key any, val any) none` +> Sets an environment value by key. + +> `func sleep(ms float) none` +> Pauses the current thread for given milliseconds. + +> `func unsetEnv(key any) none` +> Removes an environment value by key. + +> `func writeFile(path any, contents any) none` +> Writes a string value to a file. ### `type File` -| Method | Summary | -| -- | -- | -| `close() none` | Closes the file handle. File ops invoked afterwards will return `error.Closed`. | -| `read(n float) rawstring` | Reads at most `n` bytes as a `rawstring`. `n` must be at least 1. A result with length 0 indicates the end of file was reached. | -| `readToEnd() rawstring` | Reads to the end of the file and returns the content as a `rawstring`. | -| `seek(pos float) none` | Seeks the read/write position to `pos` bytes from the start. Negative `pos` is invalid. | -| `seekFromCur(pos float) none` | Seeks the read/write position by `pos` bytes from the current position. | -| `seekFromEnd(pos float) none` | Seeks the read/write position by `pos` bytes from the end. Positive `pos` is invalid. | -| `stat() Map` | Returns info about the file as a `Map`. | -| `streamLines() Iterable` | Equivalent to `streamLines(4096)`. | -| `streamLines(bufSize float) Iterable` | Returns an iterable that streams lines ending in `\n`, `\r`, `\r\n`, or the `EOF`. The lines returned include the new line character(s). A buffer size of `bufSize` bytes is allocated for reading. If `\r` is found at the end of the read buffer, the line is returned instead of waiting to see if the next read has a connecting `\n`. | -| `write(data (string \| rawstring)) float` | Writes a `string` or `rawstring` at the current file position. The number of bytes written is returned. | + +> `func close(self any) none` +> Closes the file handle. File ops invoked afterwards will return `error.Closed`. + +> `func iterator(self any) none` + +> `func next(self any) none` + +> `func read(self any, n int) none` +> Reads at most `n` bytes as a `rawstring`. `n` must be at least 1. A result with length 0 indicates the end of file was reached. + +> `func readToEnd(self any) none` +> Reads to the end of the file and returns the content as a `rawstring`. + +> `func seek(self any, n int) none` +> Seeks the read/write position to `pos` bytes from the start. Negative `pos` is invalid. + +> `func seekFromCur(self any, n int) none` +> Seeks the read/write position by `pos` bytes from the current position. + +> `func seekFromEnd(self any, n int) none` +> Seeks the read/write position by `pos` bytes from the end. Positive `pos` is invalid. + +> `func stat(self any) none` +> Returns info about the file as a `Map`. + +> `func streamLines(self any) none` +> Equivalent to `streamLines(4096)`. + +> `func streamLines(self any, bufSize int) none` +> Returns an iterable that streams lines ending in `\n`, `\r`, `\r\n`, or the `EOF`. The lines returned include the new line character(s). A buffer size of `bufSize` bytes is allocated for reading. If `\r` is found at the end of the read buffer, the line is returned instead of waiting to see if the next read has a connecting `\n`. + +> `func write(self any, val any) none` +> Writes a `string` or `rawstring` at the current file position. The number of bytes written is returned. ### `type Dir` -| Method | Summary | -| -- | -- | -| `iterator() Iterator \| error` | Returns a new iterator over the directory entries. If this directory was not opened with the iterable flag, `error.NotAllowed` is returned instead. | -| `stat() Map` | Returns info about the file as a `Map`. | -| `walk() Iterator \| error` | Returns a new iterator over the directory recursive entries. If this directory was not opened with the iterable flag, `error.NotAllowed` is returned instead. | +> `func iterator(self any) none` +> Returns a new iterator over the directory entries. If this directory was not opened with the iterable flag, `error.NotAllowed` is returned instead. + +> `func stat(self any) none` +> Returns info about the file as a `Map`. + +> `func walk(self any) none` +> Returns a new iterator over the directory recursive entries. If this directory was not opened with the iterable flag, `error.NotAllowed` is returned instead. + +### `type DirIterator` + +> `func next(self any) none` + + ### `map DirEntry` | Entry | Summary | | -- | -- | @@ -318,8 +842,16 @@ var a = 123 + 321 t.eq(a, 444) ``` -| Function | Summary | -| -- | -- | -| `eq(a any, b any) true \| error` | Returns whether two values are equal. Returns `error.AssertError` if types do not match up. | -| `eqList(a any, b any) true \| error` | Returns true if two lists have the same size and the elements are equal as if `eq` was called on those corresponding elements. | -| `eqNear(a any, b any) true \| error` | Returns two numbers are near each other within epsilon 1e-5. | + +> `func eq(a any, b any) none` +> Returns whether two values are equal. Panics with `error.AssertError` if types or values do not match up. + +> `func eqList(a any, b any) none` +> Returns true if two lists have the same size and the elements are equal as if `eq` was called on those corresponding elements. + +> `func eqNear(a any, b any) none` +> Returns two numbers are near each other within epsilon 1e-5. + +> `func fail() none` + + \ No newline at end of file diff --git a/src/api.zig b/src/api.zig index 3aec7d34f..3d4daed25 100644 --- a/src/api.zig +++ b/src/api.zig @@ -330,6 +330,14 @@ pub const UserVM = struct { return @as(*const VM, @ptrCast(self)).valueToString(val); } + pub inline fn mapRawSet(self: *UserVM, map: cy.Value, key: cy.Value, value: cy.Value) !void { + try map.asHeapObject().map.map().put(self.allocator(), self.internal(), key, value); + } + + pub inline fn listAppend(self: *UserVM, list: cy.Value, value: cy.Value) !void { + try list.asHeapObject().list.getList().append(self.allocator(), value); + } + /// Used to return a panic from a native function body. pub fn returnPanic(self: *UserVM, msg: []const u8) Value { @setCold(true); diff --git a/src/builtins/bindings.zig b/src/builtins/bindings.zig index 8a0f9d41d..fd2db62a7 100644 --- a/src/builtins/bindings.zig +++ b/src/builtins/bindings.zig @@ -167,10 +167,6 @@ pub fn bindCore(self: *cy.VM) linksection(cy.InitSection) !void { const findAnyRune = try b.ensureMethodGroup("findAnyRune"); const findRune = try b.ensureMethodGroup("findRune"); const idSym = try b.ensureMethodGroup("id"); - const index = try b.ensureMethodGroup("index"); - const indexChar = try b.ensureMethodGroup("indexChar"); - const indexCharSet = try b.ensureMethodGroup("indexCharSet"); - const indexCode = try b.ensureMethodGroup("indexCode"); const insert = try b.ensureMethodGroup("insert"); const insertByte = try b.ensureMethodGroup("insertByte"); const isAscii = try b.ensureMethodGroup("isAscii"); @@ -193,7 +189,6 @@ pub fn bindCore(self: *cy.VM) linksection(cy.InitSection) !void { const trim = try b.ensureMethodGroup("trim"); const upper = try b.ensureMethodGroup("upper"); const utf8 = try b.ensureMethodGroup("utf8"); - const value = try b.ensureMethodGroup("value"); // Init compile time builtins. var rsym: sema.Symbol = undefined; @@ -272,18 +267,17 @@ pub fn bindCore(self: *cy.VM) linksection(cy.InitSection) !void { rt.StringSliceT => .slice, else => unreachable, }; + // ret: string | rawstring + try b.addMethod(typeId, self.@"infix+MGID", &.{ bt.Any, bt.Any }, bt.Any, stringConcat(tag)); try b.addMethod(typeId, append, &.{ bt.Any, bt.Any }, bt.String, stringAppend(tag)); try b.addMethod(typeId, charAt, &.{ bt.Any, bt.Integer }, bt.Any, stringCharAt(tag)); try b.addMethod(typeId, codeAt, &.{ bt.Any, bt.Integer }, bt.Any, stringCodeAt(tag)); - try b.addMethod(typeId, concat, &.{ bt.Any, bt.Any }, bt.String, stringConcat(tag)); + // ret: string | rawstring + try b.addMethod(typeId, concat, &.{ bt.Any, bt.Any }, bt.Any, stringConcat(tag)); try b.addMethod(typeId, endsWith, &.{ bt.Any, bt.Any }, bt.Boolean, stringEndsWith(tag)); try b.addMethod(typeId, find, &.{ bt.Any, bt.Any }, bt.Any, stringFind(tag)); try b.addMethod(typeId, findAnyRune, &.{ bt.Any, bt.Any }, bt.Any, stringFindAnyRune(tag)); try b.addMethod(typeId, findRune, &.{ bt.Any, bt.Integer }, bt.Any, stringFindRune(tag)); - try b.addMethod(typeId, index, &.{ bt.Any, bt.Any }, bt.Any, stringIndex(tag)); - try b.addMethod(typeId, indexChar, &.{ bt.Any, bt.Any }, bt.Any, stringIndexChar(tag)); - try b.addMethod(typeId, indexCharSet, &.{ bt.Any, bt.Any }, bt.Any, stringIndexCharSet(tag)); - try b.addMethod(typeId, indexCode, &.{ bt.Any, bt.Integer }, bt.Any, stringIndexCode(tag)); try b.addMethod(typeId, insert, &.{ bt.Any, bt.Integer, bt.Any }, bt.String, stringInsert(tag)); try b.addMethod(typeId, isAscii, &.{ bt.Any }, bt.Boolean, stringIsAscii(tag)); try b.addMethod(typeId, len, &.{ bt.Any }, bt.Integer, stringLen(tag)); @@ -320,6 +314,7 @@ pub fn bindCore(self: *cy.VM) linksection(cy.InitSection) !void { rt.RawstringSliceT => .rawSlice, else => unreachable, }; + try b.addMethod(typeId, self.@"infix+MGID", &.{ bt.Any, bt.Any }, bt.Rawstring, stringConcat(tag)); try b.addMethod(typeId, append, &.{ bt.Any, bt.Any }, bt.Rawstring, stringAppend(tag)); try b.addMethod(typeId, byteAt, &.{ bt.Any, bt.Integer }, bt.Integer, if (tag == .rawstring) rawStringByteAt else rawStringSliceByteAt); @@ -330,10 +325,6 @@ pub fn bindCore(self: *cy.VM) linksection(cy.InitSection) !void { try b.addMethod(typeId, find, &.{ bt.Any, bt.Any }, bt.Any, stringFind(tag)); try b.addMethod(typeId, findAnyRune, &.{ bt.Any, bt.Any }, bt.Any, stringFindAnyRune(tag)); try b.addMethod(typeId, findRune, &.{ bt.Any, bt.Integer }, bt.Any, stringFindRune(tag)); - try b.addMethod(typeId, index, &.{ bt.Any, bt.Any }, bt.Any, stringIndex(tag)); - try b.addMethod(typeId, indexChar, &.{ bt.Any, bt.Any }, bt.Any, stringIndexChar(tag)); - try b.addMethod(typeId, indexCharSet, &.{ bt.Any, bt.Any }, bt.Any, stringIndexCharSet(tag)); - try b.addMethod(typeId, indexCode, &.{ bt.Any, bt.Integer }, bt.Any, stringIndexCode(tag)); try b.addMethod(typeId, insert, &.{ bt.Any, bt.Integer, bt.Any }, bt.Any, stringInsert(tag)); try b.addMethod(typeId, insertByte, &.{ bt.Any, bt.Integer, bt.Integer }, bt.Any, if (tag == .rawstring) rawStringInsertByte else rawStringSliceInsertByte); @@ -373,10 +364,6 @@ pub fn bindCore(self: *cy.VM) linksection(cy.InitSection) !void { id = try self.addBuiltinType("pointer", bt.Pointer); std.debug.assert(id == rt.PointerT); - rsym = self.compiler.sema.getSymbol(bt.Pointer); - sb = ModuleBuilder.init(self.compiler, rsym.inner.builtinType.modId); - try sb.setFunc("$call", &.{ bt.Any }, bt.Pointer, pointerCall); - try b.addMethod(rt.PointerT, value, &.{ bt.Any }, bt.Integer, pointerValue); id = try self.addBuiltinType("MetaType", bt.MetaType); std.debug.assert(id == rt.MetaTypeT); @@ -729,7 +716,7 @@ pub fn errorValue(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { } } -fn pointerValue(_: *cy.UserVM, args: [*]const Value, _: u8) Value { +pub 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)))))); } @@ -1474,6 +1461,10 @@ fn stringConcat(comptime T: cy.StringType) cy.ZHostFuncFn { const obj = getStringObject(T, args[0]); const str = getStringSlice(T, vm, obj); if (isAstringObject(T, obj)) { + if (args[1].isRawString()) { + const rstr = vm.valueToTempRawString(args[1]); + return vm.allocRawStringConcat(str, rstr) catch fatal(); + } var rcharLen: u32 = undefined; const rstr = vm.valueToTempString2(args[1], &rcharLen); if (rcharLen == rstr.len) { @@ -1482,12 +1473,16 @@ fn stringConcat(comptime T: cy.StringType) cy.ZHostFuncFn { return vm.allocUstringConcat(str, rstr, @intCast(str.len + rcharLen)) catch fatal(); } } else if (isUstringObject(T, obj)) { + if (args[1].isRawString()) { + const rstr = vm.valueToTempRawString(args[1]); + return vm.allocRawStringConcat(str, rstr) catch fatal(); + } var rcharLen: u32 = undefined; 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[1]); + const rstr = vm.valueToTempRawString(args[1]); return vm.allocRawStringConcat(str, rstr) catch fatal(); } else fatal(); } @@ -1544,16 +1539,6 @@ fn stringInsert(comptime T: cy.StringType) cy.ZHostFuncFn { return S.inner; } -fn stringIndex(comptime T: cy.StringType) cy.ZHostFuncFn { - const S = struct { - 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, args, nargs}); - } - }; - return S.inner; -} - fn stringFind(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { @@ -1706,16 +1691,6 @@ fn stringIsAscii(comptime T: cy.StringType) cy.ZHostFuncFn { return S.inner; } -pub fn stringIndexCharSet(comptime T: cy.StringType) cy.ZHostFuncFn { - const S = struct { - 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, args, nargs}); - } - }; - return S.inner; -} - fn stringFindAnyRune(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { @@ -1807,16 +1782,6 @@ fn stringFindAnyRune(comptime T: cy.StringType) cy.ZHostFuncFn { return S.inner; } -pub fn stringIndexCode(comptime T: cy.StringType) cy.ZHostFuncFn { - const S = struct { - 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, args, nargs}); - } - }; - return S.inner; -} - fn stringFindRune(comptime T: cy.StringType) cy.ZHostFuncFn { const S = struct { fn inner(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { @@ -1867,21 +1832,6 @@ fn stringFindRune(comptime T: cy.StringType) cy.ZHostFuncFn { return S.inner; } -fn stringIndexChar(comptime T: cy.StringType) cy.ZHostFuncFn { - const S = struct { - 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[1]); - if (needle.len > 0) { - const cp = cy.string.utf8CodeAtNoCheck(needle, 0); - return @call(.never_inline, stringFindRune(T), .{vm, &[_]Value{Value.initF64(@floatFromInt(cp))}, 1}); - } - return Value.None; - } - }; - return S.inner; -} - pub fn metatypeId(_: *cy.UserVM, args: [*]const Value, _: u8) linksection(StdSection) Value { const obj = args[0].asHeapObject(); return Value.initInt(obj.metatype.symId); diff --git a/src/builtins/builtins.cy b/src/builtins/builtins.cy index 5734f9855..772faa64d 100644 --- a/src/builtins/builtins.cy +++ b/src/builtins/builtins.cy @@ -1,14 +1,68 @@ +--| 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. +@host func arrayFill(val any, n int) List + +--| Copies a primitive value or creates a shallow copy of an object value. +@host func copy(val any) any + +--| Prints the result of `toCyon` on a value. +@host func dump(val any) string + +@host func errorReport() string + +--| Returns whether a rune is an alphabetic letter. +@host func isAlpha(val int) boolean + +--| Returns whether a rune is a digit. +@host func isDigit(val int) boolean + +--| If `val` is an error, `panic(val)` is invoked. Otherwise, `val` is returned. +@host func must(val any) any + +--| Stop execution in the current fiber and starts unwinding the call stack. See [Unexpected Errors]({{}}). +@host func panic(err any) none + +--| 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. +@host func parseCyber(src any) Map + +--| Parses a CYON string into a value. +@host func parseCyon(src any) any + +--| Runs the garbage collector once to detect reference cycles and abandoned objects. +--| Returns the statistics of the run in a map value. +@host func performGC() Map + +--| Prints a value. The host determines how it is printed. +@host func print(str any) none + +--| Converts a rune to a string. +@host func runestr(val int) string + +--| Encodes a value to CYON string. +@host func toCyon(val any) string + +--| Returns the value's type as a `metatype` object. +@host func typeof(val any) metatype + +--| 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 +@host func typesym(val any) symbol + @host type boolean object: + --| Converts a value to either `true` or `false`. @host func '$call'(val any) boolean @host type 'error' object: + --| Create an error from an enum or symbol. @host func '$call'(val any) error @host func value(self) any @host type int object: + --| Converts a value to an 48-bit integer. @host func '$call'(val any) int @host func '$prefix~'(self) int @host func '$prefix-'(self) int @@ -30,6 +84,7 @@ type int object: @host type float object: + --| Converts the value to a `float`. Panics if type conversion fails. @host func '$call'(val any) float @host func '$prefix-'(self) float @host func '$infix<'(self, o any) boolean @@ -48,15 +103,37 @@ type List object: @host func '$index'(self, idx any) any @host func '$setIndex'(self, idx any, val any) none @host func add(self, val any) none + + --| Appends a value to the end of the list. @host func append(self, val any) none + + --| Concats the elements of another list to the end of this list. @host func concat(self, list List) none + + --| Inserts a value at index `idx`. @host func insert(self, idx int, val any) any + + --| Returns a new iterator over the list elements. @host func iterator(self) any + + --| Returns a new string that joins the elements with `separator`. @host func joinString(self, sep any) string + + --| Returns the number of elements in the list. @host func len(self) int + + --| Returns a new sequence iterator over the list elements. @host func seqIterator(self) any + + --| Removes an element at index `idx`. @host func remove(self, idx int) any + + --| Resizes the list to `len` elements. If the new size is bigger, `none` values + --| are appended to the list. If the new size is smaller, elements at the end of the list are removed. @host func resize(self, size int) any + + --| Sorts the list with the given `less` function. + --| If element `a` should be ordered before `b`, the function should return `true` otherwise `false`. @host func sort(self, lessFn any) any @host @@ -68,9 +145,17 @@ type ListIterator object: type Map object: @host func '$index'(self, key any) any @host func '$setIndex'(self, key any, val any) none + + --| Removes the element with the given key `key`. @host func remove(self, key any) none + + --| Returns the number of key-value pairs in the map. @host func size(self) int + + --| Returns a new iterator over the map elements. @host func iterator(self) any + + --| Returns a new sequence iterator over the map elements. @host func seqIterator(self) any @host @@ -87,10 +172,6 @@ type MapIterator object: -- func find(self, str any) any -- func findAnyRune(self, runes any) any -- func findRune(self, rune int) any --- func index(self, str any) any --- func indexChar(self, ch any) any --- func indexCharSet(self, chars any) any --- func indexCode(self, rune int) any -- func insert(self, idx int, str any) string -- func isAscii(self) boolean -- func len(self) int @@ -108,25 +189,11 @@ type MapIterator object: -- func trim(self, mode symbol, str any) any -- func upper(self) string -@host func arrayFill(val any, n int) List -@host func asciiCode(val any) any -@host func bool(val any) boolean -@host func char(val any) any -@host func copy(val any) any -@host func dump(val any) string -@host func errorReport() string -@host func isAlpha(val int) boolean -@host func isDigit(val int) boolean -@host func must(val any) any -@host func opaque(val any) pointer -@host func panic(err any) none -@host func parseCyber(src any) Map -@host func parseCyon(src any) any -@host func performGC() Map -@host func print(str any) none -@host func runestr(val int) string -@host func toCyon(val any) string -@host func typeid(val any) int -@host func valtag(val any) symbol -@host func typesym(val any) symbol -@host func typeof(val any) metatype \ No newline at end of file +@host +type pointer object: + --| Converts a `int` to a `pointer` value, or casts to a `pointer`. This is usually used with FFI. + @host func '$call'(val any) pointer + + --| Returns the memory address as an `int`. The value may be negative since it's + --| bitcasted from an unsigned 48-bit integer but it retains the original pointer bits. + @host func 'value'(self) int \ No newline at end of file diff --git a/src/builtins/builtins.zig b/src/builtins/builtins.zig index 64eb01058..909ea06bd 100644 --- a/src/builtins/builtins.zig +++ b/src/builtins/builtins.zig @@ -27,6 +27,24 @@ pub fn funcLoader(_: *cy.UserVM, func: cy.HostFuncInfo, out: *cy.HostFuncResult) const NameFunc = struct { []const u8, ?*const anyopaque, cy.HostFuncType }; const funcs = [_]NameFunc{ + // Utils. + .{"arrayFill", arrayFill, .standard}, + .{"copy", copy, .standard}, + .{"dump", dump, .standard}, + .{"errorReport", errorReport, .standard}, + .{"isAlpha", isAlpha, .standard}, + .{"isDigit", isDigit, .standard}, + .{"must", must, .standard}, + .{"panic", panic, .standard}, + .{"parseCyber", parseCyber, .standard}, + .{"parseCyon", parseCyon, .standard}, + .{"performGC", performGC, .standard}, + .{"print", print, .standard}, + .{"runestr", runestr, .standard}, + .{"toCyon", toCyon, .standard}, + .{"typeof", typeof, .standard}, + .{"typesym", typesym, .standard}, + // boolean .{"$call", bindings.booleanCall, .standard}, @@ -101,29 +119,9 @@ const funcs = [_]NameFunc{ .{"next", bindings.mapIteratorNext, .standard}, .{"nextSeq", bindings.mapIteratorNextSeq, .standard}, - // Utils. - .{"arrayFill", arrayFill, .standard}, - .{"asciiCode", asciiCode, .standard}, - .{"bool", btBool, .standard}, - .{"char", char, .standard}, - .{"copy", copy, .standard}, - .{"dump", dump, .standard}, - .{"errorReport", errorReport, .standard}, - .{"isAlpha", isAlpha, .standard}, - .{"isDigit", isDigit, .standard}, - .{"must", must, .standard}, - .{"opaque", btOpaque, .standard}, - .{"panic", panic, .standard}, - .{"parseCyber", parseCyber, .standard}, - .{"parseCyon", parseCyon, .standard}, - .{"performGC", performGC, .standard}, - .{"print", print, .standard}, - .{"runestr", runestr, .standard}, - .{"toCyon", toCyon, .standard}, - .{"typeid", typeid, .standard}, - .{"valtag", valtag, .standard}, - .{"typesym", typesym, .standard}, - .{"typeof", typeof, .standard}, + // pointer + .{"$call", bindings.pointerCall, .standard}, + .{"value", bindings.pointerValue, .standard}, }; const NameType = struct { []const u8, cy.rt.TypeId, cy.types.TypeId }; @@ -136,6 +134,7 @@ const types = [_]NameType{ .{"ListIterator", rt.ListIteratorT, bt.ListIter }, .{"Map", rt.MapT, bt.Map }, .{"MapIterator", rt.MapIteratorT, bt.MapIter }, + .{"pointer", rt.PointerT, bt.Pointer }, }; pub fn typeLoader(_: *cy.UserVM, info: cy.HostTypeInfo, out: *cy.HostTypeResult) callconv(.C) bool { @@ -154,26 +153,6 @@ pub fn arrayFill(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.Std return vm.allocListFill(args[0], @intCast(args[1].asInteger())) catch cy.fatal(); } -pub fn asciiCode(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - fmt.printDeprecated("asciiCode", "0.2", "Use UTF-8 rune notation or string.runeAt(0) instead.", &.{}); - const str = vm.valueToTempString(args[0]); - if (str.len > 0) { - return Value.initInt(str[0]); - } else { - return Value.None; - } -} - -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); -} - -pub fn char(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { - fmt.printDeprecated("char", "0.1", "Use asciiCode() instead.", &.{}); - return asciiCode(vm, args, nargs); -} - pub fn copy(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { const val = args[0]; return cy.value.shallowCopy(@ptrCast(@alignCast(vm)), val); @@ -215,11 +194,6 @@ pub fn must(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdS } } -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); -} - pub fn panic(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { const str = vm.valueToTempString(args[0]); return vm.returnPanic(str); @@ -387,74 +361,273 @@ pub fn parseCyber(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.St const alloc = vm.allocator(); var parser = cy.Parser.init(alloc); + parser.parseComments = true; defer parser.deinit(); - const res = parser.parse(src) catch |err| { + _ = parser.parse(src) catch |err| { log.debug("parseCyber: {}", .{err}); return prepareThrowSymbol(vm, .UnknownError); }; - return parseCyberGenResult(vm, &parser, res) catch |err| { + return parseCyberGenResult(vm, &parser) catch |err| { log.debug("parseCyber: {}", .{err}); return prepareThrowSymbol(vm, .UnknownError); }; } -fn parseCyberGenResult(vm: *cy.UserVM, parser: *const cy.Parser, res: cy.ParseResultView) !Value { +const ParseCyberState = struct { + sb: std.ArrayListUnmanaged(u8), + commentIdx: u32, + pos: u32, + node: cy.Node, +}; + +fn genTypeSpecString(vm: *cy.UserVM, parser: *const cy.Parser, headId: cy.NodeId) !cy.Value { + const alloc = vm.allocator(); + if (headId != cy.NullId) { + var sb: std.ArrayListUnmanaged(u8) = .{}; + defer sb.deinit(alloc); + + var name = cy.parser.getNodeTokenString(parser, headId); + try sb.appendSlice(alloc, name); + + var curId = parser.nodes.items[headId].next; + while (curId != cy.NullId) { + try sb.append(alloc, '.'); + name = cy.parser.getNodeTokenString(parser, curId); + try sb.appendSlice(alloc, name); + curId = parser.nodes.items[curId].next; + } + + return try vm.allocAstring(sb.items); + } else { + return try vm.allocAstring(""); + } +} + +fn genNodeValue(vm: *cy.UserVM, parser: *const cy.Parser, nodeId: cy.NodeId) !cy.Value { + const node = parser.nodes.items[nodeId]; + const res = try vm.allocEmptyMap(); + switch (node.node_t) { + .funcHeader => { + const name = cy.parser.getNodeTokenString(parser, node.head.funcHeader.name); + try vm.mapRawSet(res, try vm.allocAstring("name"), try vm.allocStringInfer(name)); + + const params = try vm.allocEmptyList(); + var paramId = node.head.funcHeader.paramHead; + while (paramId != cy.NullId) { + const param = try genNodeValue(vm, parser, paramId); + try vm.listAppend(params, param); + paramId = parser.nodes.items[paramId].next; + } + try vm.mapRawSet(res, try vm.allocAstring("params"), params); + }, + .funcParam => { + var name = cy.parser.getNodeTokenString(parser, node.head.funcParam.name); + try vm.mapRawSet(res, try vm.allocAstring("name"), try vm.allocStringInfer(name)); + + const typeSpec = try genTypeSpecString(vm, parser, node.head.funcParam.typeSpecHead); + try vm.mapRawSet(res, try vm.allocAstring("typeSpec"), typeSpec); + }, + else => {}, + } + return res; +} + +fn genDeclEntry(vm: *cy.UserVM, parser: *const cy.Parser, decl: cy.parser.StaticDecl, state: *ParseCyberState) !Value { + const alloc = vm.allocator(); + const nodes = parser.nodes.items; + const tokens = parser.tokens.items; + const entry = try vm.allocEmptyMap(); + try vm.mapRawSet(entry, try vm.allocAstring("type"), try vm.allocAstring(@tagName(decl.declT))); + var name: []const u8 = undefined; + var node: cy.Node = undefined; + switch (decl.declT) { + .variable => { + node = nodes[decl.inner.variable]; + const varSpec = nodes[node.head.staticDecl.varSpec]; + name = cy.parser.getNodeTokenString(parser, varSpec.head.varSpec.name); + + const typeSpec = try genTypeSpecString(vm, parser, varSpec.head.varSpec.typeSpecHead); + try vm.mapRawSet(entry, try vm.allocAstring("typeSpec"), typeSpec); + }, + .typeAlias => { + node = nodes[decl.inner.typeAlias]; + name = cy.parser.getNodeTokenString(parser, node.head.typeAliasDecl.name); + }, + .func => { + node = nodes[decl.inner.func]; + const header = nodes[node.head.func.header]; + name = cy.parser.getNodeTokenString(parser, header.head.funcHeader.name); + }, + .funcInit => { + node = nodes[decl.inner.funcInit]; + const header = nodes[node.head.func.header]; + name = cy.parser.getNodeTokenString(parser, header.head.funcHeader.name); + + const headerv = try genNodeValue(vm, parser, node.head.func.header); + try vm.mapRawSet(entry, try vm.allocAstring("header"), headerv); + }, + .import => { + node = nodes[decl.inner.import]; + name = cy.parser.getNodeTokenString(parser, node.head.left_right.left); + }, + .object => { + node = nodes[decl.inner.object]; + name = cy.parser.getNodeTokenString(parser, node.head.objectDecl.name); + + const childrenv = try vm.allocEmptyList(); + const children = childrenv.asHeapObject().list.getList(); + + const body = nodes[node.head.objectDecl.body]; + var funcId = body.head.objectDeclBody.funcsHead; + while (funcId != cy.NullId) { + const funcN = nodes[funcId]; + var childDecl: cy.parser.StaticDecl = undefined; + switch (funcN.node_t) { + .funcDecl => childDecl = .{ .declT = .func, .inner = .{ .func = funcId }}, + .funcDeclInit => childDecl = .{ .declT = .funcInit, .inner = .{ .funcInit = funcId }}, + else => return error.Unsupported, + } + + try children.append(alloc, try genDeclEntry(vm, parser, childDecl, state)); + funcId = funcN.next; + } + + try vm.mapRawSet(entry, try vm.allocAstring("children"), childrenv); + }, + .enumT => { + node = nodes[decl.inner.object]; + name = cy.parser.getNodeTokenString(parser, node.head.enumDecl.name); + } + } + const pos = tokens[node.start_token].pos(); + state.pos = pos; + state.node = node; + + // Find doc comments. + if (try genDocComment(vm, parser, decl, state)) |docStr| { + try vm.mapRawSet(entry, try vm.allocAstring("docs"), docStr); + } + + try vm.mapRawSet(entry, try vm.allocAstring("name"), try vm.allocAstring(name)); + try vm.mapRawSet(entry, try vm.allocAstring("pos"), Value.initF64(@floatFromInt(pos))); + return entry; +} + +fn genDocComment(vm: *cy.UserVM, parser: *const cy.Parser, decl: cy.parser.StaticDecl, state: *ParseCyberState) !?cy.Value { const alloc = vm.allocator(); const nodes = parser.nodes.items; + const tokens = parser.tokens.items; + if (state.commentIdx < parser.comments.items.len) { + var docStartIdx = state.commentIdx; + var docEndIdx = state.commentIdx; + while (state.commentIdx < parser.comments.items.len) { + var commentPos = parser.comments.items[state.commentIdx]; + if (commentPos.start > state.pos) { + break; + } + state.commentIdx += 1; + docEndIdx = state.commentIdx; + if (commentPos.len() < 3 or !std.mem.eql(u8, "--|", parser.src[commentPos.start..commentPos.start+3])) { + // Not a doc comment, reset. + docStartIdx = state.commentIdx; + continue; + } + // Check it is connected to last comment. + if (docEndIdx > docStartIdx + 1) { + const last = parser.comments.items[docEndIdx - 2]; + if (!linesConnected(parser.src, last.end, commentPos.start)) { + // Reset. + docStartIdx = state.commentIdx; + continue; + } + } + } + if (docEndIdx > docStartIdx) { + // Check it is connected to last comment. + const last = parser.comments.items[docEndIdx - 1]; + + var posWithModifiers = state.pos; + switch (decl.declT) { + .variable => { + const varSpec = nodes[state.node.head.staticDecl.varSpec]; + if (varSpec.head.varSpec.modifierHead != cy.NullId) { + const modifier = nodes[varSpec.head.varSpec.modifierHead]; + posWithModifiers = tokens[modifier.start_token].pos() - 1; + } + }, + .funcInit, + .func => { + const header = nodes[state.node.head.func.header]; + if (header.next != cy.NullId) { + const modifier = nodes[header.next]; + posWithModifiers = tokens[modifier.start_token].pos() - 1; + } + }, + .object => { + if (state.node.head.objectDecl.modifierHead != cy.NullId) { + const modifier = nodes[state.node.head.objectDecl.modifierHead]; + posWithModifiers = tokens[modifier.start_token].pos() - 1; + } + }, + else => {}, + } + + if (linesConnected(parser.src, last.end, posWithModifiers)) { + for (parser.comments.items[docStartIdx..docEndIdx]) |docPos| { + try state.sb.appendSlice(alloc, parser.src[docPos.start+3..docPos.end]); + try state.sb.append(alloc, ' '); + } + const finalStr = std.mem.trim(u8, state.sb.items, " "); + defer state.sb.clearRetainingCapacity(); + return try vm.allocStringInfer(finalStr); + } + } + } + return null; +} + +// Returns whether two lines are connected by a new line and indentation. +fn linesConnected(src: []const u8, aEnd: u32, bStart: u32) bool { + var i = aEnd; + if (src[i] == '\r') { + i += 1; + if (src[i] != '\n') { + return false; + } + i += 1; + } else if (src[i] == '\n') { + i += 1; + } else { + return false; + } + while (i < bStart) { + if (src[i] != ' ' and src[i] != '\t') { + return false; + } + i += 1; + } + return true; +} + +fn parseCyberGenResult(vm: *cy.UserVM, parser: *const cy.Parser) !Value { + const alloc = vm.allocator(); const root = try vm.allocEmptyMap(); const map = root.asHeapObject().map.map(); const decls = try vm.allocEmptyList(); const declsList = decls.asHeapObject().list.getList(); + + var state = ParseCyberState{ + .commentIdx = 0, + .sb = .{}, + .pos = undefined, + .node = undefined, + }; + defer state.sb.deinit(alloc); + for (parser.staticDecls.items) |decl| { - const entry = try vm.allocEmptyMap(); - const entryMap = entry.asHeapObject().map.map(); - try entryMap.put(alloc, vm.internal(), try vm.allocAstring("type"), try vm.allocAstring(@tagName(decl.declT))); - var name: []const u8 = undefined; - var pos: u32 = undefined; - switch (decl.declT) { - .variable => { - const node = nodes[decl.inner.variable]; - const varSpec = nodes[node.head.staticDecl.varSpec]; - name = res.getFirstNodeString(varSpec.head.varSpec.name); - pos = res.tokens[node.start_token].pos(); - }, - .typeAlias => { - const node = nodes[decl.inner.typeAlias]; - name = res.getFirstNodeString(node.head.typeAliasDecl.name); - pos = res.tokens[node.start_token].pos(); - }, - .func => { - const node = nodes[decl.inner.func]; - const header = nodes[node.head.func.header]; - name = res.getFirstNodeString(header.head.funcHeader.name); - pos = res.tokens[node.start_token].pos(); - }, - .funcInit => { - const node = nodes[decl.inner.funcInit]; - 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); - pos = res.tokens[node.start_token].pos(); - }, - .object => { - const node = nodes[decl.inner.object]; - name = res.getFirstNodeString(node.head.objectDecl.name); - pos = res.tokens[node.start_token].pos(); - }, - .enumT => { - const node = nodes[decl.inner.object]; - name = res.getFirstNodeString(node.head.enumDecl.name); - pos = res.tokens[node.start_token].pos(); - } - } - try entryMap.put(alloc, vm.internal(), try vm.allocAstring("name"), try vm.allocAstring(name)); - try entryMap.put(alloc, vm.internal(), try vm.allocAstring("pos"), Value.initF64(@floatFromInt(pos))); + const entry = try genDeclEntry(vm, parser, decl, &state); try declsList.append(alloc, entry); } try map.put(alloc, vm.internal(), try vm.allocAstring("decls"), decls); @@ -542,16 +715,6 @@ pub fn print(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSect return Value.None; } -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())); -} - -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); -} - pub fn typeof(vm: *cy.UserVM, args: [*]const Value, _: u8) Value { const val = args[0]; const typeId = val.getTypeId(); diff --git a/src/builtins/math.cy b/src/builtins/math.cy index f9da12733..4fdbc538b 100644 --- a/src/builtins/math.cy +++ b/src/builtins/math.cy @@ -1,69 +1,154 @@ --- Euler's number and the base of natural logarithms; approximately 2.718. +--| Euler's number and the base of natural logarithms; approximately 2.718. @host var e float --- Infinity. +--| Infinity. @host var inf float --- Base-10 logarithm of E; approximately 0.434. +--| Base-10 logarithm of E; approximately 0.434. @host var log10e float --- Base-2 logarithm of E; approximately 1.443. +--| Base-2 logarithm of E; approximately 1.443. @host var log2e float --- Natural logarithm of 10; approximately 2.303. +--| Natural logarithm of 10; approximately 2.303. @host var ln10 float --- Natural logarithm of 2; approximately 0.693. +--| Natural logarithm of 2; approximately 0.693. @host var ln2 float --- Not a number. +--| The maximum integer value that can be safely represented as a float. 2^53-1 or 9007199254740991. +@host var maxSafeInt float + +--| The minumum integer value that can be safely represented as a float. -(2^53-1) or -9007199254740991. +@host var minSafeInt float + +--| Not a number. Note that nan == nan. +--| However, if a nan came from an arithmetic operation, the comparison is undefined. +--| Use `isNaN` instead. @host var nan float --- Neg infinity. +--| Negative infinity. @host var neginf float --- Ratio of a circle's circumference to its diameter; approximately 3.14159. +--| Ratio of a circle's circumference to its diameter; approximately 3.14159. @host var pi float --- Square root of ½; approximately 0.707. +--| Square root of ½; approximately 0.707. @host var sqrt1_2 float --- Square root of 2; approximately 1.414. +--| Square root of 2; approximately 1.414. @host var sqrt2 float +--| Returns the absolute value of x. @host func abs(a float) float + +--| Returns the arccosine of x. @host func acos(a float) float + +--| Returns the hyperbolic arccosine of x. @host func acosh(a float) float + +--| Returns the arcsine of x. @host func asin(a float) float + +--| Returns the hyperbolic arcsine of a number. @host func asinh(a float) float + +--| Returns the arctangent of x. @host func atan(a float) float + +--| Returns the arctangent of the quotient of its arguments. @host func atan2(a float, b float) float + +--| Returns the hyperbolic arctangent of x. @host func atanh(a float) float + +--| Returns the cube root of x. @host func cbrt(a float) float + +--| Returns the smallest integer greater than or equal to x. @host func ceil(a float) float + +--| Returns the number of leading zero bits of the 32-bit integer x. @host func clz32(a float) float + +--| Returns the cosine of x. @host func cos(a float) float + +--| Returns the hyperbolic cosine of x. @host func cosh(a float) float + +--| Returns e^x, where x is the argument, and e is Euler's number (2.718…, the base of the natural logarithm). @host func exp(a float) float + +--| Returns subtracting 1 from exp(x). @host func expm1(a float) float + +--| Returns the largest integer less than or equal to x. @host func floor(a float) float + +--| Returns the fractional or decimal part of a float value. +@host func frac(a float) float + +--| Returns the square root of the sum of squares of its arguments. @host func hypot(a float, b float) float + +--| Returns true if the float has no fractional part, otherwise false. +@host func isInt(a float) boolean + +--| Returns whether x is not a number. @host func isNaN(a float) boolean + +--| Returns the natural logarithm (㏒e; also, ㏑) of x. @host func ln(a float) float + +--| Returns the logarithm of y with base x. @host func log(a float, b float) float + +--| Returns the base-10 logarithm of x. @host func log10(a float) float + +--| Returns the natural logarithm (㏒e; also ㏑) of 1 + x for the number x. @host func log1p(a float) float + +--| Returns the base-2 logarithm of x. @host func log2(a float) float + +--| Returns the largest of two numbers. @host func max(a float, b float) float + +--| Returns the smallest of two numbers. @host func min(a float, b float) float + +--| Returns the result of the 32-bit integer multiplication of x and y. Integer overflow is allowed. @host func mul32(a float, b float) float + +--| Returns base x to the exponent power y (that is, x^y). @host func pow(a float, b float) float + +--| Returns a pseudo-random number between 0 and 1. @host func random() float + +--| Returns the value of the number x rounded to the nearest integer. @host func round(a float) float + +--| Returns the sign of the x, indicating whether x is positive, negative, or zero. @host func sign(a float) float + +--| Returns the sine of x. @host func sin(a float) float + +--| Returns the hyperbolic sine of x. @host func sinh(a float) float + +--| Returns the positive square root of x. @host func sqrt(a float) float + +--| Returns the tangent of x. @host func tan(a float) float + +--| Returns the hyperbolic tangent of x. @host func tanh(a float) float + +--| Returns the integer portion of x, removing any fractional digits. @host func trunc(a float) float \ No newline at end of file diff --git a/src/builtins/math.zig b/src/builtins/math.zig index d75ce0e74..7608da760 100644 --- a/src/builtins/math.zig +++ b/src/builtins/math.zig @@ -28,6 +28,8 @@ const vars = [_]NameVar{ .{"log2e", Value.initF64(std.math.log2e)}, .{"ln10", Value.initF64(std.math.ln10)}, .{"ln2", Value.initF64(std.math.ln2)}, + .{"maxSafeInt", Value.initF64(9007199254740991)}, + .{"minSafeInt", Value.initF64(-9007199254740991)}, .{"nan", Value.initF64(std.math.nan_f64)}, .{"neginf", Value.initF64(-std.math.inf(f64))}, .{"pi", Value.initF64(std.math.pi)}, @@ -53,7 +55,9 @@ const funcs = [_]NameFunc{ .{"exp", exp}, .{"expm1", expm1}, .{"floor", floor}, + .{"frac", frac}, .{"hypot", hypot}, + .{"isInt", isInt}, .{"isNaN", isNaN}, .{"ln", ln}, .{"log", log}, @@ -155,11 +159,25 @@ pub fn floor(_: *cy.UserVM, args: [*]const Value, _: u8) Value { return Value.initF64(std.math.floor(args[0].asF64())); } +pub fn frac(_: *cy.UserVM, args: [*]const Value, _: u8) Value { + const f = args[0].asF64(); + const res = std.math.modf(f); + return Value.initF64(res.fpart); +} + /// Returns the square root of the sum of squares of its arguments. pub fn hypot(_: *cy.UserVM, args: [*]const Value, _: u8) Value { return Value.initF64(std.math.hypot(f64, args[0].asF64(), args[1].asF64())); } +pub fn isInt(_: *cy.UserVM, args: [*]const Value, _: u8) Value { + const f = args[0].asF64(); + if (std.math.isNan(f) or std.math.isInf(f)) { + return Value.False; + } + return Value.initBool(std.math.trunc(f) == f); +} + /// Returns the absolute value of x. pub fn isNaN(_: *cy.UserVM, args: [*]const Value, _: u8) Value { return Value.initBool(std.math.isNan(args[0].asF64())); diff --git a/src/cbindgen.cy b/src/cbindgen.cy index ea92fedcd..7f6655951 100755 --- a/src/cbindgen.cy +++ b/src/cbindgen.cy @@ -1,5 +1,5 @@ #!cyber -import os 'os' +import os POST_HEADER = " " diff --git a/src/codegen.zig b/src/codegen.zig index f78da78e3..93b618f90 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -242,7 +242,8 @@ fn stringTemplate(self: *Chunk, nodeId: cy.NodeId, req: RegisterCstr) !GenValue try pushReleases(self, retainedTemps, nodeId); - return self.initGenValue(dst, bt.String, true); + // string or rawstring + return self.initGenValue(dst, bt.Any, true); } fn objectInit(self: *Chunk, nodeId: cy.NodeId, req: RegisterCstr) !GenValue { diff --git a/src/cyber.zig b/src/cyber.zig index 631d5929b..6e9788d5a 100644 --- a/src/cyber.zig +++ b/src/cyber.zig @@ -2,7 +2,7 @@ const builtin = @import("builtin"); -const parser = @import("parser.zig"); +pub const parser = @import("parser.zig"); pub const Parser = parser.Parser; pub const ParseResultView = parser.ResultView; pub const ParseResult = parser.Result; diff --git a/src/heap.zig b/src/heap.zig index a2a7b841b..90a72408a 100644 --- a/src/heap.zig +++ b/src/heap.zig @@ -948,13 +948,23 @@ pub fn allocStringTemplate(self: *cy.VM, strs: []const cy.Inst, vals: []const Va std.mem.copy(u8, self.u8Buf.items(), firstStr); const writer = self.u8Buf.writer(self.alloc); + var isSafeStr = true; for (vals, 0..) |val, i| { - self.writeValueToString(writer, val); + var isRaw: bool = undefined; + const bytes = self.valueToTempRawString2(val, &isRaw); + if (isRaw) { + isSafeStr = false; + } + _ = try writer.write(bytes); try self.u8Buf.appendSlice(self.alloc, self.valueAsStaticString(Value.initRaw(self.consts[strs[i+1].val].val))); } // TODO: As string is built, accumulate charLen and detect rawstring to avoid doing validation. - return getOrAllocStringInfer(self, self.u8Buf.items()); + if (isSafeStr) { + return getOrAllocStringInfer(self, self.u8Buf.items()); + } else { + return allocRawString(self, self.u8Buf.items()); + } } pub fn getOrAllocOwnedAstring(self: *cy.VM, obj: *HeapObject) linksection(cy.HotSection) !Value { diff --git a/src/parser.zig b/src/parser.zig index b10f0b54c..f50164da3 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -95,6 +95,10 @@ pub const Parser = struct { inObjectDecl: bool, + /// Whether to parse and accumulate comment tokens in `comments`. + parseComments: bool, + comments: std.ArrayListUnmanaged(cy.IndexSlice(u32)), + /// For custom functions. user: struct { ctx: *anyopaque, @@ -125,6 +129,8 @@ pub const Parser = struct { .tokenizeOpts = .{}, .staticDecls = .{}, .inObjectDecl = false, + .parseComments = false, + .comments = .{}, }; } @@ -138,6 +144,7 @@ pub const Parser = struct { self.block_stack.deinit(self.alloc); self.deps.deinit(self.alloc); self.staticDecls.deinit(self.alloc); + self.comments.deinit(self.alloc); } fn dumpTokensToCurrent(self: *Parser) void { @@ -3753,6 +3760,12 @@ pub const Result = struct { } }; +pub fn getNodeTokenString(p: *const Parser, nodeId: NodeId) []const u8 { + const node = p.nodes.items[nodeId]; + const token = p.tokens.items[node.start_token]; + return p.src[token.pos()..token.data.end_pos]; +} + /// Result data is not owned. pub const ResultView = struct { root_id: NodeId, @@ -4110,11 +4123,17 @@ pub fn Tokenizer(comptime Config: TokenizerConfig) type { // Single line comment. Ignore chars until eol. while (!isAtEndChar(p)) { if (peekChar(p) == '\n') { + if (p.parseComments) { + try p.comments.append(p.alloc, cy.IndexSlice(u32).init(start, p.next_pos)); + } // Don't consume new line or the current indentation could augment with the next line. return tokenizeOne(p, state); } advanceChar(p); } + if (p.parseComments) { + try p.comments.append(p.alloc, cy.IndexSlice(u32).init(start, p.next_pos)); + } return .{ .stateT = .end }; } else { try p.pushOpToken(.minus, start); @@ -4754,7 +4773,7 @@ const StaticDeclType = enum { enumT, }; -const StaticDecl = struct { +pub const StaticDecl = struct { declT: StaticDeclType, inner: extern union { variable: NodeId, diff --git a/src/sema.zig b/src/sema.zig index 19167ba87..9121e6921 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -1586,7 +1586,8 @@ fn semaExprInner(c: *cy.Chunk, nodeId: cy.NodeId, preferType: TypeId) anyerror!T curId = cur.next; expStringPart = !expStringPart; } - return bt.String; + // string | rawstring + return bt.Any; }, .ident => { return identifier(c, nodeId); diff --git a/src/std/os.cy b/src/std/os.cy index 0a49bad01..704783039 100644 --- a/src/std/os.cy +++ b/src/std/os.cy @@ -1,69 +1,191 @@ +--| The current cpu arch's tag name. @host var cpu string + +--| The current arch's endianness: .little, .big @host var endian symbol -@host var stdin any -@host var stdout any + +--| Standard error file descriptor. @host var stderr any -@host var system string -@host var vecBitSize int -@host -type File object: - @host func close(self) none - @host func iterator(self) any - @host func next(self) any - @host func read(self, n int) any - @host func readToEnd(self) any - @host func seek(self, n int) any - @host func seekFromCur(self, n int) any - @host func seekFromEnd(self, n int) any - @host func stat(self) any - @host func streamLines(self) any - @host func streamLines(self, bufSize int) any - @host func write(self, val any) any +--| Standard input file descriptor. +@host var stdin any -@host -type Dir object: - @host func iterator(self) any - @host func stat(self) any - @host func walk(self) any +--| Standard output file descriptor. +@host var stdout any -@host -type DirIterator object: - @host func next(self) any +--| The current operating system's tag name. +@host var system string + +--| Default SIMD vector bit size. +@host var vecBitSize int +--| Attempts to access a file at the given `path` with the `.read`, `.write`, or `.readWrite` mode. +--| Return true or an error. @host func access(path any, mode symbol) any + +--| 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. @host func args() List + +--| Calls `bindLib(path, decls, {})`. @host func bindLib(path any, decls List) any + +--| 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. @host func bindLib(path any, decls List, config Map) any + +--| Returns the path of a locally cached file of `url`. +--| If no such file exists locally, it's fetched from `url`. @host func cacheUrl(url any) any + +--| Copies a file to a destination path. @host func copyFile(srcPath any, dstPath any) any + +--| Creates the directory at `path`. Returns `true` if successful. @host func createDir(path any) any + +--| Creates and opens the file at `path`. If `truncate` is true, an existing file will be truncated. @host func createFile(path any, truncate boolean) any + +--| Returns a null terminated C string. @host func cstr(s any) pointer + +--| Returns the current working directory. @host func cwd() string + +--| Returns the given path with its last component removed. @host func dirName(path any) any + +--| Runs a shell command and returns the stdout/stderr. @host func execCmd(args List) any + +--| Returns the current executable's path. @host func exePath() string + +--| Exits the program with a status code. @host func exit(status int) none + +--| Fetches the contents at `url` using the HTTP GET request method. @host func fetchUrl(url any) any + +--| Frees the memory located at `ptr`. @host func free(ptr pointer) none + +--| Returns a `rawstring` from a null terminated C string. @host func fromCstr(ptr pointer) rawstring + +--| Returns an environment value by key. @host func getEnv(key any) any + +--| Returns all environment entries as a `Map`. @host func getEnvAll() Map + +--| 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`. @host func getInput() any + +--| Allocates `size` bytes of memory and returns a pointer. @host func malloc(size int) pointer + +--| Return the calendar timestamp, in milliseconds, relative to UTC 1970-01-01. @host func milliTime() float + +--| Invokes `openDir(path, false)`. @host func openDir(path any) any + +--| Opens a directory at the given `path`. `iterable` indicates that the directory's entries can be iterated. @host func openDir(path any, iterable boolean) any + +--| Opens a file at the given `path` with the `.read`, `.write`, or `.readWrite` mode. @host func openFile(path any, mode symbol) any + +--| Given expected `ArgOption`s, returns a map of the options and a `rest` entry which contains the non-option arguments. | @host func parseArgs(options List) Map + +--| Reads stdin to the EOF as a `rawstring`. @host func readAll() any + +--| Reads the file contents into a `rawstring` value. @host func readFile(path any) any + @host func readLine() any + +--| Returns the absolute path of the given path. @host func realPath(path any) any + +--| Removes an empty directory at `path`. Returns `true` if successful. @host func removeDir(path any) any + +--| Removes the file at `path`. Returns `true` if successful. @host func removeFile(path any) any + +--| Sets an environment value by key. @host func setEnv(key any, val any) none + +--| Pauses the current thread for given milliseconds. @host func sleep(ms float) none + +--| Removes an environment value by key. @host func unsetEnv(key any) none -@host func writeFile(path any, contents any) any \ No newline at end of file + +--| Writes a string value to a file. +@host func writeFile(path any, contents any) any + +@host +type File object: + --| Closes the file handle. File ops invoked afterwards will return `error.Closed`. + @host func close(self) none + @host func iterator(self) any + @host func next(self) any + + --| Reads at most `n` bytes as a `rawstring`. `n` must be at least 1. + --| A result with length 0 indicates the end of file was reached. + @host func read(self, n int) any + + --| Reads to the end of the file and returns the content as a `rawstring`. + @host func readToEnd(self) any + + --| Seeks the read/write position to `pos` bytes from the start. Negative `pos` is invalid. + @host func seek(self, n int) any + + --| Seeks the read/write position by `pos` bytes from the current position. + @host func seekFromCur(self, n int) any + + --| Seeks the read/write position by `pos` bytes from the end. Positive `pos` is invalid. + @host func seekFromEnd(self, n int) any + + --| Returns info about the file as a `Map`. + @host func stat(self) any + + --| Equivalent to `streamLines(4096)`. + @host func streamLines(self) any + + --| Returns an iterable that streams lines ending in `\n`, `\r`, `\r\n`, or the `EOF`. + --| The lines returned include the new line character(s). + --| A buffer size of `bufSize` bytes is allocated for reading. + --| If `\r` is found at the end of the read buffer, the line is returned instead of + --| waiting to see if the next read has a connecting `\n`. + @host func streamLines(self, bufSize int) any + + --| Writes a `string` or `rawstring` at the current file position. + --| The number of bytes written is returned. + @host func write(self, val any) any + +@host +type Dir object: + --| Returns a new iterator over the directory entries. + --| If this directory was not opened with the iterable flag, `error.NotAllowed` is returned instead. + @host func iterator(self) any + + --| Returns info about the file as a `Map`. + @host func stat(self) any + + --| Returns a new iterator over the directory recursive entries. + --| If this directory was not opened with the iterable flag, `error.NotAllowed` is returned instead. + @host func walk(self) any + +@host +type DirIterator object: + @host func next(self) any \ No newline at end of file diff --git a/src/std/os.zig b/src/std/os.zig index 3a0a98247..98568ccbb 100644 --- a/src/std/os.zig +++ b/src/std/os.zig @@ -35,28 +35,6 @@ pub fn funcLoader(_: *cy.UserVM, func: cy.HostFuncInfo, out: *cy.HostFuncResult) const NameFunc = struct { []const u8, cy.ZHostFuncFn }; const funcs = [_]NameFunc{ - // File - .{"close", fs.fileClose}, - .{"iterator", fs.fileIterator}, - .{"next", fs.fileNext}, - .{"read", fs.fileRead}, - .{"readToEnd", fs.fileReadToEnd}, - .{"seek", fs.fileSeek}, - .{"seekFromCur", fs.fileSeekFromCur}, - .{"seekFromEnd", fs.fileSeekFromEnd}, - .{"stat", fs.fileOrDirStat}, - .{"streamLines", fs.fileStreamLines}, - .{"streamLines", fs.fileStreamLines1}, - .{"write", fs.fileWrite}, - - // Dir - .{"iterator", fs.dirIterator}, - .{"stat", fs.fileOrDirStat}, - .{"walk", fs.dirWalk}, - - // DirIterator - .{"next", fs.dirIteratorNext}, - // Top level .{"access", access}, .{"args", osArgs}, @@ -94,6 +72,28 @@ const funcs = [_]NameFunc{ .{"sleep", sleep}, .{"unsetEnv", unsetEnv}, .{"writeFile", writeFile}, + + // File + .{"close", fs.fileClose}, + .{"iterator", fs.fileIterator}, + .{"next", fs.fileNext}, + .{"read", fs.fileRead}, + .{"readToEnd", fs.fileReadToEnd}, + .{"seek", fs.fileSeek}, + .{"seekFromCur", fs.fileSeekFromCur}, + .{"seekFromEnd", fs.fileSeekFromEnd}, + .{"stat", fs.fileOrDirStat}, + .{"streamLines", fs.fileStreamLines}, + .{"streamLines", fs.fileStreamLines1}, + .{"write", fs.fileWrite}, + + // Dir + .{"iterator", fs.dirIterator}, + .{"stat", fs.fileOrDirStat}, + .{"walk", fs.dirWalk}, + + // DirIterator + .{"next", fs.dirIteratorNext}, }; const NameValue = struct { []const u8, cy.Value }; @@ -142,19 +142,19 @@ pub fn zPostTypeLoad(c: *cy.VMcompiler, modId: cy.ModuleId) !void { vars[1] = .{ "endian", cy.Value.initSymbol(@intFromEnum(Symbol.big)) }; } if (cy.hasStdFiles) { + const stderr = try fs.allocFile(c.vm, std.io.getStdErr().handle); + stderr.asHostObject(*fs.File).closeOnFree = false; + vars[2] = .{ "stderr", stderr }; const stdin = try fs.allocFile(c.vm, std.io.getStdIn().handle); stdin.asHostObject(*fs.File).closeOnFree = false; - vars[2] = .{ "stdin", stdin }; + vars[3] = .{ "stdin", stdin }; const stdout = try fs.allocFile(c.vm, std.io.getStdOut().handle); stdout.asHostObject(*fs.File).closeOnFree = false; - vars[3] = .{ "stdout", stdout }; - const stderr = try fs.allocFile(c.vm, std.io.getStdErr().handle); - stderr.asHostObject(*fs.File).closeOnFree = false; - vars[4] = .{ "stderr", stderr }; + vars[4] = .{ "stdout", stdout }; } else { - vars[2] = .{ "stdin", Value.None }; - vars[3] = .{ "stdout", Value.None }; - vars[4] = .{ "stderr", Value.None }; + vars[2] = .{ "stderr", Value.None }; + vars[3] = .{ "stdin", Value.None }; + vars[4] = .{ "stdout", Value.None }; } vars[5] = .{ "system", try c.buf.getOrPushStringValue(@tagName(builtin.os.tag)) }; diff --git a/src/std/test.cy b/src/std/test.cy index 37099c35a..816288bc3 100644 --- a/src/std/test.cy +++ b/src/std/test.cy @@ -1,4 +1,12 @@ +--| Returns whether two values are equal. +--| Panics with `error.AssertError` if types or values do not match up. @host func eq(a any, b any) any + +--| Returns true if two lists have the same size and the elements are equal +--| as if `eq` was called on those corresponding elements. @host func eqList(a any, b any) any + +--| Returns two numbers are near each other within epsilon 1e-5. @host func eqNear(a any, b any) any + @host func fail() any \ No newline at end of file diff --git a/src/vm.zig b/src/vm.zig index 148dd4f3c..72f0f52f4 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -1542,6 +1542,16 @@ pub const VM = struct { } } + pub fn valueToTempRawString2(self: *const VM, val: Value, isRaw: *bool) linksection(cy.StdSection) []const u8 { + if (val.isRawString()) { + isRaw.* = true; + return val.asRawString(); + } else { + isRaw.* = false; + return self.valueToTempString(val); + } + } + /// String is guaranteed to be valid UTF-8. /// Uses a short desc for rawstring instead of performing validation. pub fn valueToTempString(self: *const VM, val: Value) linksection(cy.Section) []const u8 { diff --git a/src/web.zig b/src/web.zig index ba6ee1747..7be7dcd6f 100644 --- a/src/web.zig +++ b/src/web.zig @@ -19,6 +19,7 @@ pub fn loader(uvm: *cy.UserVM, spec_: cy.Str, out: *cy.ModuleLoaderResult) callc if (std.mem.eql(u8, "web", spec)) { out.* = .{ .src = cy.Str.initSlice( + \\--| Evals JS from the host environment. \\@host func eval(val any) none ), .srcIsStatic = true, diff --git a/test/behavior_test.zig b/test/behavior_test.zig index 467a54f32..4c43837d0 100644 --- a/test/behavior_test.zig +++ b/test/behavior_test.zig @@ -993,19 +993,19 @@ test "os module" { defer run.destroy(); var val = try run.eval( - \\import os 'os' + \\import os \\os.system ); try t.eqStr(try run.assertValueString(val), @tagName(builtin.os.tag)); val = try run.eval( - \\import os 'os' + \\import os \\os.cpu ); try t.eqStr(try run.assertValueString(val), @tagName(builtin.cpu.arch)); val = try run.eval( - \\import os 'os' + \\import os \\os.endian ); if (builtin.cpu.arch.endian() == .Little) { @@ -2635,16 +2635,18 @@ test "Bitwise operators." { } test "Arithmetic operators." { - // Can only add numbers. + // Can't add objects by default. try eval(.{ .silent = true }, - \\var a = 'foo' + 123 + \\type S object: + \\ a any + \\var a = S{ a: 123 } + 123 , struct { fn func(run: *VMrunner, res: EvalResult) !void { try run.expectErrorReport(res, error.Panic, - \\panic: `func $infix+(any, int) any` can not be found in `StaticAstring`. + \\panic: `func $infix+(any, int) any` can not be found in `S`. \\ - \\main:1:15 main: - \\var a = 'foo' + 123 - \\ ^ + \\main:3:21 main: + \\var a = S{ a: 123 } + 123 + \\ ^ \\ ); }}.func); diff --git a/test/core_test.cy b/test/core_test.cy index 082ee9d2f..729a9e643 100644 --- a/test/core_test.cy +++ b/test/core_test.cy @@ -1,12 +1,11 @@ import t 'test' -import os --| Tests sensitive to line numbers. -- errorReport(), current frame. try: throw error.Boom catch: - t.eq(errorReport(), "main:7:3 main: + t.eq(errorReport(), "main:6:3 main: throw error.Boom ^ ") @@ -18,13 +17,13 @@ func foo(): try: foo2() catch: - t.eq(errorReport(), "main:16:3 foo2: + t.eq(errorReport(), "main:15:3 foo2: throw error.Boom ^ -main:19:5 foo: +main:18:5 foo: foo2() ^ -main:31:1 main: +main:30:1 main: foo() ^ ") diff --git a/test/math_test.cy b/test/math_test.cy index 17c2cc4e5..9b9ef091e 100644 --- a/test/math_test.cy +++ b/test/math_test.cy @@ -152,4 +152,21 @@ t.eqNear(m.asinh(2), 1.44363547) t.eq(m.atanh(-1), m.neginf) t.eq(m.atanh(0), 0.0) t.eqNear(m.atanh(0.5), 0.54930614) -t.eq(m.atanh(1), m.inf) \ No newline at end of file +t.eq(m.atanh(1), m.inf) + +-- isInt +t.eq(m.isInt(1.0), true) +t.eq(m.isInt(40000000.0), true) +t.eq(m.isInt(40000000.1), false) +t.eq(m.isInt(40000000.01), false) +t.eq(m.isInt(40000000.001), false) +t.eq(m.isInt(m.nan), false) +t.eq(m.isInt(m.inf), false) + +-- frac +t.eqNear(m.frac(1.0), 0.0) +t.eqNear(m.frac(40000000.0), 0.0) +t.eqNear(m.frac(40000000.6), 0.6) +t.eqNear(m.frac(40000000.1), 0.1) +t.eqNear(m.frac(40000000.01), 0.01) +t.eqNear(m.frac(40000000.001), 0.001)