diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..ae03cfa5 --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +IndentWidth: 4 +AccessModifierOffset: -4 \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 02fb71d1..9bdf2d00 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,7 +48,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [macOS-latest, macOS-11] + os: [macOS-latest, macOS-13, macOS-12] steps: - uses: actions/checkout@v3 diff --git a/.gitignore b/.gitignore index 95ff94de..67cd9090 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,6 @@ test1.du .vscode /.env /vcpkg_installed -/Release \ No newline at end of file +/Release +examples/ffi-example/build +build_x64/ \ No newline at end of file diff --git a/README.md b/README.md index e16f7187..74e82307 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,7 @@ *What is Dictu?* Dictu is a high-level dynamically typed, multi-paradigm, interpreted programming language. Dictu has a very familiar -C-style syntax along with taking inspiration from the family of languages surrounding it, such as Python and JavaScript. - -*What does Dictu mean?* - -Dictu means `simplistic` in Latin. +C-style syntax along with taking inspiration from the family of languages surrounding it, such as Python and JavaScript. ### Dictu documentation Documentation for Dictu can be found [here](https://dictu-lang.com/) diff --git a/docs/_config.yml b/docs/_config.yml index 0b3a8c27..702915e1 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -5,7 +5,7 @@ description: >- color_scheme: "dictu" # Custom theme logo: "/assets/images/dictu-logo/dictu-wordmark.svg" -version: "0.29.0" +version: "0.30.0" github_username: dictu-lang search_enabled: true diff --git a/docs/docs/collections/dictionaries.md b/docs/docs/collections/dictionaries.md index 6a3d0a89..c7efd68a 100644 --- a/docs/docs/collections/dictionaries.md +++ b/docs/docs/collections/dictionaries.md @@ -38,14 +38,14 @@ Updating a value within a dictionary uses the same syntax, except you supply a v ```cs var myDict = {"key": 1, "key1": true}; -var myDict["key"] = false; // {"key": false, "key1": true} +myDict["key"] = false; // {"key": false, "key1": true} ``` Adding a value to a dictionary is the same as updating a value, however if the key does not exist it is created. ```cs var myDict = {"key": 1, "key1": true}; -var myDict["key2"] = nil; // {"key": false, "key1": true, "key3": nil} +myDict["key2"] = nil; // {"key": 1, "key1": true, "key2": nil} ``` ### dict.get(String, value: default -> Optional) -> Dict @@ -146,7 +146,7 @@ myDict.forEach(def (key, value) => { }); ``` -### dict.merge(Dict) +### dict.merge(Dict) -> Dict To merge two dicts together we use `.merge`. This operation will take a shallow copy of the dict the `.merge` method was called on and add any items from the dictionary passed into the method. If there are keys that exist in both dictionaries @@ -159,4 +159,20 @@ const dictTwo = {"key3": 4,"key1":0}; const mergedDict = dictOne.merge(dictTwo); mergedDict; //{"key2": 3, "key": 1, "key3": 4, "key1": 0} -``` \ No newline at end of file +``` + +### dict.toObj(Dict, Object) -> Object + +Returns an object created from the given dict and class ref. Dictionary fields that aren't strings are converted to strings and set on the object. To retrieve those fields, use the `.getAttribute(String)` method. + +```cs +class A {} + +const dict = {"key": 1, "key1": 2, "key2": 3}; +const obj = dict.toObj(A()); + +obj; // +obj.key // 1 +obj.key1 // 2 +obj.key2 // 3 +``` diff --git a/docs/docs/collections/lists.md b/docs/docs/collections/lists.md index ad58449c..7ae4f18d 100644 --- a/docs/docs/collections/lists.md +++ b/docs/docs/collections/lists.md @@ -234,6 +234,21 @@ list1.sort(); print(list1); // [-1, 1, 2, 3, 4, 5, 10] ``` +#### list.sortFunc(Func) + +Sorts a list with a Custom Callback, The callback function passed to `.sortFunc` takes two elements of the list `a` and `b`. +The return value should be a number whose sign indicates the relative order of the two elements: negative if `a` is less than `b`, positive if `a` is greater than `b`, and zero if they are equal. + +```cs +var list1 = [[1, "Dictu"], [-1, "SomeValue"], [5, "!"], [4, "Awesome"], [2, "is"]]; + +print(list1); // [[1, "Dictu"], [-1, "SomeValue"], [5, "!"], [4, "Awesome"], [2, "is"]]; +list1.sortFunc(def(a,b) => a[0] - b[0]); + +print(list1); // [[-1, "SomeValue"], [1, "Dictu"], [2, "is"], [4, "Awesome"], [5, "!"]] + +``` + ```cs var list1 = ["zebra", "cat", "dino", "pig"]; diff --git a/docs/docs/collections/sets.md b/docs/docs/collections/sets.md index 46d3967d..ee593b54 100644 --- a/docs/docs/collections/sets.md +++ b/docs/docs/collections/sets.md @@ -73,6 +73,15 @@ var mySet = set(); mySet.add("Dictu!"); ``` +### set.values() -> List + +Returns a list of all of the values in the set. + +```cs +var mySet = set("foo", "bar", 123); +const values = mySet.values(); // ["foo", "bar", 123] +``` + ### set.contains(value) -> Boolean To check if a set contains a value use `.contains()` diff --git a/docs/docs/standard-lib/buffer.md b/docs/docs/standard-lib/buffer.md new file mode 100644 index 00000000..4148eb7d --- /dev/null +++ b/docs/docs/standard-lib/buffer.md @@ -0,0 +1,480 @@ +--- +layout: default +title: Buffer +nav_order: 4 +parent: Standard Library +--- + +# Buffer +{: .no_toc } + +## Table of contents +{: .no_toc .text-delta } + +1. TOC +{:toc} + +--- + +## Buffer +To make use of the Buffer module an import is required. + +```js +import Buffer; +``` + +The maximum byte size for a buffer is `2147483647 - 1` + +### Buffer.new() -> Result\ + +Returns a Result with a new buffer with the given size in bytes. + +```cs +const buffer = Buffer.new(1024).unwrap(); +print(buffer); +// +``` + +### Buffer.fromString(String) -> Result\ + +Returns a Result with a new buffer created from the given string. + +```cs +const buffer = Buffer.fromString("Dictu!").unwrap(); +print(buffer); +// +``` + +### Buffer.resize(Number) -> Result\ + +Resizes the buffer to the given size. The argument needs to be greater than 0 or the function will return an error. + +```cs +const buffer = Buffer.new(8).unwrap(); +print(buffer.resize(16).unwrap()); +// 16 +``` + +### Buffer.get(Number) -> Result\ + +Returns the value of byte at the given index. +```cs +const buffer = Buffer.new("Dictu!").unwrap(); +print(buffer.get(0).unwrap()); + +// 68 (ASCII value of 'D') +``` + +### Buffer.set(Number, Number) -> Result\ + +Sets the given index of the buffer to the second argument. +Returns a Number result with the new value. +```cs +const buffer = Buffer.fromString("dictu!").unwrap(); +buffer.set(0, 68).unwrap(); +print(buffer.string()); // "Dictu!" +``` + +### Buffer.subarray(Number: start -> Optional, Number: end -> Optional) -> Result\ + +Returns a new Buffer with the optional given start and end parameters. +* `start`: The start index within the buffer, default `0` +* `end`: The end index within the buffer(non inclusive), default: `buffer.len()` +```cs +const buffer = Buffer.fromString("Dictu!").unwrap(); +const sub = buffer.subarray(0, buffer.len()-1).unwrap(); +print(sub.string()); // "Dictu" +``` + +### Buffer.string() -> String + +Returns a string representation of the buffer. +```cs +const buffer = Buffer.fromString("Dictu!").unwrap(); +const str = buffer.string(); +print(str); // "Dictu!" +``` + +### Buffer.len() -> Number + +Returns the byte length of the buffer. +```cs +const buffer = Buffer.new(9).unwrap(); +const len = buffer.len(); +print(len); // 9 +``` + +### Buffer.values() -> List + +Returns a list with the integer values of the buffer. +```cs +const buffer = Buffer.fromString("Dictu!").unwrap(); +const v = buffer.values(); +print(v); // [68, 105, 99, 116, 117, 33] +``` + + +### Buffer.writeString(Number, String) -> Result\ + +Sets a string into buffer given the starting index. If the string doesn't fit in the buffer an error is returned. +```cs +const buffer = Buffer.new(6).unwrap(); +buffer.writeString(0, "Dictu!"); +``` + +### Buffer.readString(Number: start -> Optional, Number: end -> Optional) -> Result\ + +Returns a String with the optional given start and end parameters, this works very similar to subarray. +* `start`: The start index within the buffer, default `0` +* `end`: The end index within the buffer(non inclusive), default: `buffer.len()` +```cs +const buffer = Buffer.new(6).unwrap(); +buffer.writeString(0, "Dictu!"); +const sub = buffer.string(0, buffer.len()-1).unwrap(); +print(sub) // "Dictu" +``` + +### Buffer.readUInt64LE(Number) -> Result\ + +Returns the u64(unsigned 8 byte integer in little endian) value given the starting index. +If the given index + byte length does exceed the buffer bounds an error is returned. +**Note**: The maximum value supported is: `9007199254740992`, if a read of a larger value is attempted a error is returned. + +```cs +const buffer = Buffer.new(8).unwrap(); +buffer.writeUInt64LE(0, 12000); +print(buffer.readUInt64LE(0).unwrap()) // 12000 +``` + +### Buffer.readUInt32LE(Number) -> Result\ + +Returns the u32(unsigned 4 byte integer in little endian) value given the starting index. +If the given index + byte length does exceed the buffer bounds an error is returned. + +```cs +const buffer = Buffer.new(4).unwrap(); +buffer.writeUInt32LE(0, 1337); +print(buffer.readUInt32LE(0).unwrap()) // 1337 +``` + +### Buffer.readUInt16LE(Number) -> Result\ + +Returns the u16(unsigned 2 byte integer in little endian) value given the starting index. +If the given index + byte length does exceed the buffer bounds an error is returned. + +```cs +const buffer = Buffer.new(2).unwrap(); +buffer.writeUInt16LE(0, 1337); +print(buffer.readUInt16LE(0).unwrap()) // 1337 +``` + +### Buffer.readInt64LE(Number) -> Result\ + +Returns the i64(signed 8 byte integer in little endian) value given the starting index. +If the given index + byte length does exceed the buffer bounds an error is returned. + +```cs +const buffer = Buffer.new(8).unwrap(); +buffer.writeInt64LE(0, -12000); +print(buffer.readInt64LE(0).unwrap()) // -12000 +``` + +### Buffer.readInt32LE(Number) -> Result\ + +Returns the i32(signed 4 byte integer in little endian) value given the starting index. +If the given index + byte length does exceed the buffer bounds an error is returned. + +```cs +const buffer = Buffer.new(4).unwrap(); +buffer.writeInt32LE(0, -1337); +print(buffer.readInt32LE(0).unwrap()) // -1337 +``` + +### Buffer.readInt16LE(Number) -> Result\ + +Returns the i16(signed 2 byte integer in little endian) value given the starting index. +If the given index + byte length does exceed the buffer bounds an error is returned. + +```cs +const buffer = Buffer.new(2).unwrap(); +buffer.writeInt16LE(0, -1337); +print(buffer.readInt16LE(0).unwrap()) // -1337 +``` + +### Buffer.readInt8(Number) -> Result\ + +Returns the i8(signed 1 byte integer) value given the starting index. +If the given index + byte length does exceed the buffer bounds an error is returned. +**Note**: For the unsigned equivalent use get(). + +```cs +const buffer = Buffer.new(1).unwrap(); +buffer.writeUInt8(0, -12); +print(buffer.readInt8(0).unwrap()) // -12 +``` + +### Buffer.writeUInt64LE(Number, Number) -> Result\ + +Writes a u64(unsigned 8 byte integer in little endian) at the index(the first argument). +Returns a result with the set value or an error incase the byte size from the start index would exceed the buffer bounds. +```cs +const buffer = Buffer.new(8).unwrap(); +buffer.writeUInt64LE(0, 12000); +``` + +### Buffer.writeUInt32LE(Number, Number) -> Result\ + +Writes a u32(unsigned 4 byte integer in little endian) at the index(the first argument). +Returns a result with the set value or an error incase the byte size from the start index would exceed the buffer bounds. +```cs +const buffer = Buffer.new(4).unwrap(); +buffer.writeUInt32LE(0, 1337); +``` + +### Buffer.writeUInt16LE(Number, Number) -> Result\ + +Writes a u16(unsigned 2 byte integer in little endian) at the index(the first argument). +Returns a result with the set value or an error incase the byte size from the start index would exceed the buffer bounds. +```cs +const buffer = Buffer.new(2).unwrap(); +buffer.writeUInt16LE(0, 1337); +``` + +### Buffer.writeInt64LE(Number, Number) -> Result\ + +Writes a i64(signed 8 byte integer in little endian) at the index(the first argument). +Returns a result with the set value or an error incase the byte size from the start index would exceed the buffer bounds. +```cs +const buffer = Buffer.new(8).unwrap(); +buffer.writeInt64LE(0, 12000); +``` + +### Buffer.writeInt32LE(Number, Number) -> Result\ + +Writes a i32(signed 4 byte integer in little endian) at the index(the first argument). +Returns a result with the set value or an error incase the byte size from the start index would exceed the buffer bounds. +```cs +const buffer = Buffer.new(4).unwrap(); +buffer.writeInt32LE(0, 1337); +``` + +### Buffer.writeInt16LE(Number, Number) -> Result\ + +Writes a i16(signed 2 byte integer in little endian) at the index(the first argument). +Returns a result with the set value or an error incase the byte size from the start index would exceed the buffer bounds. +```cs +const buffer = Buffer.new(2).unwrap(); +buffer.writeInt16LE(0, 1337); +``` + +### Buffer.writeInt8(Number, Number) -> Result\ + +Writes a i8(signed 1 byte integer) at the index(the first argument). +Returns a result with the set value or an error incase the byte size from the start index would exceed the buffer bounds. +**Note**: For the unsigned equivalent use set(). + +```cs +const buffer = Buffer.new(1).unwrap(); +buffer.writeInt8(0, -12); +``` + +### Buffer.writeFloatLE(Number, Number) -> Result\ + +Writes a float(4 byte signed floating point number in little endian) at the index(the first argument). +Returns a result with the set value or an error incase the byte size from the start index would exceed the buffer bounds. +```cs +const buffer = Buffer.new(4).unwrap(); +buffer.writeFloatLE(0, 14.34); +``` + +### Buffer.writeDoubleLE(Number, Number) -> Result\ + +Writes a double(8 byte signed floating point number in little endian) at the index(the first argument). +Returns a result with the set value or an error incase the byte size from the start index would exceed the buffer bounds. +```cs +const buffer = Buffer.new(8).unwrap(); +buffer.writeDoubleLE(0, 14.34); +``` + +### Buffer.readFloatLE(Number) -> Result\ + +Returns the float(signed 4 byte floating point number in little endian) value given the starting index. +If the given index + byte length does exceed the buffer bounds an error is returned. + +```cs +const buffer = Buffer.new(4).unwrap(); +buffer.writeFloatLE(0, 14.34); +print(buffer.readFloatLE(0).unwrap()) // 14.34 +``` + +### Buffer.readDoubleLE(Number) -> Result\ + +Returns the double(signed 8 byte floating point number in little endian) value given the starting index. +If the given index + byte length does exceed the buffer bounds an error is returned. + +```cs +const buffer = Buffer.new(8).unwrap(); +buffer.writeDoubleLE(0, 14.34); +print(buffer.readDoubleLE(0).unwrap()) // 14.34 +``` + +### Buffer.writeUInt64BE(Number, Number) -> Result\ + +Writes a u64(unsigned 8 byte integer in big endian) at the index(the first argument). +Returns a result with the set value or an error incase the byte size from the start index would exceed the buffer bounds. +```cs +const buffer = Buffer.new(8).unwrap(); +buffer.writeUInt64BE(0, 12000); +``` + +### Buffer.writeUInt32BE(Number, Number) -> Result\ + +Writes a u32(unsigned 4 byte integer in big endian) at the index(the first argument). +Returns a result with the set value or an error incase the byte size from the start index would exceed the buffer bounds. +```cs +const buffer = Buffer.new(4).unwrap(); +buffer.writeUInt32BE(0, 1337); +``` + +### Buffer.writeUInt16BE(Number, Number) -> Result\ + +Writes a u16(unsigned 2 byte integer in big endian) at the index(the first argument). +Returns a result with the set value or an error incase the byte size from the start index would exceed the buffer bounds. +```cs +const buffer = Buffer.new(2).unwrap(); +buffer.writeUInt16BE(0, 1337); +``` + +### Buffer.writeInt64BE(Number, Number) -> Result\ + +Writes a i64(signed 8 byte integer in big endian) at the index(the first argument). +Returns a result with the set value or an error incase the byte size from the start index would exceed the buffer bounds. +```cs +const buffer = Buffer.new(8).unwrap(); +buffer.writeInt64BE(0, 12000); +``` + +### Buffer.writeInt32BE(Number, Number) -> Result\ + +Writes a i32(signed 4 byte integer in big endian) at the index(the first argument). +Returns a result with the set value or an error incase the byte size from the start index would exceed the buffer bounds. +```cs +const buffer = Buffer.new(4).unwrap(); +buffer.writeInt32BE(0, 1337); +``` + +### Buffer.writeInt16BE(Number, Number) -> Result\ + +Writes a i16(signed 2 byte integer in big endian) at the index(the first argument). +Returns a result with the set value or an error incase the byte size from the start index would exceed the buffer bounds. +```cs +const buffer = Buffer.new(2).unwrap(); +buffer.writeInt16BE(0, 1337); +``` + +### Buffer.writeFloatBE(Number, Number) -> Result\ + +Writes a float(4 byte signed floating point number in big endian) at the index(the first argument). +Returns a result with the set value or an error incase the byte size from the start index would exceed the buffer bounds. +```cs +const buffer = Buffer.new(4).unwrap(); +buffer.writeFloatBE(0, 14.34); +``` + +### Buffer.writeDoubleBE(Number, Number) -> Result\ + +Writes a double(8 byte signed floating point number in big endian) at the index(the first argument). +Returns a result with the set value or an error incase the byte size from the start index would exceed the buffer bounds. +```cs +const buffer = Buffer.new(8).unwrap(); +buffer.writeDoubleBE(0, 14.34); +``` + +### Buffer.readUInt64BE(Number) -> Result\ + +Returns the u64(unsigned 8 byte integer in big endian) value given the starting index. +If the given index + byte length does exceed the buffer bounds an error is returned. +**Note**: The maximum value supported is: `9007199254740992`, if a read of a larger value is attempted a error is returned. + +```cs +const buffer = Buffer.new(8).unwrap(); +buffer.writeUInt64BE(0, 12000); +print(buffer.readUInt64BE(0).unwrap()) // 12000 +``` + +### Buffer.readUInt32BE(Number) -> Result\ + +Returns the u32(unsigned 4 byte integer in big endian) value given the starting index. +If the given index + byte length does exceed the buffer bounds an error is returned. + +```cs +const buffer = Buffer.new(4).unwrap(); +buffer.writeUInt32BE(0, 1337); +print(buffer.readUInt32BE(0).unwrap()) // 1337 +``` + +### Buffer.readUInt16BE(Number) -> Result\ + +Returns the u16(unsigned 2 byte integer in big endian) value given the starting index. +If the given index + byte length does exceed the buffer bounds an error is returned. + +```cs +const buffer = Buffer.new(2).unwrap(); +buffer.writeUInt16BE(0, 1337); +print(buffer.readUInt16BE(0).unwrap()) // 1337 +``` + +### Buffer.readInt64BE(Number) -> Result\ + +Returns the i64(signed 8 byte integer in big endian) value given the starting index. +If the given index + byte length does exceed the buffer bounds an error is returned. + +```cs +const buffer = Buffer.new(8).unwrap(); +buffer.writeInt64BE(0, -12000); +print(buffer.readInt64BE(0).unwrap()) // -12000 +``` + +### Buffer.readInt32BE(Number) -> Result\ + +Returns the i32(signed 4 byte integer in big endian) value given the starting index. +If the given index + byte length does exceed the buffer bounds an error is returned. + +```cs +const buffer = Buffer.new(4).unwrap(); +buffer.writeInt32BE(0, -1337); +print(buffer.readInt32BE(0).unwrap()) // -1337 +``` + +### Buffer.readInt16BE(Number) -> Result\ + +Returns the i16(signed 2 byte integer in big endian) value given the starting index. +If the given index + byte length does exceed the buffer bounds an error is returned. + +```cs +const buffer = Buffer.new(2).unwrap(); +buffer.writeInt16BE(0, -1337); +print(buffer.readInt16BE(0).unwrap()) // -1337 +``` + +### Buffer.readFloatBE(Number) -> Result\ + +Returns the float(signed 4 byte floating point number in big endian) value given the starting index. +If the given index + byte length does exceed the buffer bounds an error is returned. + +```cs +const buffer = Buffer.new(4).unwrap(); +buffer.writeFloatBE(0, 14.34); +print(buffer.readFloatBE(0).unwrap()) // 14.34 +``` + +### Buffer.readDoubleBE(Number) -> Result\ + +Returns the double(signed 8 byte floating point number in big endian) value given the starting index. +If the given index + byte length does exceed the buffer bounds an error is returned. + +```cs +const buffer = Buffer.new(8).unwrap(); +buffer.writeDoubleBE(0, 14.34); +print(buffer.readDoubleBE(0).unwrap()) // 14.34 +``` \ No newline at end of file diff --git a/docs/docs/standard-lib/datetime.md b/docs/docs/standard-lib/datetime.md index b6558bbd..2e27c3ad 100644 --- a/docs/docs/standard-lib/datetime.md +++ b/docs/docs/standard-lib/datetime.md @@ -1,7 +1,7 @@ --- layout: default title: Datetime -nav_order: 4 +nav_order: 5 parent: Standard Library --- diff --git a/docs/docs/standard-lib/env.md b/docs/docs/standard-lib/env.md index 0ee26cf1..fb7ac73c 100644 --- a/docs/docs/standard-lib/env.md +++ b/docs/docs/standard-lib/env.md @@ -1,7 +1,7 @@ --- layout: default title: Env -nav_order: 5 +nav_order: 6 parent: Standard Library --- diff --git a/docs/docs/standard-lib/ffi.md b/docs/docs/standard-lib/ffi.md new file mode 100644 index 00000000..d9cc94b9 --- /dev/null +++ b/docs/docs/standard-lib/ffi.md @@ -0,0 +1,139 @@ +--- +layout: default +title: FFI +nav_order: 7 +parent: Standard Library +--- +# FFI +{: .no_toc } + +## Table of contents +{: .no_toc .text-delta } + +1. TOC +{:toc} + +--- +## FFI +To make use of the FFI module an import is required. + +```js +import FFI; +``` + +### Creating a module +Creating a FFI module requires building a dynamically shared library(.dll/.dylib/.so). + +To build a module theres only a single header needed [dictu_ffi_include.h](https://github.com/dictu-lang/Dictu/blob/develop/src/include/dictu_ffi_include.h). +The header contains definitions, function declerations and function pointers(set at runtime automatically) for anything needed to interact with the DictuVM. +Further it contains the following function decleration: +```c +int dictu_ffi_init(DictuVM *vm, Table *method_table); +``` +You will need to write a function definition(implementation) for it. +The functions return value determines success or failure, `0` is success and anything > `0` is failure, +Dictu's vm will throw a runtime error with the given code if the function returns a non 0 value. + +Upon loading the module(shared library) in dictu, the vm will load and invoke this function. +**NOTE: do not use vm functions before the init function is called since functions wont be available yet!** + +Within the function you can then define properties and functions which will then be available from dictu. +The functions must follow this signature, the types are defined in the `dictu_ffi_include.h` header: +```c +Value ffi_function(DictuVM *vm, int argCount, Value *args); +``` + +[Full example in C using cmake]() + +#### Functions +Given the following c code: +```c +#include +//... +Value dictu_ffi_function(DictuVM *vm, int argCount, Value *args) { + return OBJ_VAL(copyString(vm, "Hello From Dictu FFI module!", 28)); +} +//... +int dictu_ffi_init(DictuVM *vm, Table *method_table) { + // ... + defineNative(vm, method_table, "getString", dictu_ffi_function); + // ... + return 0; +} +``` +In dictu you can do +```cs +import FFI; + +const mod = FFI.load("/path/to/library{}".format(FFI.suffix)); +const str = mod.getString(); // "Hello From Dictu FFI module!" +``` + +#### Properties +Given the following c code: +```c +//... +int dictu_ffi_init(DictuVM *vm, Table *method_table) { + // ... + defineNativeProperty( + vm, method_table, "foo", + OBJ_VAL(copyString(vm, "Dictu!", 6))); + // ... +} +``` +In dictu you can do +```cs +import FFI; + +const mod = FFI.load("/path/to/library{}".format(FFI.suffix)); +const str = mod.foo; // Dictu! +``` + +Here a entire example from the [ffi-example](): +```c +#include + +Value dictu_ffi_test(DictuVM *vm, int argCount, Value *args) { + if(argCount != 2 || !IS_NUMBER(args[0]) || !IS_NUMBER(args[1])){ + return NIL_VAL; + } + double a = AS_NUMBER(args[0]); + double b = AS_NUMBER(args[1]); + return NUMBER_VAL(a+b); +} +Value dictu_ffi_test_str(DictuVM *vm, int argCount, Value *args) { + return OBJ_VAL(copyString(vm, "Hello From Dictu FFI module!", 28)); +} + +int dictu_ffi_init(DictuVM *vm, Table *method_table) { + defineNative(vm, method_table, "dictuFFITestAdd", dictu_ffi_test); + defineNative(vm, method_table, "dictuFFITestStr", dictu_ffi_test_str); + defineNativeProperty( + vm, method_table, "test", + OBJ_VAL(copyString(vm, "Dictu!", 6))); + return 0; +} +``` + +### Constants + +| Constant | Description | +| --------------- | ------------------------------------------------------------------------------------------------- | +| FFI.suffix | The system specific shared library suffix with the period(`.`): .dll/.so/.dylib. | + +```cs +import FFI; +const mod = FFI.load("/path/to/library{}".format(FFI.suffix)); +print(mod.test); // Dictu! +print(mod.dictuFFITestStr()); // "Hello From Dictu FFI module!" +print(mod.dictuFFITestAdd(22, 22)); // 44 +print(mod.dictuFFITestAdd(22)); // nil +``` + +### FFI.load(String) -> FFIInstance +Load a module, the Shared library **MUST** include the `dictu_ffi_include.h` header and have `dictu_ffi_init` defined otherwise it might lead to UB. +```cs +const mod = FFI.load("/path/to/shared-object"); +print(mod); // +// mod will contain all defined functions and properties by the module. +``` diff --git a/docs/docs/standard-lib/hashlib.md b/docs/docs/standard-lib/hashlib.md index ea7063cc..5759fedd 100644 --- a/docs/docs/standard-lib/hashlib.md +++ b/docs/docs/standard-lib/hashlib.md @@ -1,7 +1,7 @@ --- layout: default title: Hashlib -nav_order: 6 +nav_order: 8 parent: Standard Library --- diff --git a/docs/docs/standard-lib/http.md b/docs/docs/standard-lib/http.md index 8af4a530..a742109b 100644 --- a/docs/docs/standard-lib/http.md +++ b/docs/docs/standard-lib/http.md @@ -1,7 +1,7 @@ --- layout: default title: HTTP -nav_order: 7 +nav_order: 9 parent: Standard Library --- @@ -74,6 +74,19 @@ HTTP.head("https://httpbin.org/get", ["Content-Type: application/json"], 1); {"content": "", "headers": ["...", "..."], "statusCode": 200} ``` +### HTTP.options(String, list: headers -> Optional, Number: timeout -> Optional) -> Result\ + +Sends a HTTP OPTIONS request to a given URL. Timeout is given in seconds. +Returns a Result and unwraps to a Response upon success. + +```cs +HTTP.head("https://httpbin.org/options"); +HTTP.head("https://httpbin.org/options", ["Content-Type: application/json"]); +HTTP.head("https://httpbin.org/options", ["Content-Type: application/json"], 1); + +{"content": "", "headers": ["...", "..."], "statusCode": 404} +``` + ### HTTP.newClient(Dict) -> HttpClient Creates a new HTTP client with a given set of options. @@ -136,6 +149,12 @@ httpClient.head("https://httpbin.org/get"); {"content": "", "headers": ["...", "..."], "statusCode": 200} ``` +```cs +httpClient.options("https://httpbin.org/options"); + +{"content": "", "headers": ["...", "..."], "statusCode": 404} +``` + ### Response All HTTP requests return a Result that unwraps a Response object on success, or nil on error. diff --git a/docs/docs/standard-lib/inspect.md b/docs/docs/standard-lib/inspect.md index 30751860..c57f4a61 100644 --- a/docs/docs/standard-lib/inspect.md +++ b/docs/docs/standard-lib/inspect.md @@ -1,7 +1,7 @@ --- layout: default title: Inspect -nav_order: 8 +nav_order: 10 parent: Standard Library --- diff --git a/docs/docs/standard-lib/io.md b/docs/docs/standard-lib/io.md index b0b59bd7..c0c0f3f5 100644 --- a/docs/docs/standard-lib/io.md +++ b/docs/docs/standard-lib/io.md @@ -1,7 +1,7 @@ --- layout: default title: IO -nav_order: 9 +nav_order: 11 parent: Standard Library --- @@ -24,6 +24,12 @@ To make use of the IO module an import is required. import IO; ``` +### Constants + +| Constant | Description | +| ---------- | ----------------------------------- | +| IO.devNull | Provides access to the null device. | + ### IO.print(...values) -> Nil Prints a given list of values to stdout. diff --git a/docs/docs/standard-lib/json.md b/docs/docs/standard-lib/json.md index ada1a8a7..4a76e868 100644 --- a/docs/docs/standard-lib/json.md +++ b/docs/docs/standard-lib/json.md @@ -1,7 +1,7 @@ --- layout: default title: JSON -nav_order: 10 +nav_order: 12 parent: Standard Library --- diff --git a/docs/docs/standard-lib/log.md b/docs/docs/standard-lib/log.md index e14f0cc5..9c23fe4a 100644 --- a/docs/docs/standard-lib/log.md +++ b/docs/docs/standard-lib/log.md @@ -1,7 +1,7 @@ --- layout: default title: Log -nav_order: 11 +nav_order: 13 parent: Standard Library --- diff --git a/docs/docs/standard-lib/math.md b/docs/docs/standard-lib/math.md index d7d94ae7..81baa7ee 100644 --- a/docs/docs/standard-lib/math.md +++ b/docs/docs/standard-lib/math.md @@ -1,7 +1,7 @@ --- layout: default title: Math -nav_order: 12 +nav_order: 14 parent: Standard Library --- diff --git a/docs/docs/standard-lib/net.md b/docs/docs/standard-lib/net.md index 1d59f56a..ec6f1a1f 100644 --- a/docs/docs/standard-lib/net.md +++ b/docs/docs/standard-lib/net.md @@ -1,7 +1,7 @@ --- layout: default title: Net -nav_order: 13 +nav_order: 15 parent: Standard Library --- diff --git a/docs/docs/standard-lib/object.md b/docs/docs/standard-lib/object.md index dc5cbaf9..2c3522ff 100644 --- a/docs/docs/standard-lib/object.md +++ b/docs/docs/standard-lib/object.md @@ -1,7 +1,7 @@ --- layout: default title: Object -nav_order: 14 +nav_order: 16 parent: Standard Library --- @@ -61,3 +61,27 @@ This method will return a string of the object's hash value. ```cs Object.hash("Dictu"); ``` + +### Object.prettyPrint(Value, Number: indent -> Optional) + +This method will output to stdout a string representation of the given value. + +**NOTE** Strings, dicts, lists, numbers, booleans, and nil are valid values for pretty printing at this time. + +```cs +Object.prettyPrint([1, 2, 3]); + +// Output +'[1, 2, 3]' +``` + +```cs +Object.prettyPrint({"a": 1}, 4); + +// Output +' +{ + "a": 1 +} +' +``` diff --git a/docs/docs/standard-lib/path.md b/docs/docs/standard-lib/path.md index acdef7c3..d17ccae9 100644 --- a/docs/docs/standard-lib/path.md +++ b/docs/docs/standard-lib/path.md @@ -1,7 +1,7 @@ --- layout: default title: Path -nav_order: 15 +nav_order: 17 parent: Standard Library --- diff --git a/docs/docs/standard-lib/process.md b/docs/docs/standard-lib/process.md index 378343fb..69e99ef2 100644 --- a/docs/docs/standard-lib/process.md +++ b/docs/docs/standard-lib/process.md @@ -1,7 +1,7 @@ --- layout: default title: Process -nav_order: 16 +nav_order: 18 parent: Standard Library --- @@ -50,3 +50,19 @@ If the external process writes to stdout and you wish to capture the output you Process.run(["ls", "-la"]).unwrap(); print(Process.run(["echo", "test"], true).unwrap()); // 'test' ``` + +### Process.kill(Number, Number -> Optional) -> Result\ + +kill receives a process ID number and an optional signal number and attempts to kill the process associated with the given pid. If no signal is provided, SIGKILL is used. + +```cs +const res = Process.kill(709871); +// 0 +``` + +```cs +const res = Process.kill(709871, Process.SIGTERM).unwrap(); +// 0 +``` + +**Note:** On Windows, `kill` only takes the PID as the argument. diff --git a/docs/docs/standard-lib/queue.md b/docs/docs/standard-lib/queue.md index a8db6d28..b724391d 100644 --- a/docs/docs/standard-lib/queue.md +++ b/docs/docs/standard-lib/queue.md @@ -1,7 +1,7 @@ --- layout: default title: Queue -nav_order: 17 +nav_order: 19 parent: Standard Library --- diff --git a/docs/docs/standard-lib/random.md b/docs/docs/standard-lib/random.md index b4f330dd..8f237dca 100644 --- a/docs/docs/standard-lib/random.md +++ b/docs/docs/standard-lib/random.md @@ -1,7 +1,7 @@ --- layout: default title: Random -nav_order: 18 +nav_order: 20 parent: Standard Library --- diff --git a/docs/docs/standard-lib/socket.md b/docs/docs/standard-lib/socket.md index 8fcec833..888dda57 100644 --- a/docs/docs/standard-lib/socket.md +++ b/docs/docs/standard-lib/socket.md @@ -1,7 +1,7 @@ --- layout: default title: Socket -nav_order: 19 +nav_order: 21 parent: Standard Library --- diff --git a/docs/docs/standard-lib/sqlite.md b/docs/docs/standard-lib/sqlite.md index 2bf9c6ed..4ad27980 100644 --- a/docs/docs/standard-lib/sqlite.md +++ b/docs/docs/standard-lib/sqlite.md @@ -1,7 +1,7 @@ --- layout: default title: Sqlite -nav_order: 20 +nav_order: 22 parent: Standard Library --- diff --git a/docs/docs/standard-lib/stack.md b/docs/docs/standard-lib/stack.md index 10756fa8..022a906f 100644 --- a/docs/docs/standard-lib/stack.md +++ b/docs/docs/standard-lib/stack.md @@ -1,7 +1,7 @@ --- layout: default title: Stack -nav_order: 21 +nav_order: 23 parent: Standard Library --- diff --git a/docs/docs/standard-lib/system.md b/docs/docs/standard-lib/system.md index b1e54e8b..66f5a30b 100644 --- a/docs/docs/standard-lib/system.md +++ b/docs/docs/standard-lib/system.md @@ -1,7 +1,7 @@ --- layout: default title: System -nav_order: 22 +nav_order: 24 parent: Standard Library --- @@ -29,6 +29,7 @@ import System; | --------------- | ------------------------------------------------------------------------------------------------- | | System.argv | The list of command line arguments. The first element of the argv list is always the script name. | | System.platform | This string identifies the underlying system platform. | +| System.arch | This string identifies the underlying process architecture. | | System.version | Dictionary containing Dictu major, minor and patch versions. | | System.S_IRWXU | Read, write, and execute by owner. | | System.S_IRUSR | Read by owner. | @@ -238,7 +239,7 @@ Shell $ echo $?; // 10 ``` -### System.chmod(String, String) +### System.chmod(String, String) -> Result\ Set the permissions on a file or directory. @@ -246,7 +247,21 @@ Set the permissions on a file or directory. System.chmod("/usr/local/share", "755"); ``` -### System.chown(String, Number, Number) +### System.chown(String, Number, Number) -> Result\ + +Set the ownership of a file or directory with the given path, uid, and gid. + +Note: This is not available on Windows systems. + +### System.rename(String, String) -> Result\ + +Rename the given file. + +```cs +System.rename("old_file", "new_file"); +``` + +### System.chown(String, Number, Number) -> Result\ Set the ownership of a file or directory with the given path, uid, and gid. diff --git a/docs/docs/standard-lib/term.md b/docs/docs/standard-lib/term.md index b46c7391..0b2cb43c 100644 --- a/docs/docs/standard-lib/term.md +++ b/docs/docs/standard-lib/term.md @@ -1,7 +1,7 @@ --- layout: default title: Term -nav_order: 23 +nav_order: 25 parent: Standard Library --- diff --git a/docs/docs/standard-lib/unittest.md b/docs/docs/standard-lib/unittest.md index be88a70d..84a93713 100644 --- a/docs/docs/standard-lib/unittest.md +++ b/docs/docs/standard-lib/unittest.md @@ -1,7 +1,7 @@ --- layout: default title: UnitTest -nav_order: 24 +nav_order: 26 parent: Standard Library --- diff --git a/docs/docs/standard-lib/uuid.md b/docs/docs/standard-lib/uuid.md index 2e6f0e93..3cc5d120 100644 --- a/docs/docs/standard-lib/uuid.md +++ b/docs/docs/standard-lib/uuid.md @@ -1,7 +1,7 @@ --- layout: default title: UUID -nav_order: 25 +nav_order: 27 parent: Standard Library --- diff --git a/docs/docs/strings.md b/docs/docs/strings.md index 5dfb75c8..bc946f67 100644 --- a/docs/docs/strings.md +++ b/docs/docs/strings.md @@ -205,6 +205,15 @@ To find the index of a given substring, use the `.find()` method. This method ta "House".find("Lost Keys"); // -1 (Not found) ``` +### string.findLast(String) -> Number + +Returns the last index of the given string. If the substring doesn't exist, -1 is returned. + +```cs +"woolly woolly mammoth".findLast("woolly"); // 7 +"mammoth".findLast("woolly"); // -1 +``` + ### string.leftStrip() -> String Strips whitespace from the left side of a string and returns the result. @@ -267,4 +276,53 @@ Returns a new string with the original string repeated the given number of times ```cs "ba"+"na".repeat(2); // banana "la".repeat(2) + " land"; // lala land -``` \ No newline at end of file +``` + +### string.isUpper() -> Boolean + +Returns a boolean indicating that the given string is an upper case letter. + +```cs +"D".isUpper(); // true +"d".isUpper() // false +"G00D!".isUpper() // true +"Dog".isUpper() // false +``` + +### string.isLower() -> Boolean + +Returns a boolean indicating that the given string is an lower case letter. Empty strings are considered false. + +```cs +"D".isLower(); // false +"d".isLower() // true +"g00d!".isLower() // true +"Dog".isLower() // false +``` + +### string.wordCount() -> Number + +Returns the number of words in the given string. Empty strings are considered false. + +```cs +"".wordCount(); // 0 +"This".wordCount(); // 1 +"This is a sentence".wordCount(); // 4 +"This is an even longer sentence".wordCount(); // 6 +``` + +### string.collapseSpaces() -> String + +Returns a string with extraneous whitespace removed. + +```cs +"This is a huge string of a lot of spaces.".collapseSpaces(); // "This is a huge string of a lot of spaces." +``` + +### string.wrap(Number) -> String + +Returns a new string with new lines inserted at the given length. + +```cs +"This is a really really long string that will need to be broken up for whatever reason the caller has determined. Not our business as to why, but we can provide the functionality for them to do so.".wrap(80); +``` diff --git a/docs/docs/variables.md b/docs/docs/variables.md index 6eb98bbe..867700cc 100644 --- a/docs/docs/variables.md +++ b/docs/docs/variables.md @@ -27,6 +27,43 @@ nav_order: 3 | Nil | `nil` | Used to signify no value (much like null in other languages) | | Result | `Success`, `Error` | See [Error Handling section.](/docs/error-handling) | +## Truthy / Falsey + +Truthy / Falsey are terms that describe a value that will evaluate to either true or false. This, however, is not the same +as the value equalling the boolean value of true or false. + +The table below describes falsey types for built-in data types, any other value would evaluate to truthy. + +| Type | Example | Note | +| --- |-----------------------------------------------|-----------------------------| +| String | `''`, `""` | A string of 0 length. | +| Number | `0` | The value `0`. | +| Boolean | `false` | The value `false`. | +| List | `[]` | A list of 0 length. | +| Dictionary | `{}` | A dictionary with 0 length. | +| Set | `set()` | A set with 0 length. | +| Nil | `nil` | The value `nil` | +| Result | `Error` | A result in Error state. | + +### Example + +This means we can use these values in a conditional check without the need for an explicit value check + +```cs +const someList = [1, 2, 3].filter(def (x) => x > 3); + +if (someList) { + // The list has values within it + ... +} + +const someResult = ...; // Some action that returns a Result + +if (not someResult) { + print("Error!"); +} +``` + ## Declaring a variable ```cs diff --git a/docs/index.md b/docs/index.md index b2c1e7dd..0eb5e17b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -19,7 +19,7 @@ import JSON; HTTP.get("https://api.coindesk.com/v1/bpi/currentprice.json").match( def (response) => { - const data = JSON.parse(response["content"]).unwrap(); + const data = JSON.parse(response.content).unwrap(); print("${} per BTC".format(data["bpi"]["USD"]["rate"])); // $10,577.70 per BTC }, def (error) => print(error) @@ -29,8 +29,6 @@ HTTP.get("https://api.coindesk.com/v1/bpi/currentprice.json").match( Dictu is a high-level dynamically typed, multi-paradigm, interpreted programming language. Dictu has a very familiar C-style syntax along with taking inspiration from the family of languages surrounding it, such as Python and JavaScript. -Dictu means simplistic in Latin. This is the aim of the language: to be as simplistic, organized, and logical as humanly possible. - {: .fs-6 .fw-300 } --- diff --git a/examples/ffi-example/CMakeLists.txt b/examples/ffi-example/CMakeLists.txt new file mode 100644 index 00000000..7e3e4844 --- /dev/null +++ b/examples/ffi-example/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.16.3) +PROJECT("ffi-test") +set(CMAKE_C_STANDARD 11) +add_library(ffi-test SHARED src/lib.c) + +target_include_directories(ffi-test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../../src/include") \ No newline at end of file diff --git a/examples/ffi-example/ffi.du b/examples/ffi-example/ffi.du new file mode 100644 index 00000000..8a964dd3 --- /dev/null +++ b/examples/ffi-example/ffi.du @@ -0,0 +1,16 @@ +import FFI; +import System; + +var mod = nil; + +if(System.platform == "windows") { + mod = FFI.load("./build/Release/ffi-test{}".format(FFI.suffix)); +} else { + mod = FFI.load("./build/libffi-test{}".format(FFI.suffix)); +} + +for(var i = 0; i < 25; i+=1) { + print(mod.dictuFFITestAdd(1, i*2)); + print(mod.dictuFFITestStr()); +} +print(mod.test); diff --git a/examples/ffi-example/src/lib.c b/examples/ffi-example/src/lib.c new file mode 100644 index 00000000..7144927a --- /dev/null +++ b/examples/ffi-example/src/lib.c @@ -0,0 +1,22 @@ +#include + +Value dictu_ffi_test(DictuVM *vm, int argCount, Value *args) { + if(argCount != 2 || !IS_NUMBER(args[0]) || !IS_NUMBER(args[1])){ + return NIL_VAL; + } + double a = AS_NUMBER(args[0]); + double b = AS_NUMBER(args[1]); + return NUMBER_VAL(a+b); +} +Value dictu_ffi_test_str(DictuVM *vm, int argCount, Value *args) { + return OBJ_VAL(copyString(vm, "Hello From Dictu FFI module!", 28)); +} + +int dictu_ffi_init(DictuVM *vm, Table *method_table) { + defineNative(vm, method_table, "dictuFFITestAdd", dictu_ffi_test); + defineNative(vm, method_table, "dictuFFITestStr", dictu_ffi_test_str); + defineNativeProperty( + vm, method_table, "test", + OBJ_VAL(copyString(vm, "Dictu!", 6))); + return 0; +} diff --git a/examples/httpClient.du b/examples/httpClient.du index 8cd6b232..e6f7705c 100644 --- a/examples/httpClient.du +++ b/examples/httpClient.du @@ -43,6 +43,13 @@ import System; print("Status Code: {}".format(res.unwrap().statusCode)); print(res.unwrap().json().unwrap()); + res = httpClient.options("https://httpbin.org/options", {}); + if (not res.success()) { + print(res.unwrapError()); + System.exit(1); + } + print("Headers: {}".format(res.unwrap().headers)); + System.exit(0); } diff --git a/examples/writeDevNull.du b/examples/writeDevNull.du new file mode 100644 index 00000000..faeb6443 --- /dev/null +++ b/examples/writeDevNull.du @@ -0,0 +1,13 @@ +import IO; +import System; + + +{ // main + print(IO.devNull); + with(IO.devNull, 'w') { + const out = file.write("asdfasfdasdfasdf"); + print(out); + } + + System.exit(0); +} diff --git a/ops/checkTests.du b/ops/checkTests.du index d7f102a3..d9288ec3 100644 --- a/ops/checkTests.du +++ b/ops/checkTests.du @@ -23,6 +23,9 @@ const ignored = { 'range.du', 'select.du', ], + 'ffi': [ + 'libs', + ], '*': [ 'import.du', ] diff --git a/src/cli/main.c b/src/cli/main.c index 8bdaf95d..857005f2 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -1,6 +1,20 @@ +#include +#if defined(__linux__) || defined(_WIN32) +#include +#elif defined(__APPLE__) || defined(__FreeBSD__) +#include +#endif #include #include #include +#include +#include +#ifdef _WIN32 +#include "../optionals/windowsapi.h" +#define PATH_MAX MAX_PATH +#else +#include +#endif #define UNUSED(__x__) (void) __x__ @@ -13,8 +27,10 @@ #include "linenoise/linenoise.h" -static int matchStringLiteral(char* line, int i) -{ +#define DICTU_HOME "/.dictu" +#define DICTU_HIST "/history.txt" + +static int matchStringLiteral(char* line, int i) { char quote = line[i]; if (quote != '\'' && quote != '"') { @@ -62,16 +78,44 @@ static bool matchBraces(char *line) { return braceLevel == 0; } -static void memcpyAndAppendNul(char* dst, char* src, int len) -{ +static void memcpyAndAppendNul(char* dst, char* src, int len) { memcpy(dst, src, len); dst[len] = '\0'; } +// getDictuPath sets up the path to be used for saving history +// amongst other things. The returned string needs to be freed +// by the caller. +char *getDictuPath() { + char *dictuPath = calloc(PATH_MAX, sizeof(char) * PATH_MAX); + char *tmp; + + if ((tmp = getenv("DICTU_PATH")) != NULL) { + strncat(dictuPath, tmp, strlen(tmp)); + } else { + const char *home = getenv("HOME"); + strncpy(dictuPath, home, strlen(home)); + strncat(dictuPath, DICTU_HOME, strlen(DICTU_HOME)); + } + + return dictuPath; +} + static void repl(DictuVM *vm) { printf(DICTU_STRING_VERSION); char *line; - linenoiseHistoryLoad("history.txt"); + + char *dictuPath = getDictuPath(); + + if (mkdir(dictuPath, 0700) == -1 && errno != EEXIST) { + fprintf(stderr, "Cannot create directory %s - %s\n", dictuPath, strerror(errno)); + free(dictuPath); + exit(75); + } + + strncat(dictuPath, DICTU_HIST, strlen(DICTU_HIST)); + + linenoiseHistoryLoad(dictuPath); while((line = linenoise(">>> ")) != NULL) { int statementLength = strlen(line); @@ -79,7 +123,7 @@ static void repl(DictuVM *vm) { memcpyAndAppendNul(statement, line, statementLength); linenoiseHistoryAdd(line); - linenoiseHistorySave("history.txt"); + linenoiseHistorySave(dictuPath); while (!matchBraces(statement)) { free(line); @@ -103,7 +147,7 @@ static void repl(DictuVM *vm) { statementLength += lineLength; linenoiseHistoryAdd(line); - linenoiseHistorySave("history.txt"); + linenoiseHistorySave(dictuPath); } dictuInterpret(vm, "repl", statement); @@ -111,6 +155,8 @@ static void repl(DictuVM *vm) { free(line); free(statement); } + + free(dictuPath); } static char *readFile(const char *path) { @@ -157,9 +203,9 @@ static void runFile(DictuVM *vm, char *filename) { } static const char *const usage[] = { - "dictu [options] [[--] args]", - "dictu [options]", - NULL, + "dictu [options] [[--] args]", + "dictu [options]", + NULL, }; int main(int argc, char *argv[]) { @@ -167,10 +213,10 @@ int main(int argc, char *argv[]) { char *cmd = NULL; struct argparse_option options[] = { - OPT_HELP(), - OPT_BOOLEAN('v', "version", &version, "Display Dictu version"), - OPT_STRING('c', "cmd", &cmd, "Run program passed in as string"), - OPT_END(), + OPT_HELP(), + OPT_BOOLEAN('v', "version", &version, "Display Dictu version"), + OPT_STRING('c', "cmd", &cmd, "Run program passed in as string"), + OPT_END(), }; struct argparse argparse; diff --git a/src/include/dictu_ffi_include.h b/src/include/dictu_ffi_include.h new file mode 100644 index 00000000..abefa144 --- /dev/null +++ b/src/include/dictu_ffi_include.h @@ -0,0 +1,676 @@ +#ifndef dictu_ffi_include_h +#define dictu_ffi_include_h + +#ifdef __cplusplus +extern "C" { +#endif +#include +#include +#include +#include + +// This is used ti determine if we can safely load the function pointers without +// UB. +#define FFI_MOD_API_VERSION 2 + +#define UNUSED(__x__) (void)__x__ + +#define MAX_ERROR_LEN 256 + +#define ERROR_RESULT \ + do { \ + char buf[MAX_ERROR_LEN]; \ + getStrerror(buf, errno); \ + return newResultError(vm, buf); \ + } while (false) + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +#ifdef _WIN32 +#define DIR_SEPARATOR '\\' +#define DIR_ALT_SEPARATOR '/' +#define DIR_SEPARATOR_AS_STRING "\\" +#define DIR_SEPARATOR_STRLEN 1 +#define PATH_DELIMITER ';' +#define PATH_DELIMITER_AS_STRING ";" +#define PATH_DELIMITER_STRLEN 1 +#else +#define HAS_REALPATH +#define DIR_SEPARATOR '/' +#define DIR_SEPARATOR_AS_STRING "/" +#define DIR_SEPARATOR_STRLEN 1 +#define PATH_DELIMITER ':' +#define PATH_DELIMITER_AS_STRING ":" +#define PATH_DELIMITER_STRLEN 1 +#endif + +#ifdef DIR_ALT_SEPARATOR +#define IS_DIR_SEPARATOR(c) ((c) == DIR_SEPARATOR || (c) == DIR_ALT_SEPARATOR) +#else +#define IS_DIR_SEPARATOR(c) (c == DIR_SEPARATOR) +#endif + +#define DEBUG_PRINT_CODE +#define DEBUG_TRACE_EXECUTION +#define DEBUG_TRACE_GC +#define DEBUG_TRACE_MEM + +#ifndef _MSC_VER +#define COMPUTED_GOTO +#endif + +#undef DEBUG_PRINT_CODE +#undef DEBUG_TRACE_EXECUTION +#undef DEBUG_TRACE_GC +#undef DEBUG_TRACE_MEM + +// #define DEBUG_STRESS_GC +// #define DEBUG_FINAL_MEM + +#define UINT8_COUNT (UINT8_MAX + 1) + +typedef struct sObj Obj; +typedef struct sObjString ObjString; +typedef struct sObjList ObjList; +typedef struct sObjDict ObjDict; +typedef struct sObjSet ObjSet; +typedef struct sObjFile ObjFile; +typedef struct sObjAbstract ObjAbstract; +typedef struct sObjResult ObjResult; + +// A mask that selects the sign bit. +#define SIGN_BIT ((uint64_t)1 << 63) + +// The bits that must be set to indicate a quiet NaN. +#define QNAN ((uint64_t)0x7ffc000000000000) + +// Tag values for the different singleton values. +#define TAG_NIL 1 +#define TAG_FALSE 2 +#define TAG_TRUE 3 +#define TAG_EMPTY 4 + +typedef uint64_t Value; + +#define IS_BOOL(v) (((v) | 1) == TRUE_VAL) +#define IS_NIL(v) ((v) == NIL_VAL) +#define IS_EMPTY(v) ((v) == EMPTY_VAL) +// If the NaN bits are set, it's not a number. +#define IS_NUMBER(v) (((v)&QNAN) != QNAN) +#define IS_OBJ(v) (((v) & (QNAN | SIGN_BIT)) == (QNAN | SIGN_BIT)) + +#define AS_BOOL(v) ((v) == TRUE_VAL) +#define AS_NUMBER(v) valueToNum(v) +#define AS_OBJ(v) ((Obj *)(uintptr_t)((v) & ~(SIGN_BIT | QNAN))) + +#define BOOL_VAL(boolean) ((boolean) ? TRUE_VAL : FALSE_VAL) +#define FALSE_VAL ((Value)(uint64_t)(QNAN | TAG_FALSE)) +#define TRUE_VAL ((Value)(uint64_t)(QNAN | TAG_TRUE)) +#define NIL_VAL ((Value)(uint64_t)(QNAN | TAG_NIL)) +#define EMPTY_VAL ((Value)(uint64_t)(QNAN | TAG_EMPTY)) +#define NUMBER_VAL(num) numToValue(num) +// The triple casting is necessary here to satisfy some compilers: +// 1. (uintptr_t) Convert the pointer to a number of the right size. +// 2. (uint64_t) Pad it up to 64 bits in 32-bit builds. +// 3. Or in the bits to make a tagged Nan. +// 4. Cast to a typedef'd value. +#define OBJ_VAL(obj) (Value)(SIGN_BIT | QNAN | (uint64_t)(uintptr_t)(obj)) + +// A union to let us reinterpret a double as raw bits and back. +typedef union { + uint64_t bits64; + uint32_t bits32[2]; + double num; +} DoubleUnion; + +static inline double valueToNum(Value value) { + DoubleUnion data; + data.bits64 = value; + return data.num; +} + +static inline Value numToValue(double num) { + DoubleUnion data; + data.num = num; + return data.bits64; +} + +typedef struct { + int capacity; + int count; + Value *values; +} ValueArray; + +typedef struct { + ObjString *key; + Value value; + uint32_t psl; +} Entry; + +typedef struct { + int count; + int capacity; + Entry *entries; +} Table; + +typedef enum { + OBJ_MODULE, + OBJ_BOUND_METHOD, + OBJ_CLASS, + OBJ_ENUM, + OBJ_CLOSURE, + OBJ_FUNCTION, + OBJ_INSTANCE, + OBJ_NATIVE, + OBJ_STRING, + OBJ_LIST, + OBJ_DICT, + OBJ_SET, + OBJ_FILE, + OBJ_ABSTRACT, + OBJ_RESULT, + OBJ_UPVALUE +} ObjType; + +#define OBJ_TYPE(value) (AS_OBJ(value)->type) + +#define AS_MODULE(value) ((ObjModule *)AS_OBJ(value)) +#define AS_BOUND_METHOD(value) ((ObjBoundMethod *)AS_OBJ(value)) +#define AS_CLASS(value) ((ObjClass *)AS_OBJ(value)) +#define AS_ENUM(value) ((ObjEnum *)AS_OBJ(value)) +#define AS_CLOSURE(value) ((ObjClosure *)AS_OBJ(value)) +#define AS_FUNCTION(value) ((ObjFunction *)AS_OBJ(value)) +#define AS_INSTANCE(value) ((ObjInstance *)AS_OBJ(value)) +#define AS_NATIVE(value) (((ObjNative *)AS_OBJ(value))->function) +#define AS_STRING(value) ((ObjString *)AS_OBJ(value)) +#define AS_CSTRING(value) (((ObjString *)AS_OBJ(value))->chars) +#define AS_LIST(value) ((ObjList *)AS_OBJ(value)) +#define AS_DICT(value) ((ObjDict *)AS_OBJ(value)) +#define AS_SET(value) ((ObjSet *)AS_OBJ(value)) +#define AS_FILE(value) ((ObjFile *)AS_OBJ(value)) +#define AS_ABSTRACT(value) ((ObjAbstract *)AS_OBJ(value)) +#define AS_RESULT(value) ((ObjResult *)AS_OBJ(value)) + +#define IS_MODULE(value) isObjType(value, OBJ_MODULE) +#define IS_BOUND_METHOD(value) isObjType(value, OBJ_BOUND_METHOD) +#define IS_CLASS(value) isObjType(value, OBJ_CLASS) +#define IS_DEFAULT_CLASS(value) \ + isObjType(value, OBJ_CLASS) && AS_CLASS(value)->type == CLASS_DEFAULT +#define IS_TRAIT(value) \ + isObjType(value, OBJ_CLASS) && AS_CLASS(value)->type == CLASS_TRAIT +#define IS_ENUM(value) isObjType(value, OBJ_ENUM) +#define IS_CLOSURE(value) isObjType(value, OBJ_CLOSURE) +#define IS_FUNCTION(value) isObjType(value, OBJ_FUNCTION) +#define IS_INSTANCE(value) isObjType(value, OBJ_INSTANCE) +#define IS_NATIVE(value) isObjType(value, OBJ_NATIVE) +#define IS_STRING(value) isObjType(value, OBJ_STRING) +#define IS_LIST(value) isObjType(value, OBJ_LIST) +#define IS_DICT(value) isObjType(value, OBJ_DICT) +#define IS_SET(value) isObjType(value, OBJ_SET) +#define IS_FILE(value) isObjType(value, OBJ_FILE) +#define IS_ABSTRACT(value) isObjType(value, OBJ_ABSTRACT) +#define IS_RESULT(value) isObjType(value, OBJ_RESULT) + + + +typedef enum { CLASS_DEFAULT, CLASS_ABSTRACT, CLASS_TRAIT } ClassType; + +typedef enum { ACCESS_PUBLIC, ACCESS_PRIVATE } AccessLevel; + +typedef enum { + TYPE_FUNCTION, + TYPE_ARROW_FUNCTION, + TYPE_INITIALIZER, + TYPE_METHOD, + TYPE_STATIC, + TYPE_ABSTRACT, + TYPE_TOP_LEVEL +} FunctionType; + +struct sObj { + ObjType type; + bool isDark; + struct sObj *next; +}; +static inline bool isObjType(Value value, ObjType type) { + return IS_OBJ(value) && AS_OBJ(value)->type == type; +} + +typedef struct sUpvalue { + Obj obj; + + // Pointer to the variable this upvalue is referencing. + Value *value; + + // If the upvalue is closed (i.e. the local variable it was pointing + // to has been popped off the stack) then the closed-over value is + // hoisted out of the stack into here. [value] is then be changed to + // point to this. + Value closed; + + // Open upvalues are stored in a linked list. This points to the next + // one in that list. + struct sUpvalue *next; +} ObjUpvalue; + +typedef struct { + Obj obj; + ObjString *name; + ObjString *path; + Table values; +} ObjModule; + +typedef struct { + int count; + int capacity; + uint8_t *code; + int *lines; + ValueArray constants; +} Chunk; + +typedef struct { + Obj obj; + int isVariadic; + int arity; + int arityOptional; + int upvalueCount; + Chunk chunk; + ObjString *name; + FunctionType type; + AccessLevel accessLevel; + ObjModule *module; + int propertyCount; + int *propertyNames; + int *propertyIndexes; + int privatePropertyCount; + int *privatePropertyNames; + int *privatePropertyIndexes; +} ObjFunction; + +#define STACK_MAX (64 * UINT8_COUNT) +typedef struct { + Obj obj; + ObjFunction *function; + ObjUpvalue **upvalues; + int upvalueCount; +} ObjClosure; + +typedef struct { + ObjClosure *closure; + uint8_t *ip; + Value *slots; +} CallFrame; + +struct _vm { + void* _compilerStub; + Value stack[STACK_MAX]; + Value *stackTop; + bool repl; + CallFrame *frames; + int frameCount; + int frameCapacity; + ObjModule *lastModule; + Table modules; + Table globals; + Table constants; + Table strings; + Table numberMethods; + Table boolMethods; + Table nilMethods; + Table stringMethods; + Table listMethods; + Table dictMethods; + Table setMethods; + Table fileMethods; + Table classMethods; + Table instanceMethods; + Table resultMethods; + Table enumMethods; + ObjString *initString; + ObjString *annotationString; + ObjString *replVar; + ObjUpvalue *openUpvalues; + size_t bytesAllocated; + size_t nextGC; + Obj *objects; + int grayCount; + int grayCapacity; + Obj **grayStack; + int argc; + char **argv; +}; + +#define DICTU_MAJOR_VERSION "0" +#define DICTU_MINOR_VERSION "29" +#define DICTU_PATCH_VERSION "0" + +#define DICTU_STRING_VERSION \ + "Dictu Version: " DICTU_MAJOR_VERSION "." DICTU_MINOR_VERSION \ + "." DICTU_PATCH_VERSION "\n" + +typedef struct _vm DictuVM; + +typedef Value (*NativeFn)(DictuVM *vm, int argCount, Value *args); + +typedef struct { + Obj obj; + NativeFn function; +} ObjNative; + +struct sObjString { + Obj obj; + int length; + char *chars; + uint32_t hash; +}; + +struct sObjList { + Obj obj; + ValueArray values; +}; + +typedef struct { + Value key; + Value value; +} DictItem; + +struct sObjDict { + Obj obj; + int count; + int activeCount; + int capacityMask; + DictItem *entries; +}; + +typedef struct { + Value value; + bool deleted; +} SetItem; + +struct sObjSet { + Obj obj; + int count; + int capacityMask; + SetItem *entries; +}; + +struct sObjFile { + Obj obj; + FILE *file; + char *path; + char *openType; +}; + +typedef void (*AbstractFreeFn)(DictuVM *vm, ObjAbstract *abstract); +typedef void (*AbstractGrayFn)(DictuVM *vm, ObjAbstract *abstract); +typedef char *(*AbstractTypeFn)(ObjAbstract *abstract); + +struct sObjAbstract { + Obj obj; + Table values; + void *data; + AbstractFreeFn func; + AbstractGrayFn grayFunc; + AbstractTypeFn type; + bool excludeSelf; +}; + +typedef enum { SUCCESS, ERR } ResultStatus; + +struct sObjResult { + Obj obj; + ResultStatus status; + Value value; +}; + +typedef struct sObjClass { + Obj obj; + ObjString *name; + struct sObjClass *superclass; + Table publicMethods; + Table privateMethods; + Table abstractMethods; + Table variables; + Table constants; + ObjDict *classAnnotations; + ObjDict *methodAnnotations; + ObjDict *fieldAnnotations; + ClassType type; +} ObjClass; + +typedef struct sObjEnum { + Obj obj; + ObjString *name; + Table values; +} ObjEnum; + +typedef struct { + Obj obj; + ObjClass *klass; + Table privateAttributes; + Table publicAttributes; +} ObjInstance; + +typedef struct { + Obj obj; + Value receiver; + ObjClosure *method; +} ObjBoundMethod; + +#define ALLOCATE(vm, type, count) \ + (type*)reallocate(vm, NULL, 0, sizeof(type) * (count)) + +#define FREE(vm, type, pointer) \ + reallocate(vm, pointer, sizeof(type), 0) + +#define GROW_CAPACITY(capacity) \ + ((capacity) < 8 ? 8 : (capacity) * 2) + +#define SHRINK_CAPACITY(capacity) \ + ((capacity) < 16 ? 8 : (capacity) / 2) + +#define GROW_ARRAY(vm, previous, type, oldCount, count) \ + (type*)reallocate(vm, previous, sizeof(type) * (oldCount), \ + sizeof(type) * (count)) + +#define SHRINK_ARRAY(vm, previous, type, oldCount, count) \ + (type*)reallocate(vm, previous, sizeof(type) * (oldCount), \ + sizeof(type) * (count)) + +#define FREE_ARRAY(vm, type, pointer, oldCount) \ + reallocate(vm, pointer, sizeof(type) * (oldCount), 0) + +typedef void *reallocate_t(DictuVM *vm, void *previous, size_t oldSize, size_t newSize); + +typedef ObjString *copyString_t(DictuVM *vm, const char *chars, int length); + +typedef ObjList *newList_t(DictuVM *vm); + +typedef ObjDict *newDict_t(DictuVM *vm); + +typedef ObjSet *newSet_t(DictuVM *vm); + +typedef ObjFile *newFile_t(DictuVM *vm); + +typedef ObjAbstract *newAbstract_t(DictuVM *vm, AbstractFreeFn func, + AbstractTypeFn type); + +typedef ObjResult *newResult_t(DictuVM *vm, ResultStatus status, Value value); + +typedef Value newResultSuccess_t(DictuVM *vm, Value value); + +typedef Value newResultError_t(DictuVM *vm, char *errorMsg); + +typedef void push_t(DictuVM *vm, Value value); + +typedef Value peek_t(DictuVM *vm, int distance); + +typedef void runtimeError_t(DictuVM *vm, const char *format, ...); + +typedef Value pop_t(DictuVM *vm); + +typedef bool isFalsey_t(Value value); + +typedef bool valuesEqual_t(Value a, Value b); + +typedef void initValueArray_t(ValueArray *array); + +typedef void writeValueArray_t(DictuVM *vm, ValueArray *array, Value value); + +typedef void freeValueArray_t(DictuVM *vm, ValueArray *array); + +typedef bool dictSet_t(DictuVM *vm, ObjDict *dict, Value key, Value value); + +typedef bool dictGet_t(ObjDict *dict, Value key, Value *value); + +typedef bool dictDelete_t(DictuVM *vm, ObjDict *dict, Value key); + +typedef bool setGet_t(ObjSet *set, Value value); + +typedef bool setInsert_t(DictuVM *vm, ObjSet *set, Value value); + +typedef bool setDelete_t(DictuVM *vm, ObjSet *set, Value value); + +typedef char *valueToString_t(Value value); + +typedef char *valueTypeToString_t(DictuVM *vm, Value value, int *length); + +typedef void printValue_t(Value value); + +typedef void printValueError_t(Value value); + +typedef bool compareStringLess_t(Value a, Value b); + +typedef bool compareStringGreater_t(Value a, Value b); + +typedef void defineNative_t(DictuVM *vm, Table *table, const char *name, + NativeFn function); + +typedef void defineNativeProperty_t(DictuVM *vm, Table *table, const char *name, + Value value); +reallocate_t * reallocate = NULL; + +copyString_t *copyString = NULL; + +newList_t *newList = NULL; + +newDict_t *newDict = NULL; + +newSet_t *newSet = NULL; + +newFile_t *newFile = NULL; + +newAbstract_t *newAbstract = NULL; + +newResult_t *newResult = NULL; + +newResultSuccess_t *newResultSuccess = NULL; + +newResultError_t *newResultError = NULL; + +push_t *push = NULL; + +peek_t *peek = NULL; + +runtimeError_t *runtimeError = NULL; + +pop_t *pop = NULL; + +isFalsey_t *isFalsey = NULL; + +valuesEqual_t *valuesEqual = NULL; + +initValueArray_t *initValueArray = NULL; + +writeValueArray_t *writeValueArray = NULL; + +freeValueArray_t *freeValueArray = NULL; + +dictSet_t *dictSet = NULL; + +dictGet_t *dictGet = NULL; + +dictDelete_t *dictDelete = NULL; + +setGet_t *setGet = NULL; + +setInsert_t *setInsert = NULL; + +setDelete_t *setDelete = NULL; + +valueToString_t *valueToString = NULL; + +valueTypeToString_t *valueTypeToString = NULL; + +printValue_t *printValue = NULL; + +printValueError_t *printValueError = NULL; + +compareStringLess_t *compareStringLess = NULL; + +compareStringGreater_t *compareStringGreater = NULL; + +defineNative_t *defineNative = NULL; + +defineNativeProperty_t *defineNativeProperty = NULL; + +// This needs to be implemented by the user and register all functions +int dictu_ffi_init(DictuVM *vm, Table *method_table); + +#ifdef _WIN32 +__declspec(dllexport) +#endif +int dictu_internal_ffi_init(void **function_ptrs, DictuVM *vm, + Table *methodTable, int vm_ffi_version) { + if (copyString != NULL) { + // we already initialized. + return 1; + } + if (FFI_MOD_API_VERSION > vm_ffi_version) + return 2; + size_t count = 0; + + copyString = (copyString_t *)function_ptrs[count++]; + newList = (newList_t *)function_ptrs[count++]; + newDict = (newDict_t *)function_ptrs[count++]; + newSet = (newSet_t *)function_ptrs[count++]; + newFile = (newFile_t *)function_ptrs[count++]; + newAbstract = (newAbstract_t *)function_ptrs[count++]; + newResult = (newResult_t *)function_ptrs[count++]; + newResultSuccess = (newResultSuccess_t *)function_ptrs[count++]; + newResultError = (newResultError_t *)function_ptrs[count++]; + push = (push_t *)function_ptrs[count++]; + peek = (peek_t *)function_ptrs[count++]; + runtimeError = (runtimeError_t *)function_ptrs[count++]; + pop = (pop_t *)function_ptrs[count++]; + isFalsey = (isFalsey_t *)function_ptrs[count++]; + valuesEqual = (valuesEqual_t *)function_ptrs[count++]; + initValueArray = (initValueArray_t *)function_ptrs[count++]; + writeValueArray = (writeValueArray_t *)function_ptrs[count++]; + freeValueArray = (freeValueArray_t *)function_ptrs[count++]; + dictSet = (dictSet_t *)function_ptrs[count++]; + dictGet = (dictGet_t *)function_ptrs[count++]; + dictDelete = (dictDelete_t *)function_ptrs[count++]; + setGet = (setGet_t *)function_ptrs[count++]; + setInsert = (setInsert_t *)function_ptrs[count++]; + setDelete = (setDelete_t *)function_ptrs[count++]; + valueToString = (valueToString_t *)function_ptrs[count++]; + valueTypeToString = (valueTypeToString_t *)function_ptrs[count++]; + printValue = (printValue_t *)function_ptrs[count++]; + printValueError = (printValueError_t *)function_ptrs[count++]; + compareStringLess = (compareStringLess_t *)function_ptrs[count++]; + compareStringGreater = (compareStringGreater_t *)function_ptrs[count++]; + defineNative = (defineNative_t *)function_ptrs[count++]; + defineNativeProperty = (defineNativeProperty_t *)function_ptrs[count++]; + reallocate = (reallocate_t *)function_ptrs[count++]; + int initResult = dictu_ffi_init(vm, methodTable); + if (initResult > 0) + return 3 + initResult; + return 0; +} +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/include/dictu_include.h b/src/include/dictu_include.h index 256fde3d..273ff8ba 100644 --- a/src/include/dictu_include.h +++ b/src/include/dictu_include.h @@ -4,7 +4,7 @@ #include #define DICTU_MAJOR_VERSION "0" -#define DICTU_MINOR_VERSION "29" +#define DICTU_MINOR_VERSION "30" #define DICTU_PATCH_VERSION "0" #define DICTU_STRING_VERSION "Dictu Version: " DICTU_MAJOR_VERSION "." DICTU_MINOR_VERSION "." DICTU_PATCH_VERSION "\n" diff --git a/src/optionals/arch.h b/src/optionals/arch.h new file mode 100644 index 00000000..ab0eebed --- /dev/null +++ b/src/optionals/arch.h @@ -0,0 +1,58 @@ +#ifndef dictu_system_arch_h +#define dictu_system_arch_h + +// https://stackoverflow.com/a/66249936 + +#if defined(__x86_64__) || defined(_M_X64) +#define SYSTEM_ARCH "x86_64" +#elif defined(i386) || defined(__i386__) || defined(__i386) || defined(_M_IX86) +#define SYSTEM_ARCH "x86_32" +#elif defined(__ARM_ARCH_2__) +#define SYSTEM_ARCH "arm2" +#elif defined(__ARM_ARCH_3__) || defined(__ARM_ARCH_3M__) +#define SYSTEM_ARCH "arm3" +#elif defined(__ARM_ARCH_4T__) || defined(__TARGET_ARM_4T) +#define SYSTEM_ARCH "arm4t" +#elif defined(__ARM_ARCH_5_) || defined(__ARM_ARCH_5E_) +#define SYSTEM_ARCH "arm6" +#elif defined(__ARM_ARCH_6T2_) || defined(__ARM_ARCH_6T2_) +#define SYSTEM_ARCH "arm6t2" +#elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || \ + defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || \ + defined(__ARM_ARCH_6ZK__) +#define SYSTEM_ARCH "arm6" +#elif defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || \ + defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || \ + defined(__ARM_ARCH_7S__) +#define SYSTEM_ARCH "arm7" +#elif defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) || \ + defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) +#define SYSTEM_ARCH "arm7a" +#elif defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || \ + defined(__ARM_ARCH_7S__) +#define SYSTEM_ARCH "arm7r" +#elif defined(__ARM_ARCH_7M__) +#define SYSTEM_ARCH "arm7m" +#elif defined(__ARM_ARCH_7S__) +#define SYSTEM_ARCH "arm7s" +#elif defined(__aarch64__) || defined(_M_ARM64) +#define SYSTEM_ARCH "arm64" +#elif defined(mips) || defined(__mips__) || defined(__mips) +#define SYSTEM_ARCH "mips" +#elif defined(__sh__) +#define SYSTEM_ARCH "superh" +#elif defined(__powerpc) || defined(__powerpc__) || defined(__powerpc64__) || \ + defined(__POWERPC__) || defined(__ppc__) || defined(__PPC__) || \ + defined(_ARCH_PPC) +#define SYSTEM_ARCH "powerpc" +#elif defined(__PPC64__) || defined(__ppc64__) || defined(_ARCH_PPC64) +#define SYSTEM_ARCH "powerpc_x64" +#elif defined(__sparc__) || defined(__sparc) +#define SYSTEM_ARCH "spark" +#elif defined(__m68k__) +#define SYSTEM_ARCH "m68k" +#else +#define SYSTEM_ARCH "unknown" +#endif + +#endif \ No newline at end of file diff --git a/src/optionals/buffer.c b/src/optionals/buffer.c new file mode 100644 index 00000000..10156939 --- /dev/null +++ b/src/optionals/buffer.c @@ -0,0 +1,1047 @@ +#include "buffer.h" + +typedef struct { + uint8_t *bytes; + int size; + bool bigEndian; +} Buffer; + +#define AS_BUFFER(v) ((Buffer *)AS_ABSTRACT(v)->data) + +ObjAbstract *newBufferObj(DictuVM *vm, double capacity); + +void freeBuffer(DictuVM *vm, ObjAbstract *abstract) { + Buffer *buffer = (Buffer *)abstract->data; + FREE_ARRAY(vm, uint8_t, buffer->bytes, buffer->size); + FREE(vm, Buffer, abstract->data); +} + +char *bufferToString(ObjAbstract *abstract) { + UNUSED(abstract); + + char *bufferString = malloc(sizeof(char) * 9); + snprintf(bufferString, 9, ""); + return bufferString; +} + +uint8_t *swap(uint8_t *ptr, size_t len, bool bigEndian) { + if (len < 2) + return ptr; + if (!bigEndian && !IS_BIG_ENDIAN) { + return ptr; + } else if (IS_BIG_ENDIAN && bigEndian) { + return ptr; + } + int start = 0; + int end = (len)-1; + uint8_t temp; + while (start < end) { + temp = ptr[start]; + ptr[start] = ptr[end]; + ptr[end] = temp; + start++; + end--; + } + return ptr; +} + +bool ensureSize(Buffer *buffer, size_t offset, size_t size) { + return buffer->size - offset >= size; +} + +bool writeInternal(Buffer *buffer, size_t offset, uint8_t *data, size_t len) { + if (!ensureSize(buffer, offset, len)) + return false; + memcpy(buffer->bytes + offset, data, len); + return true; +} + +uint8_t *getReadPtr(Buffer *buffer, size_t offset, size_t len) { + if (!ensureSize(buffer, offset, len)) + return NULL; + return buffer->bytes + offset; +} + +static Value bufferResize(DictuVM *vm, int argCount, Value *args) { + if (argCount != 1) { + runtimeError(vm, "resize() takes 1 argument (%d given).", argCount); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, "resize() size argument must be a number"); + return EMPTY_VAL; + } + + double capacity = AS_NUMBER(args[1]); + if (capacity <= 0 || capacity >= BUFFER_SIZE_MAX) { + return newResultError( + vm, "size must be greater than 0 and smaller than 2147483647"); + } + Buffer *buffer = AS_BUFFER(args[0]); + buffer->bytes = reallocate(vm, buffer->bytes, buffer->size, capacity); + if (capacity > buffer->size) { + // 0 init everything if we grew the buffer + size_t added = capacity - buffer->size; + memset(buffer->bytes + buffer->size, 0, added); + } + buffer->size = capacity; + return newResultSuccess(vm, args[0]); +} + +static Value bufferLen(DictuVM *vm, int argCount, Value *args) { + if (argCount != 0) { + runtimeError(vm, "len() takes no arguments"); + return EMPTY_VAL; + } + Buffer *buffer = AS_BUFFER(args[0]); + + return NUMBER_VAL(buffer->size); +} + +static Value bufferValues(DictuVM *vm, int argCount, Value *args) { + if (argCount != 0) { + runtimeError(vm, "values() takes no arguments"); + return EMPTY_VAL; + } + Buffer *buffer = AS_BUFFER(args[0]); + ObjList *list = newList(vm); + push(vm, OBJ_VAL(list)); + + for (int i = 0; i < buffer->size; ++i) { + writeValueArray(vm, &list->values, NUMBER_VAL(buffer->bytes[i])); + } + pop(vm); + return OBJ_VAL(list); +} + +static Value bufferString(DictuVM *vm, int argCount, Value *args) { + if (argCount != 0) { + runtimeError(vm, "string() takes no arguments"); + return EMPTY_VAL; + } + Buffer *buffer = AS_BUFFER(args[0]); + + return OBJ_VAL(copyString(vm, (const char *)buffer->bytes, buffer->size)); +} +static Value bufferWriteint8(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + if (argCount != 2) { + runtimeError(vm, "writeInt8() takes 2 argument"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, + "writeInt8() index argument must be a number"); + return EMPTY_VAL; + } + if (!IS_NUMBER(args[2])) { + runtimeError(vm, + "writeInt8() value argument must be a number"); + return EMPTY_VAL; + } + double index = AS_NUMBER(args[1]); + double value = AS_NUMBER(args[2]); + + int8_t correctVal = (int8_t)value; + + if (!writeInternal( + buffer, index, + swap((uint8_t *)&correctVal, sizeof(correctVal), buffer->bigEndian), + sizeof(correctVal))) + return newResultError(vm, "index must be smaller than buffer size - 1"); + return newResultSuccess(vm, NUMBER_VAL(correctVal)); +} +static Value bufferWriteUint16LE(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + if (argCount != 2) { + runtimeError(vm, buffer->bigEndian + ? "writeUInt16BE() takes 2 argument" + : "writeUInt16LE() takes 2 argument"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, + buffer->bigEndian + ? "writeUInt16BE() index argument must be a number" + : "writeUInt16LE() index argument must be a number"); + return EMPTY_VAL; + } + if (!IS_NUMBER(args[2])) { + runtimeError(vm, + buffer->bigEndian + ? "writeUInt16BE() value argument must be a number" + : "writeUInt16LE() value argument must be a number"); + return EMPTY_VAL; + } + double index = AS_NUMBER(args[1]); + double value = AS_NUMBER(args[2]); + + uint16_t correctVal = (uint16_t)value; + + if (!writeInternal( + buffer, index, + swap((uint8_t *)&correctVal, sizeof(correctVal), buffer->bigEndian), + sizeof(correctVal))) + return newResultError(vm, "index must be smaller than buffer size - 2"); + return newResultSuccess(vm, NUMBER_VAL(correctVal)); +} + +static Value bufferWriteUint32LE(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + if (argCount != 2) { + runtimeError(vm, buffer->bigEndian + ? "writeUInt32BE() takes 2 argument" + : "writeUInt32LE() takes 2 argument"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, + buffer->bigEndian + ? "writeUInt32BE() index argument must be a number" + : "writeUInt32LE() index argument must be a number"); + return EMPTY_VAL; + } + if (!IS_NUMBER(args[2])) { + runtimeError(vm, + buffer->bigEndian + ? "writeUInt32BE() value argument must be a number" + : "writeUInt32LE() value argument must be a number"); + return EMPTY_VAL; + } + double index = AS_NUMBER(args[1]); + double value = AS_NUMBER(args[2]); + + uint32_t correctVal = (uint32_t)value; + if (!writeInternal( + buffer, index, + swap((uint8_t *)&correctVal, sizeof(correctVal), buffer->bigEndian), + sizeof(correctVal))) + return newResultError(vm, "index must be smaller than buffer size - 4"); + return newResultSuccess(vm, NUMBER_VAL(correctVal)); +} + +static Value bufferWriteUint64LE(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + if (argCount != 2) { + runtimeError(vm, buffer->bigEndian + ? "writeUInt64BE() takes 2 argument" + : "writeUInt64LE() takes 2 argument"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, + buffer->bigEndian + ? "writeUInt64BE() index argument must be a number" + : "writeUInt64LE() index argument must be a number"); + return EMPTY_VAL; + } + if (!IS_NUMBER(args[2])) { + runtimeError(vm, + buffer->bigEndian + ? "writeUInt64BE() value argument must be a number" + : "writeUInt64LE() value argument must be a number"); + return EMPTY_VAL; + } + double index = AS_NUMBER(args[1]); + double value = AS_NUMBER(args[2]); + + uint64_t correctVal = (uint64_t)value; + if (!writeInternal( + buffer, index, + swap((uint8_t *)&correctVal, sizeof(correctVal), buffer->bigEndian), + sizeof(correctVal))) + return newResultError(vm, "index must be smaller than buffer size - 8"); + return newResultSuccess(vm, NUMBER_VAL(correctVal)); +} + +static Value bufferWriteint64LE(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + if (argCount != 2) { + runtimeError(vm, buffer->bigEndian ? "writeInt64BE() takes 2 argument" + : "writeInt64LE() takes 2 argument"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, + buffer->bigEndian + ? "writeInt64BE() index argument must be a number" + : "writeInt64LE() index argument must be a number"); + return EMPTY_VAL; + } + if (!IS_NUMBER(args[2])) { + runtimeError(vm, + buffer->bigEndian + ? "writeInt64BE() value argument must be a number" + : "writeInt64LE() value argument must be a number"); + return EMPTY_VAL; + } + double index = AS_NUMBER(args[1]); + double value = AS_NUMBER(args[2]); + int64_t correctVal = (int64_t)value; + if (!writeInternal( + buffer, index, + swap((uint8_t *)&correctVal, sizeof(correctVal), buffer->bigEndian), + sizeof(correctVal))) + return newResultError(vm, "index must be smaller than buffer size - 8"); + return newResultSuccess(vm, NUMBER_VAL(correctVal)); +} + +static Value bufferWriteint32LE(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + if (argCount != 2) { + runtimeError(vm, buffer->bigEndian ? "writeInt32BE() takes 2 argument" + : "writeInt32LE() takes 2 argument"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, + buffer->bigEndian + ? "writeInt32BE() index argument must be a number" + : "writeInt32LE() index argument must be a number"); + return EMPTY_VAL; + } + if (!IS_NUMBER(args[2])) { + runtimeError(vm, + buffer->bigEndian + ? "writeInt32BE() value argument must be a number" + : "writeInt32LE() value argument must be a number"); + return EMPTY_VAL; + } + double index = AS_NUMBER(args[1]); + double value = AS_NUMBER(args[2]); + + int32_t correctVal = (int32_t)value; + if (!writeInternal( + buffer, index, + swap((uint8_t *)&correctVal, sizeof(correctVal), buffer->bigEndian), + sizeof(correctVal))) + return newResultError(vm, "index must be smaller than buffer size - 4"); + return newResultSuccess(vm, NUMBER_VAL(correctVal)); +} + +static Value bufferWriteint16LE(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + if (argCount != 2) { + runtimeError(vm, buffer->bigEndian ? "writeInt16BE() takes 2 argument" + : "writeInt16LE() takes 2 argument"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, + buffer->bigEndian + ? "writeInt16BE() index argument must be a number" + : "writeInt16LE() index argument must be a number"); + return EMPTY_VAL; + } + if (!IS_NUMBER(args[2])) { + runtimeError(vm, + buffer->bigEndian + ? "writeInt16BE() value argument must be a number" + : "writeInt16LE() value argument must be a number"); + return EMPTY_VAL; + } + double index = AS_NUMBER(args[1]); + double value = AS_NUMBER(args[2]); + + int16_t correctVal = (int16_t)value; + if (!writeInternal( + buffer, index, + swap((uint8_t *)&correctVal, sizeof(correctVal), buffer->bigEndian), + sizeof(correctVal))) + return newResultError(vm, "index must be smaller than buffer size - 2"); + return newResultSuccess(vm, NUMBER_VAL(correctVal)); +} + +static Value bufferWritefloat32LE(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + if (argCount != 2) { + runtimeError(vm, buffer->bigEndian ? "writeFloatBE() takes 2 argument" + : "writeFloatLE() takes 2 argument"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, + buffer->bigEndian + ? "writeFloatBE() index argument must be a number" + : "writeFloatLE() index argument must be a number"); + return EMPTY_VAL; + } + if (!IS_NUMBER(args[2])) { + runtimeError(vm, + buffer->bigEndian + ? "writeFloatBE() value argument must be a number" + : "writeFloatLE() value argument must be a number"); + return EMPTY_VAL; + } + double index = AS_NUMBER(args[1]); + double value = AS_NUMBER(args[2]); + + float correctVal = (float)value; + if (!writeInternal( + buffer, index, + swap((uint8_t *)&correctVal, sizeof(correctVal), buffer->bigEndian), + sizeof(correctVal))) + return newResultError(vm, "index must be smaller than buffer size - 4"); + return newResultSuccess(vm, NUMBER_VAL(correctVal)); +} + +static Value bufferWritefloat64LE(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + if (argCount != 2) { + runtimeError(vm, buffer->bigEndian + ? "writeDoubleBE() takes 2 argument" + : "writeDoubleLE() takes 2 argument"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, + buffer->bigEndian + ? "writeDoubleBE() index argument must be a number" + : "writeDoubleLE() index argument must be a number"); + return EMPTY_VAL; + } + if (!IS_NUMBER(args[2])) { + runtimeError(vm, + buffer->bigEndian + ? "writeDoubleBE() value argument must be a number" + : "writeDoubleLE() value argument must be a number"); + return EMPTY_VAL; + } + double index = AS_NUMBER(args[1]); + double value = AS_NUMBER(args[2]); + + double correctVal = value; + + if (!writeInternal( + buffer, index, + swap((uint8_t *)&correctVal, sizeof(correctVal), buffer->bigEndian), + sizeof(correctVal))) + return newResultError(vm, "index must be smaller than buffer size - 8"); + return newResultSuccess(vm, NUMBER_VAL(correctVal)); +} + +static Value bufferReadfloat64LE(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + if (argCount != 1) { + runtimeError(vm, buffer->bigEndian ? "readDoubleBE() takes 1 argument" + : "readDoubleLE() takes 1 argument"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, + buffer->bigEndian + ? "readDoubleBE() index argument must be a number" + : "readDoubleLE() index argument must be a number"); + return EMPTY_VAL; + } + double index = AS_NUMBER(args[1]); + double value; + + uint8_t *ptr = getReadPtr(buffer, index, sizeof(value)); + if (ptr == NULL) + return newResultError(vm, "index must be smaller than buffer size - 8"); + memcpy(&value, ptr, sizeof(value)); + swap((uint8_t *)&value, sizeof(value), buffer->bigEndian); + + return newResultSuccess(vm, NUMBER_VAL(value)); +} + +static Value bufferReadfloat32LE(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + if (argCount != 1) { + runtimeError(vm, buffer->bigEndian ? "readFloatBE() takes 1 argument" + : "readFloatLE() takes 1 argument"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, buffer->bigEndian + ? "readFloatBE() index argument must be a number" + : "readFloatLE() index argument must be a number"); + return EMPTY_VAL; + } + double index = AS_NUMBER(args[1]); + float value; + + uint8_t *ptr = getReadPtr(buffer, index, sizeof(value)); + if (ptr == NULL) + return newResultError(vm, "index must be smaller than buffer size - 4"); + memcpy(&value, ptr, sizeof(value)); + swap((uint8_t *)&value, sizeof(value), buffer->bigEndian); + return newResultSuccess(vm, NUMBER_VAL(value)); +} + +static Value bufferReadUint64LE(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + if (argCount != 1) { + runtimeError(vm, buffer->bigEndian ? "readUInt64BE() takes 1 argument" + : "readUInt64LE() takes 1 argument"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, + buffer->bigEndian + ? "readUInt64BE() index argument must be a number" + : "readUInt64LE() index argument must be a number"); + return EMPTY_VAL; + } + double index = AS_NUMBER(args[1]); + uint64_t value; + + uint8_t *ptr = getReadPtr(buffer, index, sizeof(value)); + if (ptr == NULL) + return newResultError(vm, "index must be smaller than buffer size - 8"); + memcpy(&value, ptr, sizeof(value)); + swap((uint8_t *)&value, sizeof(value), buffer->bigEndian); + + // Above this value theres no guarantee that the integer value is correctly represented, + // so if are above that we don't allow it, + const uint64_t MAX_VALUE = 9007199254740992; + if (value > MAX_VALUE){ + return newResultError(vm, + "value too large for internal representation"); + } + return newResultSuccess(vm, NUMBER_VAL(value)); +} + +static Value bufferReadUint32LE(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + if (argCount != 1) { + runtimeError(vm, buffer->bigEndian ? "readUInt32BE() takes 1 argument" + : "readUInt32LE() takes 1 argument"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, + buffer->bigEndian + ? "readUInt32BE() index argument must be a number" + : "readUInt32LE() index argument must be a number"); + return EMPTY_VAL; + } + double index = AS_NUMBER(args[1]); + uint32_t value; + uint8_t *ptr = getReadPtr(buffer, index, sizeof(value)); + if (ptr == NULL) + return newResultError(vm, "index must be smaller than buffer size - 4"); + memcpy(&value, ptr, sizeof(value)); + swap((uint8_t *)&value, sizeof(value), buffer->bigEndian); + return newResultSuccess(vm, NUMBER_VAL(value)); +} + +static Value bufferReadUint16LE(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + if (argCount != 1) { + runtimeError(vm, buffer->bigEndian ? "readUInt16BE() takes 1 argument" + : "readUInt16LE() takes 1 argument"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, + buffer->bigEndian + ? "readUInt16BE() index argument must be a number" + : "readUInt16LE() index argument must be a number"); + return EMPTY_VAL; + } + double index = AS_NUMBER(args[1]); + + uint16_t value; + uint8_t *ptr = getReadPtr(buffer, index, sizeof(value)); + if (ptr == NULL) + return newResultError(vm, "index must be smaller than buffer size - 2"); + memcpy(&value, ptr, sizeof(value)); + swap((uint8_t *)&value, sizeof(value), buffer->bigEndian); + return newResultSuccess(vm, NUMBER_VAL(value)); +} + +static Value bufferReadint64LE(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + if (argCount != 1) { + runtimeError(vm, buffer->bigEndian ? "readInt64BE() takes 1 argument" + : "readInt64LE() takes 1 argument"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, buffer->bigEndian + ? "readInt64BE() index argument must be a number" + : "readInt64LE() index argument must be a number"); + return EMPTY_VAL; + } + double index = AS_NUMBER(args[1]); + int64_t value; + uint8_t *ptr = getReadPtr(buffer, index, sizeof(value)); + if (ptr == NULL) + return newResultError(vm, "index must be smaller than buffer size - 8"); + memcpy(&value, ptr, sizeof(value)); + + swap((uint8_t *)&value, sizeof(value), buffer->bigEndian); + // Above this value theres no guarantee that the integer value is correctly represented, + // so if are above that we don't allow it, + const int64_t MAX_VALUE = 9007199254740992; + if (value > MAX_VALUE || value < -MAX_VALUE){ + return newResultError(vm, + "value too large for internal representation"); + } + return newResultSuccess(vm, NUMBER_VAL(value)); +} + +static Value bufferReadint32LE(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + if (argCount != 1) { + runtimeError(vm, buffer->bigEndian ? "readInt32BE() takes 1 argument" + : "readInt32LE() takes 1 argument"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, buffer->bigEndian + ? "readInt32BE() index argument must be a number" + : "readInt32LE() index argument must be a number"); + return EMPTY_VAL; + } + double index = AS_NUMBER(args[1]); + + int32_t value; + uint8_t *ptr = getReadPtr(buffer, index, sizeof(value)); + if (ptr == NULL) + return newResultError(vm, "index must be smaller than buffer size - 4"); + memcpy(&value, ptr, sizeof(value)); + swap((uint8_t *)&value, sizeof(value), buffer->bigEndian); + return newResultSuccess(vm, NUMBER_VAL(value)); +} + +static Value bufferReadint16LE(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + if (argCount != 1) { + runtimeError(vm, buffer->bigEndian ? "readInt16BE() takes 1 argument" + : "readInt16LE() takes 1 argument"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, buffer->bigEndian + ? "readInt16BE() index argument must be a number" + : "readInt16LE() index argument must be a number"); + return EMPTY_VAL; + } + double index = AS_NUMBER(args[1]); + + int16_t value; + uint8_t *ptr = getReadPtr(buffer, index, sizeof(value)); + if (ptr == NULL) + return newResultError(vm, "index must be smaller than buffer size - 2"); + memcpy(&value, ptr, sizeof(value)); + swap((uint8_t *)&value, sizeof(value), buffer->bigEndian); + return newResultSuccess(vm, NUMBER_VAL(value)); +} + +static Value bufferReadint8(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + if (argCount != 1) { + runtimeError(vm, "readInt8() takes 1 argument"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, "readInt8() index argument must be a number"); + return EMPTY_VAL; + } + double index = AS_NUMBER(args[1]); + + int8_t value; + uint8_t *ptr = getReadPtr(buffer, index, sizeof(value)); + if (ptr == NULL) + return newResultError(vm, "index must be smaller than buffer size - 1"); + memcpy(&value, ptr, sizeof(value)); + swap((uint8_t *)&value, sizeof(value), buffer->bigEndian); + return newResultSuccess(vm, NUMBER_VAL(value)); +} + +typedef Value buffer_func_t(DictuVM *vm, int argCount, Value *args); +// is this hacky? +static Value runBigEndian(DictuVM *vm, int argCount, Value *args, + buffer_func_t *f) { + Buffer *buffer = AS_BUFFER(args[0]); + buffer->bigEndian = true; + Value result = f(vm, argCount, args); + buffer->bigEndian = false; + return result; +} + +static Value bufferReadUint64BE(DictuVM *vm, int argCount, Value *args) { + return runBigEndian(vm, argCount, args, &bufferReadUint64LE); +} + +static Value bufferReadUint32BE(DictuVM *vm, int argCount, Value *args) { + return runBigEndian(vm, argCount, args, &bufferReadUint32LE); +} + +static Value bufferReadUint16BE(DictuVM *vm, int argCount, Value *args) { + return runBigEndian(vm, argCount, args, &bufferReadUint16LE); +} + +static Value bufferReadint64BE(DictuVM *vm, int argCount, Value *args) { + return runBigEndian(vm, argCount, args, &bufferReadint64LE); +} + +static Value bufferReadint32BE(DictuVM *vm, int argCount, Value *args) { + return runBigEndian(vm, argCount, args, &bufferReadint32LE); +} + +static Value bufferReadint16BE(DictuVM *vm, int argCount, Value *args) { + return runBigEndian(vm, argCount, args, &bufferReadint16LE); +} + +static Value bufferReadfloat32BE(DictuVM *vm, int argCount, Value *args) { + return runBigEndian(vm, argCount, args, &bufferReadfloat32LE); +} + +static Value bufferReadfloat64BE(DictuVM *vm, int argCount, Value *args) { + return runBigEndian(vm, argCount, args, &bufferReadfloat64LE); +} + +static Value bufferWriteUint64BE(DictuVM *vm, int argCount, Value *args) { + return runBigEndian(vm, argCount, args, &bufferWriteUint64LE); +} + +static Value bufferWriteUint32BE(DictuVM *vm, int argCount, Value *args) { + return runBigEndian(vm, argCount, args, &bufferWriteUint32LE); +} + +static Value bufferWriteUint16BE(DictuVM *vm, int argCount, Value *args) { + return runBigEndian(vm, argCount, args, &bufferWriteUint16LE); +} + +static Value bufferWriteint64BE(DictuVM *vm, int argCount, Value *args) { + return runBigEndian(vm, argCount, args, &bufferWriteint64LE); +} + +static Value bufferWriteint32BE(DictuVM *vm, int argCount, Value *args) { + return runBigEndian(vm, argCount, args, &bufferWriteint32LE); +} + +static Value bufferWriteint16BE(DictuVM *vm, int argCount, Value *args) { + return runBigEndian(vm, argCount, args, &bufferWriteint16LE); +} + +static Value bufferWritefloat32BE(DictuVM *vm, int argCount, Value *args) { + return runBigEndian(vm, argCount, args, &bufferWritefloat32LE); +} + +static Value bufferWritefloat64BE(DictuVM *vm, int argCount, Value *args) { + return runBigEndian(vm, argCount, args, &bufferWritefloat64LE); +} + +static Value bufferGet(DictuVM *vm, int argCount, Value *args) { + if (argCount != 1) { + runtimeError(vm, "get() takes 1 argument (%d given).", argCount); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, "get() argument must be a number"); + return EMPTY_VAL; + } + + double index = AS_NUMBER(args[1]); + if (index < 0) { + return newResultError(vm, "index must be greater than -1"); + } + Buffer *buffer = AS_BUFFER(args[0]); + if (index >= buffer->size) { + return newResultError(vm, "index must be smaller than buffer size"); + } + + return newResultSuccess(vm, NUMBER_VAL(buffer->bytes[(size_t)index])); +} + +static Value bufferSet(DictuVM *vm, int argCount, Value *args) { + if (argCount != 2) { + runtimeError(vm, "set() takes 2 argument (%d given).", argCount); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, "set() index argument must be a number"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[2])) { + runtimeError(vm, "set() value argument must be a number"); + return EMPTY_VAL; + } + + double index = AS_NUMBER(args[1]); + double value = AS_NUMBER(args[2]); + if (index < 0) { + return newResultError(vm, "index must be greater than -1"); + } + Buffer *buffer = AS_BUFFER(args[0]); + if (index >= buffer->size) { + return newResultError(vm, "index must be smaller than buffer size"); + } + + buffer->bytes[(size_t)index] = (uint8_t)value; + + return newResultSuccess(vm, NUMBER_VAL(buffer->bytes[(size_t)index])); +} + +static Value bufferWriteString(DictuVM *vm, int argCount, Value *args) { + if (argCount != 2) { + runtimeError(vm, "writeString() takes 2 argument (%d given).", + argCount); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, "writeString() index argument must be a number"); + return EMPTY_VAL; + } + + if (!IS_STRING(args[2])) { + runtimeError(vm, "writeString() value argument must be a string"); + return EMPTY_VAL; + } + + double index = AS_NUMBER(args[1]); + ObjString *str = AS_STRING(args[2]); + if (index < 0) { + return newResultError(vm, "index must be greater than -1"); + } + Buffer *buffer = AS_BUFFER(args[0]); + if (index >= buffer->size) { + return newResultError(vm, "index must be smaller than buffer size"); + } + + if (buffer->size - index < str->length) { + return newResultError(vm, + "buffer is not large enough to fit the string"); + } + memcpy(buffer->bytes + (size_t)index, str->chars, str->length); + return newResultSuccess(vm, NIL_VAL); +} + +static Value bufferReadString(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + size_t start = 0; + size_t end = buffer->size; + int length = buffer->size; + if (argCount > 0) { + if (!IS_NUMBER(args[1])) { + runtimeError(vm, "readString() start argument must be a number"); + return EMPTY_VAL; + } + double startParam = AS_NUMBER(args[1]); + if (startParam >= buffer->size) { + return newResultError(vm, + "start greater or equals than buffer length"); + } else { + start = startParam; + length = end - start; + } + } + if (argCount == 2) { + if (!IS_NUMBER(args[2])) { + runtimeError(vm, "readString() end argument must be a number"); + return EMPTY_VAL; + } + double endParam = AS_NUMBER(args[2]); + if (endParam > buffer->size) { + return newResultError(vm, "end greater than buffer length"); + } else { + end = endParam; + length = end - start; + } + } + if (length <= 0) { + return newResultError(vm, "string length is 0"); + } + return newResultSuccess( + vm, + OBJ_VAL(copyString(vm, (const char *)buffer->bytes + start, length))); +} + +static Value bufferSubArray(DictuVM *vm, int argCount, Value *args) { + Buffer *buffer = AS_BUFFER(args[0]); + size_t start = 0; + size_t end = buffer->size; + int length = buffer->size; + if (argCount > 0) { + if (!IS_NUMBER(args[1])) { + runtimeError(vm, "subarray() start argument must be a number"); + return EMPTY_VAL; + } + double startParam = AS_NUMBER(args[1]); + if (startParam >= buffer->size) { + return newResultError(vm, + "start greater or equals than buffer length"); + } else { + start = startParam; + length = end - start; + } + } + if (argCount == 2) { + if (!IS_NUMBER(args[2])) { + runtimeError(vm, "subarray() end argument must be a number"); + return EMPTY_VAL; + } + double endParam = AS_NUMBER(args[2]); + if (endParam > buffer->size) { + return newResultError(vm, "end greater than buffer length"); + } else { + end = endParam; + length = end - start; + } + } + if (length <= 0) { + return newResultError(vm, "array length is 0"); + } + ObjAbstract *newBuffer = newBufferObj(vm, length); + Buffer *nb = (Buffer *)newBuffer->data; + for (int i = 0; i < length; i++) { + nb->bytes[i] = buffer->bytes[start + i]; + } + return newResultSuccess(vm, OBJ_VAL(newBuffer)); +} + +ObjAbstract *newBufferObj(DictuVM *vm, double capacity) { + ObjAbstract *abstract = newAbstract(vm, freeBuffer, bufferToString); + push(vm, OBJ_VAL(abstract)); + + Buffer *buffer = ALLOCATE(vm, Buffer, 1); + buffer->bigEndian = false; + buffer->bytes = ALLOCATE(vm, uint8_t, capacity); + memset(buffer->bytes, 0, capacity); + buffer->size = capacity; + + /** + * Setup Buffer object methods + */ + defineNative(vm, &abstract->values, "resize", bufferResize); + defineNative(vm, &abstract->values, "set", bufferSet); + defineNative(vm, &abstract->values, "get", bufferGet); + defineNative(vm, &abstract->values, "subarray", bufferSubArray); + defineNative(vm, &abstract->values, "string", bufferString); + defineNative(vm, &abstract->values, "len", bufferLen); + defineNative(vm, &abstract->values, "values", bufferValues); + + defineNative(vm, &abstract->values, "writeString", bufferWriteString); + defineNative(vm, &abstract->values, "readString", bufferReadString); + + defineNative(vm, &abstract->values, "readUInt64LE", bufferReadUint64LE); + defineNative(vm, &abstract->values, "readUInt32LE", bufferReadUint32LE); + defineNative(vm, &abstract->values, "readUInt16LE", bufferReadUint16LE); + defineNative(vm, &abstract->values, "readInt64LE", bufferReadint64LE); + defineNative(vm, &abstract->values, "readInt32LE", bufferReadint32LE); + defineNative(vm, &abstract->values, "readInt16LE", bufferReadint16LE); + defineNative(vm, &abstract->values, "readInt8", bufferReadint8); + + defineNative(vm, &abstract->values, "readFloatLE", bufferReadfloat32LE); + defineNative(vm, &abstract->values, "readDoubleLE", bufferReadfloat64LE); + + defineNative(vm, &abstract->values, "writeUInt64LE", bufferWriteUint64LE); + defineNative(vm, &abstract->values, "writeUInt32LE", bufferWriteUint32LE); + defineNative(vm, &abstract->values, "writeUInt16LE", bufferWriteUint16LE); + defineNative(vm, &abstract->values, "writeInt64LE", bufferWriteint64LE); + defineNative(vm, &abstract->values, "writeInt32LE", bufferWriteint32LE); + defineNative(vm, &abstract->values, "writeInt16LE", bufferWriteint16LE); + defineNative(vm, &abstract->values, "writeInt8", bufferWriteint8); + + defineNative(vm, &abstract->values, "writeFloatLE", bufferWritefloat32LE); + defineNative(vm, &abstract->values, "writeDoubleLE", bufferWritefloat64LE); + + defineNative(vm, &abstract->values, "readUInt64BE", bufferReadUint64BE); + defineNative(vm, &abstract->values, "readUInt32BE", bufferReadUint32BE); + defineNative(vm, &abstract->values, "readUInt16BE", bufferReadUint16BE); + defineNative(vm, &abstract->values, "readInt64BE", bufferReadint64BE); + defineNative(vm, &abstract->values, "readInt32BE", bufferReadint32BE); + defineNative(vm, &abstract->values, "readInt16BE", bufferReadint16BE); + + defineNative(vm, &abstract->values, "readFloatBE", bufferReadfloat32BE); + defineNative(vm, &abstract->values, "readDoubleBE", bufferReadfloat64BE); + + defineNative(vm, &abstract->values, "writeUInt64BE", bufferWriteUint64BE); + defineNative(vm, &abstract->values, "writeUInt32BE", bufferWriteUint32BE); + defineNative(vm, &abstract->values, "writeUInt16BE", bufferWriteUint16BE); + defineNative(vm, &abstract->values, "writeInt64BE", bufferWriteint64BE); + defineNative(vm, &abstract->values, "writeInt32BE", bufferWriteint32BE); + defineNative(vm, &abstract->values, "writeInt16BE", bufferWriteint16BE); + + defineNative(vm, &abstract->values, "writeFloatBE", bufferWritefloat32BE); + defineNative(vm, &abstract->values, "writeDoubleBE", bufferWritefloat64BE); + + abstract->data = buffer; + pop(vm); + + return abstract; +} + +static Value newBuffer(DictuVM *vm, int argCount, Value *args) { + if (argCount != 1) { + runtimeError(vm, "new() takes 1 argument (%d given).", argCount); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[0])) { + runtimeError(vm, "new() argument must be a numbers"); + return EMPTY_VAL; + } + + double capacity = AS_NUMBER(args[0]); + if (capacity <= 0 || capacity >= BUFFER_SIZE_MAX) { + return newResultError( + vm, "capacity must be greater than 0 and less than 2147483647"); + } + + return newResultSuccess(vm, OBJ_VAL(newBufferObj(vm, capacity))); +} + +static Value newBufferFromString(DictuVM *vm, int argCount, Value *args) { + if (argCount != 1) { + runtimeError(vm, "fromString() takes 1 argument (%d given).", argCount); + return EMPTY_VAL; + } + + if (!IS_STRING(args[0])) { + runtimeError(vm, "fromString() argument must be a string"); + return EMPTY_VAL; + } + + ObjString *str = AS_STRING(args[0]); + if (str->length <= 0) { + return newResultError(vm, "string length needs to be greater than 0"); + } + ObjAbstract *b = newBufferObj(vm, str->length); + Buffer *buffer = (Buffer *)b->data; + memcpy(buffer->bytes, str->chars, str->length); + return newResultSuccess(vm, OBJ_VAL(b)); +} + +Value createBufferModule(DictuVM *vm) { + ObjString *name = copyString(vm, "Buffer", 6); + push(vm, OBJ_VAL(name)); + ObjModule *module = newModule(vm, name); + push(vm, OBJ_VAL(module)); + + defineNative(vm, &module->values, "new", newBuffer); + defineNative(vm, &module->values, "fromString", newBufferFromString); + + pop(vm); + pop(vm); + + return OBJ_VAL(module); +} \ No newline at end of file diff --git a/src/optionals/buffer.h b/src/optionals/buffer.h new file mode 100644 index 00000000..9d77f06e --- /dev/null +++ b/src/optionals/buffer.h @@ -0,0 +1,14 @@ +#ifndef dictu_buffer_h +#define dictu_buffer_h + + +#include "optionals.h" +#include "../vm/vm.h" + +#define BUFFER_SIZE_MAX 2147483647 + +#define IS_BIG_ENDIAN (!*(unsigned char *)&(uint16_t){1}) + +Value createBufferModule(DictuVM *vm); + +#endif //dictu_buffer_h \ No newline at end of file diff --git a/src/optionals/c.c b/src/optionals/c.c index 1e3ecfe2..d752654b 100644 --- a/src/optionals/c.c +++ b/src/optionals/c.c @@ -19,262 +19,3 @@ void getStrerror(char *buf, int error) { strerror_r(error, buf, MAX_ERROR_LEN); #endif } - -Value createCModule(DictuVM *vm) { - ObjString *name = copyString(vm, "C", 1); - push(vm, OBJ_VAL(name)); - ObjModule *module = newModule(vm, name); - push(vm, OBJ_VAL(module)); - - /** - * Define C properties - */ - defineNativeProperty(vm, &module->values, "EPERM", NUMBER_VAL(EPERM)); - defineNativeProperty(vm, &module->values, "ENOENT", NUMBER_VAL(ENOENT)); - defineNativeProperty(vm, &module->values, "ESRCH", NUMBER_VAL(ESRCH)); - defineNativeProperty(vm, &module->values, "EINTR", NUMBER_VAL(EINTR)); - defineNativeProperty(vm, &module->values, "EIO", NUMBER_VAL(EIO)); - defineNativeProperty(vm, &module->values, "ENXIO", NUMBER_VAL(ENXIO)); - defineNativeProperty(vm, &module->values, "E2BIG", NUMBER_VAL(E2BIG)); - defineNativeProperty(vm, &module->values, "ENOEXEC",NUMBER_VAL(ENOEXEC)); - defineNativeProperty(vm, &module->values, "EAGAIN", NUMBER_VAL(EAGAIN)); - defineNativeProperty(vm, &module->values, "ENOMEM", NUMBER_VAL(ENOMEM)); - defineNativeProperty(vm, &module->values, "EACCES", NUMBER_VAL(EACCES)); - defineNativeProperty(vm, &module->values, "EFAULT", NUMBER_VAL(EFAULT)); -#ifdef ENOTBLK - defineNativeProperty(vm, &module->values, "ENOTBLK", NUMBER_VAL(ENOTBLK)); -#endif - defineNativeProperty(vm, &module->values, "EBUSY", NUMBER_VAL(EBUSY)); - defineNativeProperty(vm, &module->values, "EEXIST", NUMBER_VAL(EEXIST)); - defineNativeProperty(vm, &module->values, "EXDEV", NUMBER_VAL(EXDEV)); - defineNativeProperty(vm, &module->values, "ENODEV", NUMBER_VAL(ENODEV)); - defineNativeProperty(vm, &module->values, "ENOTDIR",NUMBER_VAL(ENOTDIR)); - defineNativeProperty(vm, &module->values, "EISDIR", NUMBER_VAL(EISDIR)); - defineNativeProperty(vm, &module->values, "EINVAL", NUMBER_VAL(EINVAL)); - defineNativeProperty(vm, &module->values, "ENFILE", NUMBER_VAL(ENFILE)); - defineNativeProperty(vm, &module->values, "EMFILE", NUMBER_VAL(EMFILE)); - defineNativeProperty(vm, &module->values, "ENOTTY", NUMBER_VAL(ENOTTY)); - defineNativeProperty(vm, &module->values, "ETXTBSY",NUMBER_VAL(ETXTBSY)); - defineNativeProperty(vm, &module->values, "EFBIG", NUMBER_VAL(EFBIG)); - defineNativeProperty(vm, &module->values, "ENOSPC", NUMBER_VAL(ENOSPC)); - defineNativeProperty(vm, &module->values, "ESPIPE", NUMBER_VAL(ESPIPE)); - defineNativeProperty(vm, &module->values, "EROFS", NUMBER_VAL(EROFS)); - defineNativeProperty(vm, &module->values, "EMLINK", NUMBER_VAL(EMLINK)); - defineNativeProperty(vm, &module->values, "EPIPE", NUMBER_VAL(EPIPE)); - defineNativeProperty(vm, &module->values, "EDOM", NUMBER_VAL(EDOM)); - defineNativeProperty(vm, &module->values, "ERANGE", NUMBER_VAL(ERANGE)); - defineNativeProperty(vm, &module->values, "EDEADLK",NUMBER_VAL(EDEADLK)); - defineNativeProperty(vm, &module->values, "ENAMETOOLONG", NUMBER_VAL(ENAMETOOLONG)); - defineNativeProperty(vm, &module->values, "ENOLCK", NUMBER_VAL(ENOLCK)); - defineNativeProperty(vm, &module->values, "ENOSYS", NUMBER_VAL(ENOSYS)); - defineNativeProperty(vm, &module->values, "ENOTEMPTY", NUMBER_VAL(ENOTEMPTY)); - defineNativeProperty(vm, &module->values, "ELOOP", NUMBER_VAL(ELOOP)); - defineNativeProperty(vm, &module->values, "EWOULDBLOCK", NUMBER_VAL(EWOULDBLOCK)); - defineNativeProperty(vm, &module->values, "ENOMSG", NUMBER_VAL(ENOMSG)); - defineNativeProperty(vm, &module->values, "EIDRM", NUMBER_VAL(EIDRM)); -#ifdef ECHRNG - defineNativeProperty(vm, &module->values, "ECHRNG", NUMBER_VAL(ECHRNG)); -#endif -#ifdef EL2NSYNC - defineNativeProperty(vm, &module->values, "EL2NSYNC", NUMBER_VAL(EL2NSYNC)); -#endif -#ifdef EL3HLT - defineNativeProperty(vm, &module->values, "EL3HLT", NUMBER_VAL(EL3HLT)); -#endif -#ifdef EL3RST - defineNativeProperty(vm, &module->values, "EL3RST", NUMBER_VAL(EL3RST)); -#endif -#ifdef ELNRNG - defineNativeProperty(vm, &module->values, "ELNRNG", NUMBER_VAL(ELNRNG)); -#endif -#ifdef EUNATCH - defineNativeProperty(vm, &module->values, "EUNATCH", NUMBER_VAL(EUNATCH)); -#endif -#ifdef ENOCSI - defineNativeProperty(vm, &module->values, "ENOCSI", NUMBER_VAL(ENOCSI)); -#endif -#ifdef EL2HLT - defineNativeProperty(vm, &module->values, "EL2HLT", NUMBER_VAL(EL2HLT)); -#endif -#ifdef EBADE - defineNativeProperty(vm, &module->values, "EBADE", NUMBER_VAL(EBADE)); -#endif -#ifdef EBADR - defineNativeProperty(vm, &module->values, "EBADR", NUMBER_VAL(EBADR)); -#endif -#ifdef EXFULL - defineNativeProperty(vm, &module->values, "EXFULL", NUMBER_VAL(EXFULL)); -#endif -#ifdef ENOANO - defineNativeProperty(vm, &module->values, "ENOANO", NUMBER_VAL(ENOANO)); -#endif -#ifdef EBADRQC - defineNativeProperty(vm, &module->values, "EBADRQC", NUMBER_VAL(EBADRQC)); -#endif -#ifdef EBADSLT - defineNativeProperty(vm, &module->values, "EBADSLT", NUMBER_VAL(EBADSLT)); -#endif -#ifdef EDEADLOCK - defineNativeProperty(vm, &module->values, "EDEADLOCK", NUMBER_VAL(EDEADLOCK)); -#endif -#ifdef EBFONT - defineNativeProperty(vm, &module->values, "EBFONT", NUMBER_VAL(EBFONT)); -#endif - defineNativeProperty(vm, &module->values, "ENOSTR", NUMBER_VAL(ENOSTR)); - defineNativeProperty(vm, &module->values, "ENODATA", NUMBER_VAL(ENODATA)); - defineNativeProperty(vm, &module->values, "ETIME", NUMBER_VAL(ETIME)); - defineNativeProperty(vm, &module->values, "ENOSR", NUMBER_VAL(ENOSR)); -#ifdef ENONET - defineNativeProperty(vm, &module->values, "ENONET", NUMBER_VAL(ENONET)); -#endif -#ifdef ENOPKG - defineNativeProperty(vm, &module->values, "ENOPKG", NUMBER_VAL(ENOPKG)); -#endif -#ifdef EREMOTE - defineNativeProperty(vm, &module->values, "EREMOTE", NUMBER_VAL(EREMOTE)); -#endif - defineNativeProperty(vm, &module->values, "ENOLINK", NUMBER_VAL(ENOLINK)); -#ifdef EADV - defineNativeProperty(vm, &module->values, "EADV", NUMBER_VAL(EADV)); -#endif -#ifdef ESRMNT - defineNativeProperty(vm, &module->values, "ESRMNT", NUMBER_VAL(ESRMNT)); -#endif -#ifdef ECOMM - defineNativeProperty(vm, &module->values, "ECOMM", NUMBER_VAL(ECOMM)); -#endif - defineNativeProperty(vm, &module->values, "EPROTO", NUMBER_VAL(EPROTO)); -#ifdef EMULTIHOP - defineNativeProperty(vm, &module->values, "EMULTIHOP", NUMBER_VAL(EMULTIHOP)); -#endif -#ifdef EDOTDOT - defineNativeProperty(vm, &module->values, "EDOTDOT", NUMBER_VAL(EDOTDOT)); -#endif - defineNativeProperty(vm, &module->values, "EBADMSG", NUMBER_VAL(EBADMSG)); - defineNativeProperty(vm, &module->values, "EOVERFLOW", NUMBER_VAL(EOVERFLOW)); -#ifdef ENOTUNIQ - defineNativeProperty(vm, &module->values, "ENOTUNIQ", NUMBER_VAL(ENOTUNIQ)); -#endif -#ifdef EBADFD - defineNativeProperty(vm, &module->values, "EBADFD", NUMBER_VAL(EBADFD)); -#endif -#ifdef EREMCHG - defineNativeProperty(vm, &module->values, "EREMCHG", NUMBER_VAL(EREMCHG)); -#endif -#ifdef ELIBACC - defineNativeProperty(vm, &module->values, "ELIBACC", NUMBER_VAL(ELIBACC)); -#endif -#ifdef ELIBBAD - defineNativeProperty(vm, &module->values, "ELIBBAD", NUMBER_VAL(ELIBBAD)); -#endif -#ifdef ELIBSCN - defineNativeProperty(vm, &module->values, "ELIBSCN", NUMBER_VAL(ELIBSCN)); -#endif -#ifdef ELIBMAX - defineNativeProperty(vm, &module->values, "ELIBMAX", NUMBER_VAL(ELIBMAX)); -#endif -#ifdef ELIBEXEC - defineNativeProperty(vm, &module->values, "ELIBEXEC", NUMBER_VAL(ELIBEXEC)); -#endif - defineNativeProperty(vm, &module->values, "EILSEQ", NUMBER_VAL(EILSEQ)); -#ifdef ERESTART - defineNativeProperty(vm, &module->values, "ERESTART", NUMBER_VAL(ERESTART)); -#endif -#ifdef ESTRPIPE - defineNativeProperty(vm, &module->values, "ESTRPIPE", NUMBER_VAL(ESTRPIPE)); -#endif -#ifdef EUSERS - defineNativeProperty(vm, &module->values, "EUSERS", NUMBER_VAL(EUSERS)); -#endif - defineNativeProperty(vm, &module->values, "ENOTSOCK", NUMBER_VAL(ENOTSOCK)); - defineNativeProperty(vm, &module->values, "EDESTADDRREQ", NUMBER_VAL(EDESTADDRREQ)); - defineNativeProperty(vm, &module->values, "EMSGSIZE", NUMBER_VAL(EMSGSIZE)); - defineNativeProperty(vm, &module->values, "EPROTOTYPE", NUMBER_VAL(EPROTOTYPE)); - defineNativeProperty(vm, &module->values, "ENOPROTOOPT", NUMBER_VAL(ENOPROTOOPT)); - defineNativeProperty(vm, &module->values, "EPROTONOSUPPORT", NUMBER_VAL(EPROTONOSUPPORT)); -#ifdef ESOCKTNOSUPPORT - defineNativeProperty(vm, &module->values, "ESOCKTNOSUPPORT", NUMBER_VAL(ESOCKTNOSUPPORT)); -#endif - defineNativeProperty(vm, &module->values, "EOPNOTSUPP", NUMBER_VAL(EOPNOTSUPP)); -#ifdef EPFNOSUPPORT - defineNativeProperty(vm, &module->values, "EPFNOSUPPORT", NUMBER_VAL(EPFNOSUPPORT)); -#endif - defineNativeProperty(vm, &module->values, "EAFNOSUPPORT", NUMBER_VAL(EAFNOSUPPORT)); - defineNativeProperty(vm, &module->values, "EADDRINUSE", NUMBER_VAL(EADDRINUSE)); - defineNativeProperty(vm, &module->values, "EADDRNOTAVAIL", NUMBER_VAL(EADDRNOTAVAIL)); - defineNativeProperty(vm, &module->values, "ENETDOWN", NUMBER_VAL(ENETDOWN)); - defineNativeProperty(vm, &module->values, "ENETUNREACH", NUMBER_VAL(ENETUNREACH)); - defineNativeProperty(vm, &module->values, "ENETRESET", NUMBER_VAL(ENETRESET)); - defineNativeProperty(vm, &module->values, "ECONNABORTED", NUMBER_VAL(ECONNABORTED)); - defineNativeProperty(vm, &module->values, "ECONNRESET", NUMBER_VAL(ECONNRESET)); - defineNativeProperty(vm, &module->values, "ENOBUFS", NUMBER_VAL(ENOBUFS)); - defineNativeProperty(vm, &module->values, "EISCONN", NUMBER_VAL(EISCONN)); - defineNativeProperty(vm, &module->values, "ENOTCONN", NUMBER_VAL(ENOTCONN)); -#ifdef ESHUTDOWN - defineNativeProperty(vm, &module->values, "ESHUTDOWN", NUMBER_VAL(ESHUTDOWN)); -#endif -#ifdef ETOOMANYREFS - defineNativeProperty(vm, &module->values, "ETOOMANYREFS", NUMBER_VAL(ETOOMANYREFS)); -#endif - defineNativeProperty(vm, &module->values, "ETIMEDOUT", NUMBER_VAL(ETIMEDOUT)); - defineNativeProperty(vm, &module->values, "ECONNREFUSED", NUMBER_VAL(ECONNREFUSED)); -#ifdef EHOSTDOWN - defineNativeProperty(vm, &module->values, "EHOSTDOWN", NUMBER_VAL(EHOSTDOWN)); -#endif - defineNativeProperty(vm, &module->values, "EHOSTUNREACH", NUMBER_VAL(EHOSTUNREACH)); - defineNativeProperty(vm, &module->values, "EALREADY", NUMBER_VAL(EALREADY)); - defineNativeProperty(vm, &module->values, "EINPROGRESS", NUMBER_VAL(EINPROGRESS)); -#ifdef ESTALE - defineNativeProperty(vm, &module->values, "ESTALE", NUMBER_VAL(ESTALE)); -#endif -#ifdef EUCLEAN - defineNativeProperty(vm, &module->values, "EUCLEAN", NUMBER_VAL(EUCLEAN)); -#endif -#ifdef ENOTNAM - defineNativeProperty(vm, &module->values, "ENOTNAM", NUMBER_VAL(ENOTNAM)); -#endif -#ifdef ENAVAIL - defineNativeProperty(vm, &module->values, "ENAVAIL", NUMBER_VAL(ENAVAIL)); -#endif -#ifdef EISNAM - defineNativeProperty(vm, &module->values, "EISNAM", NUMBER_VAL(EISNAM)); -#endif -#ifdef EREMOTEIO - defineNativeProperty(vm, &module->values, "EREMOTEIO", NUMBER_VAL(EREMOTEIO)); -#endif -#ifdef EDQUOT - defineNativeProperty(vm, &module->values, "EDQUOT", NUMBER_VAL(EDQUOT)); -#endif -#ifdef ENOMEDIUM - defineNativeProperty(vm, &module->values, "ENOMEDIUM", NUMBER_VAL(ENOMEDIUM)); -#endif -#ifdef EMEDIUMTYPE - defineNativeProperty(vm, &module->values, "EMEDIUMTYPE", NUMBER_VAL(EMEDIUMTYPE)); -#endif - defineNativeProperty(vm, &module->values, "ECANCELED", NUMBER_VAL(ECANCELED)); -#ifdef ENOKEY - defineNativeProperty(vm, &module->values, "ENOKEY", NUMBER_VAL(ENOKEY)); -#endif -#ifdef EKEYEXPIRED - defineNativeProperty(vm, &module->values, "EKEYEXPIRED", NUMBER_VAL(EKEYEXPIRED)); -#endif -#ifdef EKEYREVOKED - defineNativeProperty(vm, &module->values, "EKEYREVOKED", NUMBER_VAL(EKEYREVOKED)); -#endif -#ifdef EKEYREJECTED - defineNativeProperty(vm, &module->values, "EKEYREJECTED", NUMBER_VAL(EKEYREJECTED)); -#endif - defineNativeProperty(vm, &module->values, "EOWNERDEAD", NUMBER_VAL(EOWNERDEAD)); - defineNativeProperty(vm, &module->values, "ENOTRECOVERABLE", NUMBER_VAL(ENOTRECOVERABLE)); -#ifdef ERFKILL - defineNativeProperty(vm, &module->values, "ERFKILL", NUMBER_VAL(ERFKILL)); -#endif -#ifdef EHWPOISON - defineNativeProperty(vm, &module->values, "EHWPOISON", NUMBER_VAL(EHWPOISON)); -#endif - - pop(vm); - pop(vm); - - return OBJ_VAL(module); -} diff --git a/src/optionals/c.h b/src/optionals/c.h index 14ea5d0b..3fc9f6a0 100644 --- a/src/optionals/c.h +++ b/src/optionals/c.h @@ -39,8 +39,6 @@ #include "../vm/vm.h" #include "../vm/memory.h" -Value createCModule(DictuVM *vm); - void getStrerror(char *buf, int error); #endif //dictu_c_h diff --git a/src/optionals/ffi/ffi.c b/src/optionals/ffi/ffi.c new file mode 100644 index 00000000..90adec19 --- /dev/null +++ b/src/optionals/ffi/ffi.c @@ -0,0 +1,181 @@ +#include "ffi.h" +#ifdef _WIN32 +#include +#else +#include +#endif + +typedef struct { +#ifdef _WIN32 + HMODULE library; +#else + void *library; +#endif + char *path; +} FFIInstance; + +typedef struct _vm_external vm_external; + +typedef Value function_definition_t(DictuVM *vm, int argCount, Value *args); +typedef int init_func_definition_t(void **function_ptrs, DictuVM *vm, + Table *table, int vm_ffi_version); + + +void *ffi_function_pointers[] = {©String, + &newList, + &newDict, + &newSet, + &newFile, + &newAbstract, + &newResult, + &newResultSuccess, + &newResultError, + &push, + &peek, + &runtimeError, + &pop, + &isFalsey, + &valuesEqual, + &initValueArray, + &writeValueArray, + &freeValueArray, + &dictSet, + &dictGet, + &dictDelete, + &setGet, + &setInsert, + &setDelete, + &valueToString, + &valueTypeToString, + &printValue, + &printValueError, + &compareStringLess, + &compareStringGreater, + &defineNative, + &defineNativeProperty, + &reallocate}; + +void freeFFI(DictuVM *vm, ObjAbstract *abstract) { + FFIInstance *instance = (FFIInstance *)abstract->data; + free(instance->path); +#ifdef _WIN32 + FreeLibrary(instance->library); +#else + dlclose(instance->library); +#endif + FREE(vm, FFIInstance, abstract->data); +} + +char *ffiToString(ObjAbstract *abstract) { + UNUSED(abstract); + + char *ffiString = malloc(sizeof(char) * 11 + 3); + snprintf(ffiString, 11 + 3, ""); + return ffiString; +} + +void grayFFI(DictuVM *vm, ObjAbstract *abstract) { + (void)vm; + FFIInstance *ffi = (FFIInstance *)abstract->data; + + if (ffi == NULL) + return; +} + +static Value load(DictuVM *vm, int argCount, Value *args) { + if (argCount != 1) { + runtimeError(vm, "load() takes one argument (%d given)", argCount); + return EMPTY_VAL; + } + ObjString *path = AS_STRING(args[0]); +#ifdef _WIN32 + HMODULE library = LoadLibrary(path->chars); +#else + void *library = dlopen(path->chars, RTLD_LAZY); +#endif + if (!library) { + runtimeError(vm, "Couldn't load shared object: %s", path->chars); + return EMPTY_VAL; + } + ObjAbstract *abstract = newAbstractExcludeSelf(vm, freeFFI, ffiToString); + push(vm, OBJ_VAL(abstract)); +#ifdef _WIN32 + FARPROC init_func = GetProcAddress(library, "dictu_internal_ffi_init"); +#else + init_func_definition_t *init_func = + dlsym(library, "dictu_internal_ffi_init"); +#endif + // call init function to give ffi module the required pointers to the + // function of the vm. + if (!init_func) { + runtimeError(vm, "Couldn't initialize ffi api: %s", path->chars); +#ifdef _WIN32 + FreeLibrary(library); +#else + dlclose(library); +#endif + return EMPTY_VAL; + } + int initResult = init_func((void **)&ffi_function_pointers, vm, + &abstract->values, DICTU_FFI_API_VERSION); + if (initResult == 1) { + runtimeError(vm, "FFI mod already initialized: %s", path->chars); +#ifdef _WIN32 + FreeLibrary(library); +#else + dlclose(library); +#endif + return EMPTY_VAL; + } + if (initResult == 2) { + runtimeError(vm, + "FFI api version is newer then mod version: %s, required " + "FFI version: %d", + path->chars, DICTU_FFI_API_VERSION); +#ifdef _WIN32 + FreeLibrary(library); +#else + dlclose(library); +#endif + return EMPTY_VAL; + } + if (initResult > 3) { + runtimeError(vm, + "Mod init function returned a error: %s, error code: %d", + path->chars, initResult - 3); +#ifdef _WIN32 + FreeLibrary(library); +#else + dlclose(library); +#endif + return EMPTY_VAL; + } + FFIInstance *instance = ALLOCATE(vm, FFIInstance, 1); + instance->path = malloc(path->length + 1); + instance->path[path->length] = '\0'; + memcpy(instance->path, path->chars, path->length); + + instance->library = library; + + abstract->data = instance; + abstract->grayFunc = grayFFI; + pop(vm); + + return OBJ_VAL(abstract); +} + +Value createFFIModule(DictuVM *vm) { + ObjString *name = copyString(vm, "FFI", 3); + push(vm, OBJ_VAL(name)); + ObjModule *module = newModule(vm, name); + push(vm, OBJ_VAL(module)); + defineNative(vm, &module->values, "load", load); + + defineNativeProperty( + vm, &module->values, "suffix", + OBJ_VAL(copyString(vm, LIB_EXTENSION, LIB_EXTENSION_STRLEN))); + pop(vm); + pop(vm); + + return OBJ_VAL(module); +} diff --git a/src/optionals/ffi/ffi.h b/src/optionals/ffi/ffi.h new file mode 100644 index 00000000..245ebcb6 --- /dev/null +++ b/src/optionals/ffi/ffi.h @@ -0,0 +1,27 @@ +#ifndef dictu_ffi_module_h +#define dictu_ffi_module_h + +#include + +#include "../optionals.h" +#include "../../vm/vm.h" + +#ifdef _WIN32 +#define LIB_EXTENSION ".dll" +#define LIB_EXTENSION_STRLEN 4 +#elif __APPLE__ +#define LIB_EXTENSION ".dylib" +#define LIB_EXTENSION_STRLEN 6 +#else +#define LIB_EXTENSION ".so" +#define LIB_EXTENSION_STRLEN 3 +#endif + +// This is used to determine if we can safely load the function pointers without UB, +// if this is greater then the version from the mod we error in the internal mod load function. +#define DICTU_FFI_API_VERSION 2 + + +Value createFFIModule(DictuVM *vm); + +#endif //dictu_ffi_module_h diff --git a/src/optionals/hashlib.c b/src/optionals/hashlib.c index 87177bfa..ba8af5e7 100644 --- a/src/optionals/hashlib.c +++ b/src/optionals/hashlib.c @@ -167,4 +167,4 @@ Value createHashlibModule(DictuVM *vm) { pop(vm); return OBJ_VAL(module); -} \ No newline at end of file +} diff --git a/src/optionals/http/http.c b/src/optionals/http/http.c index e2255d65..fe7963ee 100644 --- a/src/optionals/http/http.c +++ b/src/optionals/http/http.c @@ -243,16 +243,20 @@ static void createResponse(DictuVM *vm, Response *response) { response->len = 0; response->res = NULL; + response->firstIteration = true; } static size_t writeResponse(char *ptr, size_t size, size_t nmemb, void *data) { Response *response = (Response *) data; size_t new_len = response->len + size * nmemb; - response->res = GROW_ARRAY(response->vm, response->res, char, response->len, new_len + 1); + response->res = GROW_ARRAY(response->vm, response->res, char, response->len + !response->firstIteration, new_len + 1); + response->firstIteration = false; + if (response->res == NULL) { printf("Unable to allocate memory\n"); exit(71); } + memcpy(response->res + response->len, ptr, size * nmemb); response->res[new_len] = '\0'; response->len = new_len; @@ -824,6 +828,99 @@ static Value head(DictuVM *vm, int argCount, Value *args) { return newResultError(vm, errorString); } +static Value options(DictuVM *vm, int argCount, Value *args) { + if (argCount < 0 || argCount > 3) { + runtimeError(vm, "options() takes between 1 and 3 arguments (%d given).", argCount); + return EMPTY_VAL; + } + + long timeout = DEFAULT_REQUEST_TIMEOUT; + ObjList *headers = NULL; + + if (argCount == 3) { + if (!IS_NUMBER(args[2])) { + runtimeError(vm, "Timeout passed to options() must be a number."); + return EMPTY_VAL; + } + + timeout = AS_NUMBER(args[2]); + argCount--; + } + + if (argCount == 2) { + if (!IS_LIST(args[1])) { + runtimeError(vm, "Headers passed to options() must be a list."); + return EMPTY_VAL; + } + + headers = AS_LIST(args[1]); + } + + if (!IS_STRING(args[0])) { + runtimeError(vm, "URL passed to options() must be a string."); + return EMPTY_VAL; + } + + CURL *curl; + CURLcode curlResponse; + + curl_global_init(CURL_GLOBAL_DEFAULT); + curl = curl_easy_init(); + + if (curl) { + Response response; + createResponse(vm, &response); + char *url = AS_CSTRING(args[0]); + + struct curl_slist *list = NULL; + + if (headers) { + if (!setRequestHeaders(vm, list, curl, headers)) { + curl_slist_free_all(list); + return EMPTY_VAL; + } + } + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "OPTIONS"); + curl_easy_setopt(curl, CURLOPT_REQUEST_TARGET, "*"); + curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "gzip"); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, writeHeaders); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + + /* Perform the request, res will get the return code */ + curlResponse = curl_easy_perform(curl); + + if (headers) { + curl_slist_free_all(list); + } + + /* Check for errors */ + if (curlResponse != CURLE_OK) { + /* always cleanup */ + curl_easy_cleanup(curl); + curl_global_cleanup(); + pop(vm); + + char *errorString = (char *) curl_easy_strerror(curlResponse); + return newResultError(vm, errorString); + } + + return newResultSuccess(vm, OBJ_VAL(endRequest(vm, curl, response, true))); + } + + /* always cleanup */ + curl_easy_cleanup(curl); + curl_global_cleanup(); + pop(vm); + + char *errorString = (char *) curl_easy_strerror(CURLE_FAILED_INIT); + return newResultError(vm, errorString); +} + typedef struct { CURL *curl; } HttpClient; @@ -937,7 +1034,7 @@ static Value httpClientSetHeaders(DictuVM *vm, int argCount, Value *args) { headerChunk = NULL; for (int h = 0; h < headers->values.count; h++) { - headerChunk = curl_slist_append(headerChunk, AS_STRING(headers->values.values[h])->chars); + headerChunk = curl_slist_append(headerChunk, AS_CSTRING(headers->values.values[h])); } curl_easy_setopt(httpClient->curl, CURLOPT_HTTPHEADER, headerChunk); @@ -958,7 +1055,7 @@ static Value httpClientSetKeyFile(DictuVM *vm, int argCount, Value *args) { HttpClient *httpClient = AS_HTTP_CLIENT(args[0]); - curl_easy_setopt(httpClient->curl, CURLOPT_SSLKEY, AS_STRING(args[1])->chars); + curl_easy_setopt(httpClient->curl, CURLOPT_SSLKEY, AS_CSTRING(args[1])); return NIL_VAL; } @@ -976,7 +1073,7 @@ static Value httpClientSetCertFile(DictuVM *vm, int argCount, Value *args) { HttpClient *httpClient = AS_HTTP_CLIENT(args[0]); - curl_easy_setopt(httpClient->curl, CURLOPT_SSLKEY, AS_STRING(args[1])->chars); + curl_easy_setopt(httpClient->curl, CURLOPT_SSLKEY, AS_CSTRING(args[1])); return NIL_VAL; } @@ -994,7 +1091,7 @@ static Value httpClientSetKeyPass(DictuVM *vm, int argCount, Value *args) { HttpClient *httpClient = AS_HTTP_CLIENT(args[0]); - curl_easy_setopt(httpClient->curl, CURLOPT_SSLKEY, AS_STRING(args[1])->chars); + curl_easy_setopt(httpClient->curl, CURLOPT_SSLKEY, AS_CSTRING(args[1])); return NIL_VAL; } @@ -1227,6 +1324,97 @@ static Value httpClientHead(DictuVM *vm, int argCount, Value *args) { return newResultError(vm, errorString); } +static Value httpClientOptions(DictuVM *vm, int argCount, Value *args) { + if (argCount < 0 || argCount > 3) { + runtimeError(vm, "options() takes between 1 and 3 arguments (%d given).", argCount); + return EMPTY_VAL; + } + + long timeout = DEFAULT_REQUEST_TIMEOUT; + ObjList *headers = NULL; + + HttpClient *httpClient = AS_HTTP_CLIENT(args[0]); + + if (argCount == 4) { + if (!IS_NUMBER(args[3])) { + runtimeError(vm, "Timeout passed to options() must be a number."); + return EMPTY_VAL; + } + + timeout = AS_NUMBER(args[3]); + argCount--; + } + + if (argCount == 3) { + if (!IS_LIST(args[2])) { + runtimeError(vm, "Headers passed to options() must be a list."); + return EMPTY_VAL; + } + + headers = AS_LIST(args[2]); + } + + if (!IS_STRING(args[1])) { + runtimeError(vm, "URL passed to options() must be a string."); + return EMPTY_VAL; + } + + CURLcode curlResponse; + + if (httpClient) { + Response response; + createResponse(vm, &response); + char *url = AS_CSTRING(args[1]); + + struct curl_slist *list = NULL; + + if (headers) { + if (!setRequestHeaders(vm, list, httpClient->curl, headers)) { + curl_slist_free_all(list); + return EMPTY_VAL; + } + } + + curl_easy_setopt(httpClient->curl, CURLOPT_URL, url); + curl_easy_setopt(httpClient->curl, CURLOPT_NOBODY, 1L); + curl_easy_setopt(httpClient->curl, CURLOPT_TIMEOUT, timeout); + curl_easy_setopt(httpClient->curl, CURLOPT_CUSTOMREQUEST, "OPTIONS"); + curl_easy_setopt(httpClient->curl, CURLOPT_REQUEST_TARGET, "*"); + curl_easy_setopt(httpClient->curl, CURLOPT_ACCEPT_ENCODING, "gzip"); + curl_easy_setopt(httpClient->curl, CURLOPT_HEADERFUNCTION, writeHeaders); + curl_easy_setopt(httpClient->curl, CURLOPT_HEADERDATA, &response); + curl_easy_setopt(httpClient->curl, CURLOPT_FOLLOWLOCATION, 1L); + + /* Perform the request, res will get the return code */ + curlResponse = curl_easy_perform(httpClient->curl); + + if (headers) { + curl_slist_free_all(list); + } + + /* Check for errors */ + if (curlResponse != CURLE_OK) { + /* always cleanup */ + curl_easy_cleanup(httpClient->curl); + curl_global_cleanup(); + pop(vm); + + char *errorString = (char *) curl_easy_strerror(curlResponse); + return newResultError(vm, errorString); + } + + return newResultSuccess(vm, OBJ_VAL(endRequest(vm, httpClient->curl, response, true))); + } + + /* always cleanup */ + curl_easy_cleanup(httpClient->curl); + curl_global_cleanup(); + pop(vm); + + char *errorString = (char *) curl_easy_strerror(CURLE_FAILED_INIT); + return newResultError(vm, errorString); +} + Value newHttpClient(DictuVM *vm, ObjDict *opts) { ObjAbstract *abstract = newAbstract(vm, freeHttpClient, httpClientToString); push(vm, OBJ_VAL(abstract)); @@ -1277,7 +1465,7 @@ Value newHttpClient(DictuVM *vm, ObjDict *opts) { ObjList *headers = AS_LIST(entry->value); for (int h = 0; h < headers->values.count; h++) { - headerChunk = curl_slist_append(headerChunk, AS_STRING(headers->values.values[h])->chars); + headerChunk = curl_slist_append(headerChunk, AS_CSTRING(headers->values.values[h])); } curl_easy_setopt(httpClient->curl, CURLOPT_HTTPHEADER, headerChunk); @@ -1319,7 +1507,7 @@ Value newHttpClient(DictuVM *vm, ObjDict *opts) { return EMPTY_VAL; } - char *keyFile = AS_STRING(entry->value)->chars; + char *keyFile = AS_CSTRING(entry->value); if (keyFile[0] == '\0') { continue; } @@ -1335,7 +1523,7 @@ Value newHttpClient(DictuVM *vm, ObjDict *opts) { return EMPTY_VAL; } - char *certFile = AS_STRING(entry->value)->chars; + char *certFile = AS_CSTRING(entry->value); if (certFile[0] == '\0') { continue; } @@ -1351,7 +1539,7 @@ Value newHttpClient(DictuVM *vm, ObjDict *opts) { return EMPTY_VAL; } - char *keyPasswd = AS_STRING(entry->value)->chars; + char *keyPasswd = AS_CSTRING(entry->value); if (keyPasswd[0] == '\0') { continue; } @@ -1370,6 +1558,7 @@ Value newHttpClient(DictuVM *vm, ObjDict *opts) { defineNative(vm, &abstract->values, "post", httpClientPost); defineNative(vm, &abstract->values, "put", httpClientPut); defineNative(vm, &abstract->values, "head", httpClientHead); + defineNative(vm, &abstract->values, "options", httpClientOptions); defineNative(vm, &abstract->values, "setTimeout", httpClientSetTimeout); defineNative(vm, &abstract->values, "setHeaders", httpClientSetHeaders); defineNative(vm, &abstract->values, "setInsecure", httpClientSetInsecure); @@ -1643,6 +1832,7 @@ Value createHTTPModule(DictuVM *vm) { defineNative(vm, &module->values, "post", post); defineNative(vm, &module->values, "put", put); defineNative(vm, &module->values, "head", head); + defineNative(vm, &module->values, "options", options); defineNative(vm, &module->values, "newClient", newClient); diff --git a/src/optionals/http/http.h b/src/optionals/http/http.h index fe13633d..57c860e9 100644 --- a/src/optionals/http/http.h +++ b/src/optionals/http/http.h @@ -14,6 +14,7 @@ typedef struct response { char *res; size_t len; long statusCode; + bool firstIteration; } Response; Value createHTTPModule(DictuVM *vm); diff --git a/src/optionals/io.c b/src/optionals/io.c index 877233b3..1602c7d4 100644 --- a/src/optionals/io.c +++ b/src/optionals/io.c @@ -50,8 +50,8 @@ static Value copyFileIO(DictuVM *vm, int argCount, Value *args) { return EMPTY_VAL; } - char *srcFile = AS_STRING(args[0])->chars; - char *dstFile = AS_STRING(args[1])->chars; + char *srcFile = AS_CSTRING(args[0]); + char *dstFile = AS_CSTRING(args[1]); FILE *sf = fopen(srcFile, "r"); if (sf == NULL) { @@ -89,8 +89,8 @@ static Value copyFileIO(DictuVM *vm, int argCount, Value *args) { return EMPTY_VAL; } - char *src = AS_STRING(args[0])->chars; - char *dst = AS_STRING(args[1])->chars; + char *src = AS_CSTRING(args[0]); + char *dst = AS_CSTRING(args[1]); int in = 0; int out = 0; @@ -127,6 +127,11 @@ Value createIOModule(DictuVM *vm) { defineNativeProperty(vm, &module->values, "stdin", NUMBER_VAL(STDIN_FILENO)); defineNativeProperty(vm, &module->values, "stdout", NUMBER_VAL(STDOUT_FILENO)); defineNativeProperty(vm, &module->values, "stderr", NUMBER_VAL(STDERR_FILENO)); +#ifndef _WIN32 + defineNativeProperty(vm, &module->values, "devNull", OBJ_VAL(copyString(vm, "/dev/null", strlen("/dev/null")))); +#else + defineNativeProperty(vm, &module->values, "devNull", OBJ_VAL(copyString(vm, "\\\\.\\NUL", strlen("\\\\.\\NUL")))); +#endif /** * Define IO methods diff --git a/src/optionals/json.c b/src/optionals/json.c index a711e7db..63c0691b 100644 --- a/src/optionals/json.c +++ b/src/optionals/json.c @@ -171,7 +171,7 @@ json_value* stringifyJson(DictuVM *vm, Value value) { return NULL; } -static Value stringify(DictuVM *vm, int argCount, Value *args) { +Value stringify(DictuVM *vm, int argCount, Value *args) { if (argCount != 1 && argCount != 2) { runtimeError(vm, "stringify() takes 1 or 2 arguments (%d given).", argCount); return EMPTY_VAL; diff --git a/src/optionals/json.h b/src/optionals/json.h index 02697a79..0b9b8f32 100644 --- a/src/optionals/json.h +++ b/src/optionals/json.h @@ -7,5 +7,6 @@ #include "../vm/vm.h" Value createJSONModule(DictuVM *vm); +Value stringify(DictuVM *vm, int argCount, Value *args); #endif //dictu_json_h diff --git a/src/optionals/object/object.c b/src/optionals/object/object.c index ea5791eb..ff5553d3 100644 --- a/src/optionals/object/object.c +++ b/src/optionals/object/object.c @@ -59,6 +59,25 @@ static Value objectHash(DictuVM *vm, int argCount, Value *args) { return OBJ_VAL(copyString(vm, (char *)str, 21)); } +static Value objectPrettyPrint(DictuVM *vm, int argCount, Value *args) { + if (argCount != 1 && argCount != 2) { + runtimeError(vm, "prettyPrint() takes 1 or arguments (%d given)", argCount); + return EMPTY_VAL; + } + + Value out = stringify(vm, argCount, args); + ObjResult *res = AS_RESULT(out); + if (res->status == ERR) { + runtimeError(vm, AS_CSTRING(res->value)); + return EMPTY_VAL; + } + + printValue(res->value); + printf("\n"); + + return NIL_VAL; +} + Value createObjectModule(DictuVM *vm) { ObjClosure *closure = compileModuleToClosure(vm, "Object", DICTU_OBJECT_SOURCE); @@ -75,6 +94,8 @@ Value createObjectModule(DictuVM *vm) { defineNative(vm, &closure->function->module->values, "getClassRef", objectGetClassRef); defineNative(vm, &closure->function->module->values, "hash", objectHash); + defineNative(vm, &closure->function->module->values, "prettyPrint", objectPrettyPrint); + pop(vm); return OBJ_VAL(closure); diff --git a/src/optionals/object/object.h b/src/optionals/object/object.h index c8384cf8..3d66276c 100644 --- a/src/optionals/object/object.h +++ b/src/optionals/object/object.h @@ -5,6 +5,7 @@ #include "../optionals.h" #include "../../vm/vm.h" +#include "../json.h" Value createObjectModule(DictuVM *vm); diff --git a/src/optionals/optionals.c b/src/optionals/optionals.c index 35ca3176..f2fbe811 100644 --- a/src/optionals/optionals.c +++ b/src/optionals/optionals.c @@ -1,7 +1,7 @@ #include "optionals.h" +#include "ffi/ffi.h" BuiltinModules modules[] = { - {"C", &createCModule, false}, {"Argparse", &createArgParseModule, false}, {"Math", &createMathsModule, false}, {"Env", &createEnvModule, true}, @@ -31,6 +31,8 @@ BuiltinModules modules[] = { {"HTTP", &createHTTPModule, true}, #endif {"BigInt", &createBigIntModule, false}, + {"Buffer", &createBufferModule, false}, + {"FFI", &createFFIModule, false}, {NULL, NULL, false} }; diff --git a/src/optionals/optionals.h b/src/optionals/optionals.h index e1649a00..70bcae93 100644 --- a/src/optionals/optionals.h +++ b/src/optionals/optionals.h @@ -12,7 +12,6 @@ #include "log.h" #include "http/http.h" #include "path.h" -#include "c.h" #include "datetime.h" #include "socket.h" #include "net.h" @@ -28,7 +27,9 @@ #include "stack.h" #include "bigint.h" #include "object/object.h" +#include "buffer.h" #include "unittest/unittest.h" +#include "ffi/ffi.h" typedef Value (*BuiltinModule)(DictuVM *vm); diff --git a/src/optionals/process.c b/src/optionals/process.c index 85284603..ec7de1f5 100644 --- a/src/optionals/process.c +++ b/src/optionals/process.c @@ -1,5 +1,14 @@ +#include +#ifdef _WIN32 +#include "windowsapi.h" +#endif + #include "process.h" +#ifdef _WIN32 +#define pid_t int +#endif + #ifdef _WIN32 static char* buildArgs(DictuVM *vm, ObjList* list, int *size) { // 3 for 1st arg escape + null terminator @@ -222,7 +231,7 @@ static Value executeReturnOutput(DictuVM* vm, ObjList* argList) { } #endif -static Value execNative(DictuVM* vm, int argCount, Value* args) { +static Value execProcess(DictuVM* vm, int argCount, Value* args) { if (argCount != 1) { runtimeError(vm, "exec() takes 1 argument (%d given).", argCount); return EMPTY_VAL; @@ -237,7 +246,7 @@ static Value execNative(DictuVM* vm, int argCount, Value* args) { return execute(vm, argList, false); } -static Value runNative(DictuVM* vm, int argCount, Value* args) { +static Value runProcess(DictuVM* vm, int argCount, Value* args) { if (argCount != 1 && argCount != 2) { runtimeError(vm, "run() takes 1 or 2 arguments (%d given)", argCount); return EMPTY_VAL; @@ -268,6 +277,60 @@ static Value runNative(DictuVM* vm, int argCount, Value* args) { return execute(vm, argList, true); } +#ifdef _WIN32 +static Value killProcess(DictuVM* vm, int argCount, Value* args) { + if (argCount > 2) { + runtimeError(vm, "kill() takes 1 argument (%d given)", argCount); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[0])) { + runtimeError(vm, "Argument passed to kill() must be a number"); + return EMPTY_VAL; + } + + pid_t pid = (pid_t)AS_NUMBER(args[0]); + + HANDLE handle = OpenProcess(PROCESS_TERMINATE, TRUE, (int)pid); + if (handle != NULL) { + TerminateProcess(handle, 0); + CloseHandle(handle); + } + + return newResultSuccess(vm, NIL_VAL); +} +#else +static Value killProcess(DictuVM* vm, int argCount, Value* args) { + if (argCount > 2) { + runtimeError(vm, "kill() takes 1 or 2 arguments (%d given)", argCount); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[0])) { + runtimeError(vm, "First argument passed to kill() must be a number"); + return EMPTY_VAL; + } + + pid_t pid = (pid_t)AS_NUMBER(args[0]); + int signal = 9; + + if (argCount == 2) { + if (!IS_NUMBER(args[1])) { + runtimeError(vm, "Second argument passed to kill() must be a number"); + return EMPTY_VAL; + } + + signal = AS_NUMBER(args[1]); + } + + if (kill(pid, signal) == -1) { + ERROR_RESULT; + } + + return newResultSuccess(vm, NIL_VAL); +} +#endif + Value createProcessModule(DictuVM* vm) { ObjString* name = copyString(vm, "Process", 7); push(vm, OBJ_VAL(name)); @@ -277,12 +340,80 @@ Value createProcessModule(DictuVM* vm) { /** * Define process methods */ - defineNative(vm, &module->values, "exec", execNative); - defineNative(vm, &module->values, "run", runNative); + defineNative(vm, &module->values, "exec", execProcess); + defineNative(vm, &module->values, "run", runProcess); + defineNative(vm, &module->values, "kill", killProcess); /** * Define process properties */ + defineNativeProperty(vm, &module->values, "SIGINT", NUMBER_VAL(2)); + defineNativeProperty(vm, &module->values, "SIGILL", NUMBER_VAL(4)); + defineNativeProperty(vm, &module->values, "SIGFPE", NUMBER_VAL(8)); + defineNativeProperty(vm, &module->values, "SIGKILL", NUMBER_VAL(9)); + defineNativeProperty(vm, &module->values, "SIGSEGV", NUMBER_VAL(11)); + defineNativeProperty(vm, &module->values, "SIGTERM", NUMBER_VAL(15)); + +#if defined(__Linux__) + defineNativeProperty(vm, &module->values, "SIGHUP", NUMBER_VAL(1)); + defineNativeProperty(vm, &module->values, "SIGQUIT", NUMBER_VAL(3)); + defineNativeProperty(vm, &module->values, "SIGABRT", NUMBER_VAL(6)); + defineNativeProperty(vm, &module->values, "SIGTRAP", NUMBER_VAL(5)); + defineNativeProperty(vm, &module->values, "SIGIOT", NUMBER_VAL(6)); + defineNativeProperty(vm, &module->values, "SIGBUS", NUMBER_VAL(7)); + defineNativeProperty(vm, &module->values, "SIGUSR1", NUMBER_VAL(10)); + defineNativeProperty(vm, &module->values, "SIGUSR2", NUMBER_VAL(12)); + defineNativeProperty(vm, &module->values, "SIGPIPE", NUMBER_VAL(13)); + defineNativeProperty(vm, &module->values, "SIGALRM", NUMBER_VAL(14)); + defineNativeProperty(vm, &module->values, "SIGSTKFLT", NUMBER_VAL(16)); + defineNativeProperty(vm, &module->values, "SIGCHLD", NUMBER_VAL(17)); + defineNativeProperty(vm, &module->values, "SIGCONT", NUMBER_VAL(18)); + defineNativeProperty(vm, &module->values, "SIGSTOP", NUMBER_VAL(19)); + defineNativeProperty(vm, &module->values, "SIGTSTP", NUMBER_VAL(20)); + defineNativeProperty(vm, &module->values, "SIGTTIN", NUMBER_VAL(21)); + defineNativeProperty(vm, &module->values, "SIGTTOU", NUMBER_VAL(22)); + defineNativeProperty(vm, &module->values, "SIGURG", NUMBER_VAL(23)); + defineNativeProperty(vm, &module->values, "SIGXCPU", NUMBER_VAL(24)); + defineNativeProperty(vm, &module->values, "SIGXFSZ", NUMBER_VAL(25)); + defineNativeProperty(vm, &module->values, "SIGVTALRM", NUMBER_VAL(26)); + defineNativeProperty(vm, &module->values, "SIGPROF", NUMBER_VAL(27)); + defineNativeProperty(vm, &module->values, "SIGWINCH", NUMBER_VAL(28)); + defineNativeProperty(vm, &module->values, "SIGIO", NUMBER_VAL(29)); + defineNativeProperty(vm, &module->values, "SIGPWR", NUMBER_VAL(30)); + defineNativeProperty(vm, &module->values, "SIGSYS", NUMBER_VAL(31)); + defineNativeProperty(vm, &module->values, "SIGUNUSED", NUMBER_VAL(31)); +#elif defined(__FreeBSD__) || defined(__APPLE__) + defineNativeProperty(vm, &module->values, "SIGHUP", NUMBER_VAL(1)); + defineNativeProperty(vm, &module->values, "SIGQUIT", NUMBER_VAL(3)); + defineNativeProperty(vm, &module->values, "SIGTRAP", NUMBER_VAL(5)); + defineNativeProperty(vm, &module->values, "SIGABRT", NUMBER_VAL(6)); + defineNativeProperty(vm, &module->values, "SIGEMT", NUMBER_VAL(7)); + defineNativeProperty(vm, &module->values, "SIGBUS", NUMBER_VAL(10)); + defineNativeProperty(vm, &module->values, "SIGSYS", NUMBER_VAL(12)); + defineNativeProperty(vm, &module->values, "SIGPIPE", NUMBER_VAL(13)); + defineNativeProperty(vm, &module->values, "SIGALRM", NUMBER_VAL(14)); + defineNativeProperty(vm, &module->values, "SIGURG", NUMBER_VAL(16)); + defineNativeProperty(vm, &module->values, "SIGSTOP", NUMBER_VAL(17)); + defineNativeProperty(vm, &module->values, "SIGTSTP", NUMBER_VAL(18)); + defineNativeProperty(vm, &module->values, "SIGCONT", NUMBER_VAL(19)); + defineNativeProperty(vm, &module->values, "SIGCHLD", NUMBER_VAL(20)); + defineNativeProperty(vm, &module->values, "SIGTTIN", NUMBER_VAL(21)); + defineNativeProperty(vm, &module->values, "SIGTTOU", NUMBER_VAL(22)); + defineNativeProperty(vm, &module->values, "SIGIO", NUMBER_VAL(23)); + defineNativeProperty(vm, &module->values, "SIGXCPU", NUMBER_VAL(24)); + defineNativeProperty(vm, &module->values, "SIGXFSZ", NUMBER_VAL(25)); + defineNativeProperty(vm, &module->values, "SIGVTALRM", NUMBER_VAL(26)); + defineNativeProperty(vm, &module->values, "SIGPROF", NUMBER_VAL(27)); + defineNativeProperty(vm, &module->values, "SIGWINCH", NUMBER_VAL(28)); + defineNativeProperty(vm, &module->values, "SIGINFO", NUMBER_VAL(29)); + defineNativeProperty(vm, &module->values, "SIGUSR1", NUMBER_VAL(30)); + defineNativeProperty(vm, &module->values, "SIGUSR2", NUMBER_VAL(31)); + defineNativeProperty(vm, &module->values, "SIGTHR", NUMBER_VAL(32)); + defineNativeProperty(vm, &module->values, "SIGLIBRT", NUMBER_VAL(33)); +#elif defined(_WIN32) + defineNativeProperty(vm, &module->values, "SIGEXIT", NUMBER_VAL(0)); + defineNativeProperty(vm, &module->values, "SIGABRT", NUMBER_VAL(22)); +#endif pop(vm); pop(vm); diff --git a/src/optionals/system.c b/src/optionals/system.c index da7ed6e6..9a17a8b3 100644 --- a/src/optionals/system.c +++ b/src/optionals/system.c @@ -98,7 +98,7 @@ static Value chownNative(DictuVM *vm, int argCount, Value *args) { ERROR_RESULT; } - return newResultSuccess(vm, EMPTY_VAL); + return newResultSuccess(vm, NIL_VAL); } static Value unameNative(DictuVM *vm, int argCount, Value *args) { @@ -490,6 +490,27 @@ static Value chmodNative(DictuVM *vm, int argCount, Value *args) { return newResultSuccess(vm, NIL_VAL); } +static Value renameNative(DictuVM *vm, int argCount, Value *args) { + if (argCount != 2) { + runtimeError(vm, "rename() takes 2 arguments (%d given).", argCount); + return EMPTY_VAL; + } + + if (!IS_STRING(args[0]) || !IS_STRING(args[1])) { + runtimeError(vm, "rename() arguments must be strings."); + return EMPTY_VAL; + } + + char *old = AS_CSTRING(args[0]); + char *new = AS_CSTRING(args[1]); + + if (rename(old, new) != 0) { + return newResultError(vm, "failed to reaneme file"); + } + + return newResultSuccess(vm, NIL_VAL); +} + void initArgv(DictuVM *vm, Table *table, int argc, char **argv) { ObjList *list = newList(vm); push(vm, OBJ_VAL(list)); @@ -590,6 +611,7 @@ Value createSystemModule(DictuVM *vm) { defineNative(vm, &module->values, "sleep", sleepNative); defineNative(vm, &module->values, "exit", exitNative); defineNative(vm, &module->values, "chmod", chmodNative); + defineNative(vm, &module->values, "rename", renameNative); /** * Define System properties @@ -602,6 +624,8 @@ Value createSystemModule(DictuVM *vm) { initPlatform(vm, &module->values); setVersion(vm, &module->values); + defineNativeProperty(vm, &module->values, "arch", OBJ_VAL(copyString(vm, SYSTEM_ARCH, strlen(SYSTEM_ARCH)))); + defineNativeProperty(vm, &module->values, "S_IRWXU", NUMBER_VAL(448)); defineNativeProperty(vm, &module->values, "S_IRUSR", NUMBER_VAL(256)); defineNativeProperty(vm, &module->values, "S_IWUSR", NUMBER_VAL(128)); diff --git a/src/optionals/system.h b/src/optionals/system.h index 4fe1851e..aec659f0 100644 --- a/src/optionals/system.h +++ b/src/optionals/system.h @@ -24,6 +24,7 @@ #include "../vm/common.h" #include "../vm/vm.h" #include "../vm/memory.h" +#include "arch.h" Value createSystemModule(DictuVM *vm); diff --git a/src/optionals/unittest/unittest.c b/src/optionals/unittest/unittest.c index 23268c63..31dd627e 100644 --- a/src/optionals/unittest/unittest.c +++ b/src/optionals/unittest/unittest.c @@ -31,7 +31,7 @@ static Value mockNative(DictuVM *vm, int argCount, Value *args) { pop(vm); push(vm, OBJ_VAL(mockedClass)); - for (int i = 0; i <= klass->publicMethods.capacityMask; ++i) { + for (int i = 0; i < klass->publicMethods.capacity; ++i) { if (klass->publicMethods.entries[i].key == NULL) { continue; } diff --git a/src/vm/datatypes/class.c b/src/vm/datatypes/class.c index b783c46b..939295da 100644 --- a/src/vm/datatypes/class.c +++ b/src/vm/datatypes/class.c @@ -38,7 +38,7 @@ static Value methods(DictuVM *vm, int argCount, Value *args) { ObjList *list = newList(vm); push(vm, OBJ_VAL(list)); - for (int i = 0; i <= klass->publicMethods.capacityMask; ++i) { + for (int i = 0; i < klass->publicMethods.capacity; ++i) { if (klass->publicMethods.entries[i].key == NULL) { continue; } diff --git a/src/vm/datatypes/copy.c b/src/vm/datatypes/copy.c index 55c21b2d..c1862da5 100644 --- a/src/vm/datatypes/copy.c +++ b/src/vm/datatypes/copy.c @@ -70,7 +70,7 @@ ObjInstance *copyInstance(DictuVM* vm, ObjInstance *oldInstance, bool shallow) { if (shallow) { tableAddAll(vm, &oldInstance->publicAttributes, &instance->publicAttributes); } else { - for (int i = 0; i <= oldInstance->publicAttributes.capacityMask; i++) { + for (int i = 0; i < oldInstance->publicAttributes.capacity; i++) { Entry *entry = &oldInstance->publicAttributes.entries[i]; if (entry->key != NULL) { Value val = entry->value; @@ -90,7 +90,7 @@ ObjInstance *copyInstance(DictuVM* vm, ObjInstance *oldInstance, bool shallow) { } } - for (int i = 0; i <= oldInstance->privateAttributes.capacityMask; i++) { + for (int i = 0; i < oldInstance->privateAttributes.capacity; i++) { Entry *entry = &oldInstance->privateAttributes.entries[i]; if (entry->key != NULL) { Value val = entry->value; diff --git a/src/vm/datatypes/dicts/dict-source.h b/src/vm/datatypes/dicts/dict-source.h index 0e6a6732..06bb6931 100644 --- a/src/vm/datatypes/dicts/dict-source.h +++ b/src/vm/datatypes/dicts/dict-source.h @@ -4,6 +4,10 @@ " *\n" \ " * We should always strive to write methods in C where possible.\n" \ " */\n" \ +"\n" \ +"import Object;\n" \ +"\n" \ +"\n" \ "def forEach(dict, func) {\n" \ " const dictKeys = dict.keys();\n" \ "\n" \ @@ -21,4 +25,17 @@ "\n" \ " return newDict;\n" \ "}\n" \ +"\n" \ +"def toObj(dict, obj) {\n" \ +" dict.forEach(def(k, v) => {\n" \ +" if (type(k) != 'string') {\n" \ +" const fieldName = k.toString();\n" \ +" obj.setAttribute(fieldName, v);\n" \ +" return;\n" \ +" }\n" \ +" obj.setAttribute(k, v);\n" \ +" });\n" \ +"\n" \ +" return obj;\n" \ +"}\n" \ diff --git a/src/vm/datatypes/dicts/dict.du b/src/vm/datatypes/dicts/dict.du index 8fc79237..c0068b91 100644 --- a/src/vm/datatypes/dicts/dict.du +++ b/src/vm/datatypes/dicts/dict.du @@ -20,4 +20,17 @@ def merge(dict, anotherDict) { }); return newDict; -} \ No newline at end of file +} + +def toObj(dict, obj) { + dict.forEach(def(k, v) => { + if (type(k) != 'string') { + const fieldName = k.toString(); + obj.setAttribute(fieldName, v); + return; + } + obj.setAttribute(k, v); + }); + + return obj; +} diff --git a/src/vm/datatypes/dicts/dicts.c b/src/vm/datatypes/dicts/dicts.c index 9fdc93df..623c0aea 100644 --- a/src/vm/datatypes/dicts/dicts.c +++ b/src/vm/datatypes/dicts/dicts.c @@ -31,7 +31,7 @@ static Value lenDict(DictuVM *vm, int argCount, Value *args) { } ObjDict *dict = AS_DICT(args[0]); - return NUMBER_VAL(dict->count); + return NUMBER_VAL(dict->activeCount); } static Value keysDict(DictuVM *vm, int argCount, Value *args) { diff --git a/src/vm/datatypes/enums.c b/src/vm/datatypes/enums.c index 7a5c170c..2d3ac7ac 100644 --- a/src/vm/datatypes/enums.c +++ b/src/vm/datatypes/enums.c @@ -10,7 +10,7 @@ static Value values(DictuVM *vm, int argCount, Value *args) { ObjDict *dict = newDict(vm); push(vm, OBJ_VAL(dict)); - for (int i = 0; i < objEnum->values.capacityMask + 1; ++i) { + for (int i = 0; i < objEnum->values.capacity; ++i) { if (objEnum->values.entries[i].key == NULL) { continue; } diff --git a/src/vm/datatypes/instance.c b/src/vm/datatypes/instance.c index 0952169e..8d497de3 100644 --- a/src/vm/datatypes/instance.c +++ b/src/vm/datatypes/instance.c @@ -132,7 +132,7 @@ static Value getAttributes(DictuVM *vm, int argCount, Value *args) { // Walk the inheritance chain while (klass != NULL) { - for (int i = 0; i < klass->variables.capacityMask + 1; i++) { + for (int i = 0; i < klass->variables.capacity; i++) { if (klass->variables.entries[i].key == NULL) { continue; } @@ -144,7 +144,7 @@ static Value getAttributes(DictuVM *vm, int argCount, Value *args) { writeValueArray(vm, &fields->values, OBJ_VAL(klass->variables.entries[i].key)); } - for (int i = 0; i < klass->constants.capacityMask + 1; i++) { + for (int i = 0; i < klass->constants.capacity; i++) { if (klass->constants.entries[i].key == NULL) { continue; } @@ -170,7 +170,7 @@ static Value getAttributes(DictuVM *vm, int argCount, Value *args) { ObjList *attributes = newList(vm); push(vm, OBJ_VAL(attributes)); - for (int i = 0; i < instance->publicAttributes.capacityMask + 1; i++) { + for (int i = 0; i < instance->publicAttributes.capacity; i++) { if (instance->publicAttributes.entries[i].key == NULL) { continue; } @@ -193,7 +193,7 @@ static Value getAttributes(DictuVM *vm, int argCount, Value *args) { ObjList *methods = newList(vm); push(vm, OBJ_VAL(methods)); - for (int i = 0; i <= instance->klass->publicMethods.capacityMask; ++i) { + for (int i = 0; i < instance->klass->publicMethods.capacity; ++i) { if (instance->klass->publicMethods.entries[i].key == NULL) { continue; } @@ -295,7 +295,7 @@ static Value methods(DictuVM *vm, int argCount, Value *args) { ObjList *list = newList(vm); push(vm, OBJ_VAL(list)); - for (int i = 0; i <= instance->klass->publicMethods.capacityMask; ++i) { + for (int i = 0; i < instance->klass->publicMethods.capacity; ++i) { if (instance->klass->publicMethods.entries[i].key == NULL) { continue; } diff --git a/src/vm/datatypes/lists/list-source.h b/src/vm/datatypes/lists/list-source.h index 32a07181..9189d06a 100644 --- a/src/vm/datatypes/lists/list-source.h +++ b/src/vm/datatypes/lists/list-source.h @@ -14,6 +14,36 @@ " return temp;\n" \ "}\n" \ "\n" \ +"def sortFunc(list, func) {\n" \ +" if(list.len() < 2) {\n" \ +" return;\n" \ +" }\n" \ +" const partition = def (arr, start, end) => {\n" \ +" const pivot = arr[end];\n" \ +" var i = start - 1;\n" \ +" for(var t = start; t <= end-1; t+= 1) {\n" \ +" if(func(pivot, arr[t]) > 0) {\n" \ +" i+= 1;\n" \ +" const temp = arr[i];\n" \ +" arr[i] = arr[t];\n" \ +" arr[t] = temp;\n" \ +" }\n" \ +" }\n" \ +" const temp = arr[i+1];\n" \ +" arr[i+1] = arr[end];\n" \ +" arr[end] = temp;\n" \ +" return i + 1;\n" \ +" };\n" \ +" const quickSort = def(arr, start, end) => {\n" \ +" if(start < end) {\n" \ +" const p = partition(arr, start, end);\n" \ +" quickSort(arr, start, p - 1);\n" \ +" quickSort(arr, p + 1, end);\n" \ +" }\n" \ +" };\n" \ +" quickSort(list, 0, list.len()-1);\n" \ +"}\n" \ +"\n" \ "def filter(list, func=def(x) => x) {\n" \ " const temp = [];\n" \ "\n" \ diff --git a/src/vm/datatypes/lists/list.du b/src/vm/datatypes/lists/list.du index a5e8ec8b..218bec48 100644 --- a/src/vm/datatypes/lists/list.du +++ b/src/vm/datatypes/lists/list.du @@ -14,6 +14,36 @@ def map(list, func) { return temp; } +def sortFunc(list, func) { + if(list.len() < 2) { + return; + } + const partition = def (arr, start, end) => { + const pivot = arr[end]; + var i = start - 1; + for(var t = start; t <= end-1; t+= 1) { + if(func(pivot, arr[t]) > 0) { + i+= 1; + const temp = arr[i]; + arr[i] = arr[t]; + arr[t] = temp; + } + } + const temp = arr[i+1]; + arr[i+1] = arr[end]; + arr[end] = temp; + return i + 1; + }; + const quickSort = def(arr, start, end) => { + if(start < end) { + const p = partition(arr, start, end); + quickSort(arr, start, p - 1); + quickSort(arr, p + 1, end); + } + }; + quickSort(list, 0, list.len()-1); +} + def filter(list, func=def(x) => x) { const temp = []; diff --git a/src/vm/datatypes/lists/lists.c b/src/vm/datatypes/lists/lists.c index 464d561e..7389e390 100644 --- a/src/vm/datatypes/lists/lists.c +++ b/src/vm/datatypes/lists/lists.c @@ -84,6 +84,9 @@ static Value insertListItem(DictuVM *vm, int argCount, Value *args) { Value insertValue = args[1]; int index = AS_NUMBER(args[2]); + if (index < 0) { + index = list->values.count + index+1; + } if (index < 0 || index > list->values.count) { runtimeError(vm, "Index passed to insert() is out of bounds for the list given"); return EMPTY_VAL; @@ -129,7 +132,9 @@ static Value popListItem(DictuVM *vm, int argCount, Value *args) { } int index = AS_NUMBER(args[1]); - + if (index < 0) { + index = list->values.count + index; + } if (index < 0 || index > list->values.count) { runtimeError(vm, "Index passed to pop() is out of bounds for the list given"); return EMPTY_VAL; diff --git a/src/vm/datatypes/sets.c b/src/vm/datatypes/sets.c index f898a21f..f8907688 100644 --- a/src/vm/datatypes/sets.c +++ b/src/vm/datatypes/sets.c @@ -14,6 +14,25 @@ static Value toStringSet(DictuVM *vm, int argCount, Value *args) { return OBJ_VAL(string); } +static Value valuesSet(DictuVM *vm, int argCount, Value *args) { + if (argCount != 0) { + runtimeError(vm, "values() takes no arguments (%d given)", argCount); + return EMPTY_VAL; + } + ObjSet *set = AS_SET(args[0]); + ObjList *list = newList(vm); + push(vm, OBJ_VAL(list)); + + for (int i = 0; i <= set->capacityMask; ++i) { + SetItem *item = &set->entries[i]; + if (IS_EMPTY(item->value) || item->deleted) + continue; + writeValueArray(vm, &list->values, item->value); + } + pop(vm); + return OBJ_VAL(list); +} + static Value lenSet(DictuVM *vm, int argCount, Value *args) { if (argCount != 0) { runtimeError(vm, "len() takes no arguments (%d given)", argCount); @@ -96,10 +115,10 @@ static Value containsAllSet(DictuVM *vm, int argCount, Value *args) { void declareSetMethods(DictuVM *vm) { defineNative(vm, &vm->setMethods, "toString", toStringSet); defineNative(vm, &vm->setMethods, "len", lenSet); + defineNative(vm, &vm->setMethods, "values", valuesSet); defineNative(vm, &vm->setMethods, "add", addSetItem); defineNative(vm, &vm->setMethods, "remove", removeSetItem); defineNative(vm, &vm->setMethods, "contains", containsSetItem); defineNative(vm, &vm->setMethods, "containsAll", containsAllSet); defineNative(vm, &vm->setMethods, "toBool", boolNative); // Defined in util } - diff --git a/src/vm/datatypes/strings.c b/src/vm/datatypes/strings.c index 296084e0..e58ca3e6 100644 --- a/src/vm/datatypes/strings.c +++ b/src/vm/datatypes/strings.c @@ -275,6 +275,49 @@ static Value findString(DictuVM *vm, int argCount, Value *args) { return NUMBER_VAL(position); } +static Value findLastString(DictuVM *vm, int argCount, Value *args) { + if (argCount != 1) { + runtimeError(vm, "findLast() takes 1 argument (%d given)", argCount); + return EMPTY_VAL; + } + + const char *str = AS_CSTRING(args[0]); + const char *ss = AS_CSTRING(args[1]); + const char *p = str; + int found = !*ss; + + if (!found) { + while (*p) { + ++p; + } + + const char *q = ss; + while (*q) { + ++q; + } + + while (!found && !(p-str < q-ss)) { + const char *s = p; + const char *t = q; + + while (t != ss && *(s-1) == *(t-1)) { + --s; + --t; + } + + found = t == ss; + + if (found) { + p = s; + } else { + --p; + } + } + } + + return NUMBER_VAL(found ? p-str : -1); +} + static Value replaceString(DictuVM *vm, int argCount, Value *args) { if (argCount != 2) { runtimeError(vm, "replace() takes 2 arguments (%d given)", argCount); @@ -502,6 +545,32 @@ static Value countString(DictuVM *vm, int argCount, Value *args) { return NUMBER_VAL(count); } +static Value wordCountString(DictuVM *vm, int argCount, Value *args) { + if (argCount != 0) { + runtimeError(vm, "count() takes no arguments (%d given)", argCount); + return EMPTY_VAL; + } + + char *string = AS_CSTRING(args[0]); + + int count = 0; + int len = strlen(string); + bool in = false; + + for (int i = 0; i < len; i++) { + if (isspace(string[i])) { + in = false; + } else if(isalpha(string[i])) { + if(!in) { + in = true; + count++; + } + } + } + + return NUMBER_VAL(count); +} + static Value titleString(DictuVM *vm, int argCount, Value *args) { if (argCount != 0) { runtimeError(vm, "title() takes no arguments (%d given)", argCount); @@ -555,6 +624,106 @@ static Value repeatString(DictuVM *vm, int argCount, Value *args) { return OBJ_VAL(takeString(vm, temp, tempLen - 1)); } +static Value isUpperString(DictuVM *vm, int argCount, Value *args) { + if (argCount != 0) { + runtimeError(vm, "isUpper() takes no arguments (%d given)", argCount); + return EMPTY_VAL; + } + + char *string = AS_CSTRING(args[0]); + int len = strlen(string); + + if (len == 0) { + return BOOL_VAL(false); + } + + for (int i = 0; i < len; i++) { + if (!isupper(string[i]) && isalpha(string[i])) { + return BOOL_VAL(false); + } + } + + return BOOL_VAL(true); +} + +static Value isLowerString(DictuVM *vm, int argCount, Value *args) { + if (argCount != 0) { + runtimeError(vm, "isLower() takes no arguments (%d given)", argCount); + return EMPTY_VAL; + } + + char *string = AS_CSTRING(args[0]); + int len = strlen(string); + + if (len == 0) { + return BOOL_VAL(false); + } + + for (int i = 0; i < len; i++) { + if (!islower(string[i]) && isalpha(string[i])) { + return BOOL_VAL(false); + } + } + + return BOOL_VAL(true); +} + +static Value collapseSpacesString(DictuVM *vm, int argCount, Value *args) { + if (argCount != 0) { + runtimeError(vm, "collapseSpaces() takes no arguments (%d given)", argCount); + return EMPTY_VAL; + } + + ObjString *string = AS_STRING(args[0]); + char *temp = ALLOCATE(vm, char, string->length + 1); + strcpy(temp, string->chars); + + int i, j; + for (i = j = 0; temp[i]; ++i) { + if (!isspace(temp[i]) || (i > 0 && !isspace(temp[i-1]))) { + temp[j++] = temp[i]; + } + } + + temp[j+1] = '\0'; + + if (i != j) { + temp = SHRINK_ARRAY(vm, temp, char, string->length + 1, j + 1); + } + + return OBJ_VAL(takeString(vm, temp, j)); +} + +static Value wrapString(DictuVM *vm, int argCount, Value *args) { + if (argCount != 1) { + runtimeError(vm, "wrap() takes 1 argument (%d given)", argCount); + return EMPTY_VAL; + } + + ObjString *string = AS_STRING(args[0]); + char *temp = ALLOCATE(vm, char, string->length + 1); + + int len = AS_NUMBER(args[1]); + + int last = 0; + int count = 0; + + for (int cur = 0; string->chars[cur] != '\0'; cur++, count++) { + temp[cur] = string->chars[cur]; + + if (isspace(temp[cur])) { + last = cur; + } + + if (count >= len) { + temp[last] = '\n'; + count = 0; + } + } + + return OBJ_VAL(takeString(vm, temp, strlen(temp))); +} + void declareStringMethods(DictuVM *vm) { defineNative(vm, &vm->stringMethods, "len", lenString); defineNative(vm, &vm->stringMethods, "toNumber", toNumberString); @@ -562,6 +731,7 @@ void declareStringMethods(DictuVM *vm) { defineNative(vm, &vm->stringMethods, "split", splitString); defineNative(vm, &vm->stringMethods, "contains", containsString); defineNative(vm, &vm->stringMethods, "find", findString); + defineNative(vm, &vm->stringMethods, "findLast", findLastString); defineNative(vm, &vm->stringMethods, "replace", replaceString); defineNative(vm, &vm->stringMethods, "lower", lowerString); defineNative(vm, &vm->stringMethods, "upper", upperString); @@ -571,8 +741,12 @@ void declareStringMethods(DictuVM *vm) { defineNative(vm, &vm->stringMethods, "rightStrip", rightStripString); defineNative(vm, &vm->stringMethods, "strip", stripString); defineNative(vm, &vm->stringMethods, "count", countString); + defineNative(vm, &vm->stringMethods, "wordCount", wordCountString); defineNative(vm, &vm->stringMethods, "toBool", boolNative); // Defined in util defineNative(vm, &vm->stringMethods, "title", titleString); defineNative(vm, &vm->stringMethods, "repeat", repeatString); - + defineNative(vm, &vm->stringMethods, "isUpper", isUpperString); + defineNative(vm, &vm->stringMethods, "isLower", isLowerString); + defineNative(vm, &vm->stringMethods, "collapseSpaces", collapseSpacesString); + defineNative(vm, &vm->stringMethods, "wrap", wrapString); } diff --git a/src/vm/object.c b/src/vm/object.c index 9257f605..413b29b8 100644 --- a/src/vm/object.c +++ b/src/vm/object.c @@ -167,6 +167,7 @@ ObjList *newList(DictuVM *vm) { ObjDict *newDict(DictuVM *vm) { ObjDict *dict = ALLOCATE_OBJ(vm, ObjDict, OBJ_DICT); dict->count = 0; + dict->activeCount = 0; dict->capacityMask = -1; dict->entries = NULL; return dict; @@ -190,6 +191,19 @@ ObjAbstract *newAbstract(DictuVM *vm, AbstractFreeFn func, AbstractTypeFn type) abstract->func = func; abstract->type = type; abstract->grayFunc = NULL; + abstract->excludeSelf = false; + initTable(&abstract->values); + + return abstract; +} + +ObjAbstract *newAbstractExcludeSelf(DictuVM *vm, AbstractFreeFn func, AbstractTypeFn type) { + ObjAbstract *abstract = ALLOCATE_OBJ(vm, ObjAbstract, OBJ_ABSTRACT); + abstract->data = NULL; + abstract->func = func; + abstract->type = type; + abstract->grayFunc = NULL; + abstract->excludeSelf = true; initTable(&abstract->values); return abstract; @@ -550,21 +564,21 @@ ObjDict *classToDict(DictuVM *vm, Value value) { push(vm, OBJ_VAL(methodsList)); while (klass != NULL) { - for (int i = 0; i < klass->variables.capacityMask + 1; i++) { + for (int i = 0; i < klass->variables.capacity; i++) { if (klass->variables.entries[i].key == NULL) { continue; } dictSet(vm, variablesDict, OBJ_VAL(klass->variables.entries[i].key), klass->variables.entries[i].value); } - for (int i = 0; i < klass->constants.capacityMask + 1; i++) { + for (int i = 0; i < klass->constants.capacity; i++) { if (klass->constants.entries[i].key == NULL) { continue; } dictSet(vm, constantsDict, OBJ_VAL(klass->constants.entries[i].key), klass->constants.entries[i].value); } - for (int i = 0; i < klass->publicMethods.capacityMask + 1; i++) { + for (int i = 0; i < klass->publicMethods.capacity; i++) { if (klass->publicMethods.entries[i].key == NULL) { continue; } diff --git a/src/vm/object.h b/src/vm/object.h index f316747f..69ff5cc0 100644 --- a/src/vm/object.h +++ b/src/vm/object.h @@ -146,6 +146,7 @@ typedef struct { struct sObjDict { Obj obj; int count; + int activeCount; int capacityMask; DictItem *entries; }; @@ -180,6 +181,7 @@ struct sObjAbstract { AbstractFreeFn func; AbstractGrayFn grayFunc; AbstractTypeFn type; + bool excludeSelf; }; typedef enum { @@ -280,6 +282,7 @@ ObjSet *newSet(DictuVM *vm); ObjFile *newFile(DictuVM *vm); ObjAbstract *newAbstract(DictuVM *vm, AbstractFreeFn func, AbstractTypeFn type); +ObjAbstract *newAbstractExcludeSelf(DictuVM *vm, AbstractFreeFn func, AbstractTypeFn type); ObjResult *newResult(DictuVM *vm, ResultStatus status, Value value); diff --git a/src/vm/table.c b/src/vm/table.c index 554d97ca..e1c0e9d2 100644 --- a/src/vm/table.c +++ b/src/vm/table.c @@ -10,18 +10,18 @@ void initTable(Table *table) { table->count = 0; - table->capacityMask = -1; + table->capacity = 0; table->entries = NULL; } void freeTable(DictuVM *vm, Table *table) { - FREE_ARRAY(vm, Entry, table->entries, table->capacityMask + 1); + FREE_ARRAY(vm, Entry, table->entries, table->capacity); initTable(table); } static Entry *findEntry(Entry *entries, int capacityMask, ObjString *key) { - uint32_t index = key->hash & capacityMask; + uint32_t index = key->hash & (capacityMask - 1); Entry *tombstone = NULL; for (;;) { @@ -40,14 +40,14 @@ static Entry *findEntry(Entry *entries, int capacityMask, return entry; } - index = (index + 1) & capacityMask; + index = (index + 1) & (capacityMask - 1); } } bool tableGet(Table *table, ObjString *key, Value *value) { if (table->count == 0) return false; - Entry *entry = findEntry(table->entries, table->capacityMask, key); + Entry *entry = findEntry(table->entries, table->capacity, key); if (entry->key == NULL) return false; *value = entry->value; @@ -55,15 +55,15 @@ bool tableGet(Table *table, ObjString *key, Value *value) { } static void adjustCapacity(DictuVM *vm, Table *table, int capacityMask) { - Entry *entries = ALLOCATE(vm, Entry, capacityMask + 1); - for (int i = 0; i <= capacityMask; i++) { + Entry *entries = ALLOCATE(vm, Entry, capacityMask); + for (int i = 0; i < capacityMask; i++) { entries[i].key = NULL; entries[i].value = NIL_VAL; } table->count = 0; - for (int i = 0; i <= table->capacityMask; i++) { + for (int i = 0; i < table->capacity; i++) { Entry *entry = &table->entries[i]; if (entry->key == NULL) continue; @@ -73,19 +73,18 @@ static void adjustCapacity(DictuVM *vm, Table *table, int capacityMask) { table->count++; } - FREE_ARRAY(vm, Entry, table->entries, table->capacityMask + 1); + FREE_ARRAY(vm, Entry, table->entries, table->capacity); table->entries = entries; - table->capacityMask = capacityMask; + table->capacity = capacityMask; } bool tableSet(DictuVM *vm, Table *table, ObjString *key, Value value) { - if (table->count + 1 > (table->capacityMask + 1) * TABLE_MAX_LOAD) { - // Figure out the new table size. - int capacityMask = GROW_CAPACITY(table->capacityMask + 1) - 1; - adjustCapacity(vm, table, capacityMask); + if (table->count + 1 > table->capacity * TABLE_MAX_LOAD) { + int capacity = GROW_CAPACITY(table->capacity); + adjustCapacity(vm, table, capacity); } - Entry *entry = findEntry(table->entries, table->capacityMask, key); + Entry *entry = findEntry(table->entries, table->capacity, key); bool isNewKey = entry->key == NULL; entry->key = key; entry->value = value; @@ -99,19 +98,18 @@ bool tableDelete(DictuVM *vm, Table *table, ObjString *key) { UNUSED(vm); if (table->count == 0) return false; - Entry *entry = findEntry(table->entries, table->capacityMask, key); + // Find the entry. + Entry *entry = findEntry(table->entries, table->capacity, key); if (entry->key == NULL) return false; // Place a tombstone in the entry. - table->count--; entry->key = NULL; entry->value = BOOL_VAL(true); - return true; } void tableAddAll(DictuVM *vm, Table *from, Table *to) { - for (int i = 0; i <= from->capacityMask; i++) { + for (int i = 0; i < from->capacity; i++) { Entry *entry = &from->entries[i]; if (entry->key != NULL) { tableSet(vm, to, entry->key, entry->value); @@ -127,13 +125,12 @@ ObjString *tableFindString(Table *table, const char *chars, int length, // Figure out where to insert it in the table. Use open addressing and // basic linear probing. - - uint32_t index = hash & table->capacityMask; + uint32_t index = hash & (table->capacity - 1); for (;;) { Entry *entry = &table->entries[index]; - if (entry->key == NULL) { + // Stop if we find an empty non-tombstone entry. if (IS_NIL(entry->value)) return NULL; } else if (entry->key->length == length && entry->key->hash == hash && @@ -142,13 +139,12 @@ ObjString *tableFindString(Table *table, const char *chars, int length, return entry->key; } - // Try the next slot. - index = (index + 1) & table->capacityMask; + index = (index + 1) & (table->capacity - 1); } } void tableRemoveWhite(DictuVM *vm, Table *table) { - for (int i = 0; i <= table->capacityMask; i++) { + for (int i = 0; i < table->capacity; i++) { Entry *entry = &table->entries[i]; if (entry->key != NULL && !entry->key->obj.isDark) { tableDelete(vm, table, entry->key); @@ -157,7 +153,7 @@ void tableRemoveWhite(DictuVM *vm, Table *table) { } void grayTable(DictuVM *vm, Table *table) { - for (int i = 0; i <= table->capacityMask; i++) { + for (int i = 0; i < table->capacity; i++) { Entry *entry = &table->entries[i]; grayObject(vm, (Obj *) entry->key); grayValue(vm, entry->value); diff --git a/src/vm/table.h b/src/vm/table.h index a4e9cbff..d42e577f 100644 --- a/src/vm/table.h +++ b/src/vm/table.h @@ -14,7 +14,7 @@ typedef struct { typedef struct { int count; - int capacityMask; + int capacity; Entry *entries; } Table; diff --git a/src/vm/value.c b/src/vm/value.c index 6e86bf1c..d06e696b 100644 --- a/src/vm/value.c +++ b/src/vm/value.c @@ -147,7 +147,10 @@ bool dictSet(DictuVM *vm, ObjDict *dict, Value key, Value value) { entry->key = key; entry->value = value; - if (isNewKey) dict->count++; + if (isNewKey) { + dict->activeCount++; + dict->count++; + } return isNewKey; } @@ -159,7 +162,7 @@ bool dictDelete(DictuVM *vm, ObjDict *dict, Value key) { if (IS_EMPTY(entry->key)) return false; // Place a tombstone in the entry. - dict->count--; + dict->activeCount--; entry->key = EMPTY_VAL; entry->value = BOOL_VAL(true); @@ -429,12 +432,12 @@ static bool dictComparison(Value a, Value b) { ObjDict *dictB = AS_DICT(b); // Different lengths, not the same - if (dict->count != dictB->count) + if (dict->activeCount != dictB->activeCount) return false; // Lengths are the same, and dict 1 has 0 length // therefore both are empty - if (dict->count == 0) + if (dict->activeCount == 0) return true; for (int i = 0; i <= dict->capacityMask; ++i) { diff --git a/src/vm/vm.c b/src/vm/vm.c index 8819d190..1dc07fb9 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -1,8 +1,8 @@ +#include #include #include #include #include -#include #include "common.h" #include "compiler.h" @@ -51,6 +51,8 @@ static void resetStack(DictuVM *vm) { unpack = false; \ } +#define INSTANCE_HAS_NO_ATTR_ERR RUNTIME_ERROR("'%s' instance has no attribute: '%s'.", instance->klass->name->chars, name->chars) + void runtimeError(DictuVM *vm, const char *format, ...) { for (int i = vm->frameCount - 1; i >= 0; i--) { CallFrame *frame = &vm->frames[i]; @@ -363,6 +365,19 @@ static bool callNativeMethod(DictuVM *vm, Value method, int argCount) { return true; } +static bool callNativeMethodExcludeSelf(DictuVM *vm, Value method, int argCount) { + NativeFn native = AS_NATIVE(method); + + Value result = native(vm, argCount, vm->stackTop - (argCount-1) - 1); + + if (IS_EMPTY(result)) + return false; + + vm->stackTop -= argCount + 1; + push(vm, result); + return true; +} + static bool invokeFromClass(DictuVM *vm, ObjClass *klass, ObjString *name, int argCount, bool unpack) { HANDLE_UNPACK @@ -643,6 +658,8 @@ static bool invoke(DictuVM *vm, ObjString *name, int argCount, bool unpack) { Value value; if (tableGet(&abstract->values, name, &value)) { + if(abstract->excludeSelf) + return callNativeMethodExcludeSelf(vm, value, argCount); return callNativeMethod(vm, value, argCount); } @@ -804,6 +821,7 @@ bool isFalsey(Value value) { (IS_STRING(value) && AS_CSTRING(value)[0] == '\0') || (IS_LIST(value) && AS_LIST(value)->values.count == 0) || (IS_DICT(value) && AS_DICT(value)->count == 0) || + (IS_RESULT(value) && AS_RESULT(value)->status == ERR) || (IS_SET(value) && AS_SET(value)->count == 0); } @@ -1139,7 +1157,7 @@ static DictuInterpretResult run(DictuVM *vm) { RUNTIME_ERROR("Cannot access private attribute '%s' on '%s' instance.", name->chars, instance->klass->name->chars); } - RUNTIME_ERROR("'%s' instance has no attribute: '%s'.", instance->klass->name->chars, name->chars); + INSTANCE_HAS_NO_ATTR_ERR; } case OBJ_MODULE: { @@ -1155,6 +1173,18 @@ static DictuInterpretResult run(DictuVM *vm) { RUNTIME_ERROR("'%s' module has no attribute: '%s'.", module->name->chars, name->chars); } + case OBJ_ABSTRACT: { + ObjAbstract *abstract = AS_ABSTRACT(receiver); + ObjString *name = READ_STRING(); + Value value; + if (tableGet(&abstract->values, name, &value)) { + pop(vm); // Abstract. + push(vm, value); + DISPATCH(); + } + RUNTIME_ERROR("'no attribute: '%s'.",name->chars); + } + case OBJ_CLASS: { ObjClass *klass = AS_CLASS(receiver); // Used to keep a reference to the class for the runtime error below @@ -1259,7 +1289,7 @@ static DictuInterpretResult run(DictuVM *vm) { klass = klass->superclass; } - RUNTIME_ERROR("'%s' instance has no attribute1: '%s'.", instance->klass->name->chars, name->chars); + INSTANCE_HAS_NO_ATTR_ERR; } else if (IS_CLASS(peek(vm, 0))) { ObjClass *klass = AS_CLASS(peek(vm, 0)); // Used to keep a reference to the class for the runtime error below @@ -1369,7 +1399,7 @@ static DictuInterpretResult run(DictuVM *vm) { klass = klass->superclass; } - RUNTIME_ERROR("'%s' instance has no attribute3: '%s'.", instance->klass->name->chars, name->chars); + INSTANCE_HAS_NO_ATTR_ERR; } CASE_CODE(SET_ATTRIBUTE): { @@ -1643,7 +1673,11 @@ static DictuInterpretResult run(DictuVM *vm) { char path[PATH_MAX]; if (!resolvePath(frame->closure->function->module->path->chars, fileName->chars, path)) { - RUNTIME_ERROR("Could not open file \"%s\".", fileName->chars); + // if stdlib import is not found, try to load from the project's modules directory + + if (!resolvePath("dictu_modules", fileName->chars, path)) { + RUNTIME_ERROR("Could not open file \"%s\".", fileName->chars); + } } ObjString *pathObj = copyString(vm, path, strlen(path)); @@ -1871,9 +1905,10 @@ static DictuInterpretResult run(DictuVM *vm) { index = string->length + index; if (index >= 0 && index < string->length) { + ObjString *newString = copyString(vm, &string->chars[index], 1); pop(vm); pop(vm); - push(vm, OBJ_VAL(copyString(vm, &string->chars[index], 1))); + push(vm, OBJ_VAL(newString)); DISPATCH(); } @@ -2276,7 +2311,7 @@ static DictuInterpretResult run(DictuVM *vm) { ObjClass *klass = AS_CLASS(peek(vm, 0)); // If super class is abstract, ensure we have defined all abstract methods - for (int i = 0; i < klass->abstractMethods.capacityMask + 1; i++) { + for (int i = 0; i < klass->abstractMethods.capacity; i++) { if (klass->abstractMethods.entries[i].key == NULL) { continue; } diff --git a/tests/buffer/allocate.du b/tests/buffer/allocate.du new file mode 100644 index 00000000..2005c3c8 --- /dev/null +++ b/tests/buffer/allocate.du @@ -0,0 +1,27 @@ +/** +* allocate.du +* +* Testing the buffer.new() and buffer.fromString() methods +* +* .new(Number) creates a new Buffer with the given size. +* .newFromString(String) creates a Buffer from the given String. +*/ +from UnitTest import UnitTest; +import Buffer; + +class TestBufferAllocate < UnitTest { + + testBufferAllocate() { + const b = Buffer.new(10).unwrap(); + this.assertEquals(b.len(), 10); + this.assertEquals(b.values(), [0,0,0,0,0,0,0,0,0,0]); + } + testBufferAllocateString() { + const b = Buffer.fromString("Dictu!").unwrap(); + this.assertEquals(b.len(), 6); + this.assertEquals(b.string(), "Dictu!"); + } + +} + +TestBufferAllocate().run(); \ No newline at end of file diff --git a/tests/buffer/get.du b/tests/buffer/get.du new file mode 100644 index 00000000..63ab3c6a --- /dev/null +++ b/tests/buffer/get.du @@ -0,0 +1,32 @@ +/** +* get.du +* +* Testing the buffer.get() method +* +* .get(Number) returns the given value at the index. +*/ +from UnitTest import UnitTest; +import Buffer; + +class TestBufferGet < UnitTest { + testBufferGet() { + const b = Buffer.fromString("Dictu!").unwrap(); + const check = b.values(); + for(var i = 0; i < b.len(); i+= 1) { + const res = b.get(i); + this.assertEquals(res.success(), true); + this.assertEquals(res.unwrap(), check[i]); + } + } + testBufferGetOutOfBounds() { + const b = Buffer.new(4).unwrap(); + var res = b.get(4); + this.assertEquals(res.success(), false); + this.assertEquals(res.unwrapError(), "index must be smaller than buffer size"); + res = b.get(-1); + this.assertEquals(res.success(), false); + this.assertEquals(res.unwrapError(), "index must be greater than -1"); + } +} + +TestBufferGet().run(); diff --git a/tests/buffer/import.du b/tests/buffer/import.du new file mode 100644 index 00000000..b603d4a0 --- /dev/null +++ b/tests/buffer/import.du @@ -0,0 +1,15 @@ +/** +* import.du +* +* General import file for all the Buffer tests +*/ + +// buffer.values and buffer.len are used a lot throughout the tests. +import "allocate.du"; +import "set.du"; +import "get.du"; +import "resize.du"; +import "string.du"; +import "stringFuncs.du"; +import "subarray.du"; +import "integers.du"; \ No newline at end of file diff --git a/tests/buffer/integers.du b/tests/buffer/integers.du new file mode 100644 index 00000000..7a9421ad --- /dev/null +++ b/tests/buffer/integers.du @@ -0,0 +1,63 @@ +/** +* integers.du +* +* Testing the buffer integers methods. +* +*/ +from UnitTest import UnitTest; +import Buffer; + +class TestBufferIntegers < UnitTest { + runTest(size, v, f, rf) { + const buffer = Buffer.new(size).unwrap(); + var res = f(buffer, v, false); + this.assertEquals(res.success(), true); + res = rf(buffer, false); + const leArray = buffer.values(); + this.assertEquals(res.success(), true); + this.assertEquals(res.unwrap(), v); + res = f(buffer, v, true); + this.assertEquals(res.success(), true); + res = rf(buffer, true); + this.assertEquals(res.success(), true); + this.assertEquals(res.unwrap(), v); + const beArray = buffer.values(); + leArray.reverse(); + this.assertEquals(leArray, beArray); + } + + testBufferIntegers() { + + this.runTest(1, -123, def(buffer, v, bigEndian) => bigEndian ? buffer.writeInt8(0, v) : buffer.writeInt8(0, v), + def(buffer, bigEndian) => bigEndian ? buffer.readInt8(0) : buffer.readInt8(0)); + + this.runTest(2, 2000, def(buffer, v, bigEndian) => bigEndian ? buffer.writeUInt16BE(0, v) : buffer.writeUInt16LE(0, v), + def(buffer, bigEndian) => bigEndian ? buffer.readUInt16BE(0) : buffer.readUInt16LE(0)); + this.runTest(2, -4321, def(buffer, v, bigEndian) => bigEndian ? buffer.writeInt16BE(0, v) : buffer.writeInt16LE(0, v), + def(buffer, bigEndian) => bigEndian ? buffer.readInt16BE(0) : buffer.readInt16LE(0)); + + this.runTest(4, 2300, def(buffer, v, bigEndian) => bigEndian ? buffer.writeUInt32BE(0, v) : buffer.writeUInt32LE(0, v), + def(buffer, bigEndian) => bigEndian ? buffer.readUInt32BE(0) : buffer.readUInt32LE(0)); + this.runTest(4, -2345, def(buffer, v, bigEndian) => bigEndian ? buffer.writeInt32BE(0, v) : buffer.writeInt32LE(0, v), + def(buffer, bigEndian) => bigEndian ? buffer.readInt32BE(0) : buffer.readInt32LE(0)); + + this.runTest(8, 20000000, def(buffer, v, bigEndian) => bigEndian ? buffer.writeUInt64BE(0, v) : buffer.writeUInt64LE(0, v), + def(buffer, bigEndian) => bigEndian ? buffer.readUInt64BE(0) : buffer.readUInt64LE(0)); + this.runTest(8, -20000000, def(buffer, v, bigEndian) => bigEndian ? buffer.writeInt64BE(0, v) : buffer.writeInt64LE(0, v), + def(buffer, bigEndian) => bigEndian ? buffer.readInt64BE(0) : buffer.readInt64LE(0)); + + this.runTest(8, 234.34534, def(buffer, v, bigEndian) => bigEndian ? buffer.writeDoubleBE(0, v) : buffer.writeDoubleLE(0, v), + def(buffer, bigEndian) => bigEndian ? buffer.readDoubleBE(0) : buffer.readDoubleLE(0)); + this.runTest(8, -234.34534, def(buffer, v, bigEndian) => bigEndian ? buffer.writeDoubleBE(0, v) : buffer.writeDoubleLE(0, v), + def(buffer, bigEndian) => bigEndian ? buffer.readDoubleBE(0) : buffer.readDoubleLE(0)); + + this.runTest(4, 1, def(buffer, v, bigEndian) => bigEndian ? buffer.writeFloatBE(0, v) : buffer.writeFloatLE(0, v), + def(buffer, bigEndian) => bigEndian ? buffer.readFloatBE(0) : buffer.readFloatLE(0)); + this.runTest(4, -1, def(buffer, v, bigEndian) => bigEndian ? buffer.writeFloatBE(0, v) : buffer.writeFloatLE(0, v), + def(buffer, bigEndian) => bigEndian ? buffer.readFloatBE(0) : buffer.readFloatLE(0)); + + } + +} + +TestBufferIntegers().run(); diff --git a/tests/buffer/resize.du b/tests/buffer/resize.du new file mode 100644 index 00000000..b27e3755 --- /dev/null +++ b/tests/buffer/resize.du @@ -0,0 +1,36 @@ +/** +* resize.du +* +* Testing the buffer.resize() methods +* +* .resize(Number) resizes the Buffer to the given size +*/ +from UnitTest import UnitTest; +import Buffer; + +class TestBufferResize < UnitTest { + + testBufferResize() { + const b = Buffer.new(10).unwrap(); + this.assertEquals(b.len(), 10); + this.assertEquals(b.values(), [0,0,0,0,0,0,0,0,0,0]); + b.resize(15); + this.assertEquals(b.len(), 15); + this.assertEquals(b.values(), [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); + b.resize(1); + this.assertEquals(b.len(), 1); + this.assertEquals(b.values(), [0]); + } + testBufferResizeOutOfBounds() { + const b = Buffer.new(5).unwrap(); + var res = b.resize(-1); + this.assertEquals(res.success(), false); + this.assertEquals(res.unwrapError(), "size must be greater than 0 and smaller than 2147483647"); + res = b.resize(2147483647); + this.assertEquals(res.success(), false); + this.assertEquals(res.unwrapError(), "size must be greater than 0 and smaller than 2147483647"); + } + +} + +TestBufferResize().run(); diff --git a/tests/buffer/set.du b/tests/buffer/set.du new file mode 100644 index 00000000..5b4e12a4 --- /dev/null +++ b/tests/buffer/set.du @@ -0,0 +1,33 @@ +/** +* set.du +* +* Testing the buffer.set() method +* +* .set(Number, Number) sets a given integer value into the buffer; +*/ +from UnitTest import UnitTest; +import Buffer; + +class TestBufferSet < UnitTest { + testBufferSet() { + const b = Buffer.new(8).unwrap(); + const check = []; + for(var i = 0; i < b.len(); i+= 1) { + const res = b.set(i, i*2); + this.assertEquals(res.success(), true); + check.push(i*2); + } + this.assertEquals(b.values(), check); + } + testBufferSetOutOfBounds() { + const b = Buffer.new(4).unwrap(); + var res = b.set(4, 0); + this.assertEquals(res.success(), false); + this.assertEquals(res.unwrapError(), "index must be smaller than buffer size"); + res = b.set(-1, 0); + this.assertEquals(res.success(), false); + this.assertEquals(res.unwrapError(), "index must be greater than -1"); + } +} + +TestBufferSet().run(); diff --git a/tests/buffer/string.du b/tests/buffer/string.du new file mode 100644 index 00000000..ee4a4a4a --- /dev/null +++ b/tests/buffer/string.du @@ -0,0 +1,19 @@ +/** +* string.du +* +* Testing the buffer.string() method +* +* .string() returns a string of the buffer. +*/ +from UnitTest import UnitTest; +import Buffer; + +class TestBufferString < UnitTest { + + testBufferString() { + const b = Buffer.fromString("Dictu!").unwrap(); + this.assertEquals(b.string(), "Dictu!"); + } +} + +TestBufferString().run(); diff --git a/tests/buffer/stringFuncs.du b/tests/buffer/stringFuncs.du new file mode 100644 index 00000000..6e156c14 --- /dev/null +++ b/tests/buffer/stringFuncs.du @@ -0,0 +1,49 @@ +/** +* stringFuncs.du +* +* Testing the buffer.readString() and buffer.writeString() methods +* +* .writeString(Number -> Optional) writes a string into the buffer +* .readString(Number -> Optional, Number -> Optional) returns a result of the string given optionally index and end. +*/ +from UnitTest import UnitTest; +import Buffer; + +class TestBufferStringFuncs < UnitTest { + + testBufferReadString() { + const b = Buffer.fromString("Dictu!").unwrap(); + var res = b.readString(0, b.len()-1); + this.assertEquals(res.success(), true); + this.assertEquals(res.unwrap(), "Dictu"); + res = b.readString(2); + this.assertEquals(res.success(), true); + this.assertEquals(res.unwrap(), "ctu!"); + } + testBufferWriteString() { + const b = Buffer.new(10).unwrap(); + const res = b.writeString(0, "Dictu!"); + this.assertEquals(res.success(), true); + } + testBufferWriteStringOutOfBounds() { + const b = Buffer.new(4).unwrap(); + const res = b.writeString(0, "Dictu!"); + this.assertEquals(res.success(), false); + this.assertEquals(res.unwrapError(), "buffer is not large enough to fit the string"); + } + testBufferReadStringOutOfBounds() { + const b = Buffer.fromString("Dictu!").unwrap(); + var res = b.readString(23); + this.assertEquals(res.success(), false); + this.assertEquals(res.unwrapError(), "start greater or equals than buffer length"); + res = b.readString(3,3); + this.assertEquals(res.success(), false); + this.assertEquals(res.unwrapError(), "string length is 0"); + res = b.readString(3,34); + this.assertEquals(res.success(), false); + this.assertEquals(res.unwrapError(), "end greater than buffer length"); + } + +} + +TestBufferStringFuncs().run(); diff --git a/tests/buffer/subarray.du b/tests/buffer/subarray.du new file mode 100644 index 00000000..1ac61b64 --- /dev/null +++ b/tests/buffer/subarray.du @@ -0,0 +1,46 @@ +/** +* subarray.du +* +* Testing the buffer.subarray() method. +* +* .subarray(Number -> Optional, Number -> Optional) returns a subarray given optionally the index and end. +*/ +from UnitTest import UnitTest; +import Buffer; + +class TestBufferSubArray < UnitTest { + + testBufferSubarray() { + const b = Buffer.new(10).unwrap(); + var sub = b.subarray(0); + this.assertEquals(sub.success(), true); + var v = sub.unwrap(); + this.assertEquals(v.values(), b.values()); + sub = b.subarray(5); + this.assertEquals(sub.success(), true); + v = sub.unwrap(); + this.assertEquals(v.values(), [0,0,0,0,0]); + } + testBufferSubarrayStr() { + const b = Buffer.fromString("Dictu is awesome!").unwrap(); + const sub = b.subarray(0, 5); + this.assertEquals(sub.success(), true); + const v = sub.unwrap(); + this.assertEquals(v.string(), "Dictu"); + } + testBufferSubarrayOutOfBounds() { + const b = Buffer.fromString("Dictu!").unwrap(); + var res = b.subarray(25); + this.assertEquals(res.success(), false); + this.assertEquals(res.unwrapError(), "start greater or equals than buffer length"); + res = b.subarray(0, 25); + this.assertEquals(res.success(), false); + this.assertEquals(res.unwrapError(), "end greater than buffer length"); + res = b.subarray(0, 0); + this.assertEquals(res.success(), false); + this.assertEquals(res.unwrapError(), "array length is 0"); + + } +} + +TestBufferSubArray().run(); diff --git a/tests/dicts/import.du b/tests/dicts/import.du index f755da74..671f5f69 100644 --- a/tests/dicts/import.du +++ b/tests/dicts/import.du @@ -15,3 +15,4 @@ import "toString.du"; import "toBool.du"; import "forEach.du"; import "merge.du"; +import "toObj.du"; diff --git a/tests/dicts/toObj.du b/tests/dicts/toObj.du new file mode 100644 index 00000000..ea60dae9 --- /dev/null +++ b/tests/dicts/toObj.du @@ -0,0 +1,34 @@ +/** +* toObj.du +* +* Testing the dict.toObj() method +* +* .toObj() returns an object created from the given dict and class ref. +*/ +from UnitTest import UnitTest; + + +class Test {} + +class TestDictToObj < UnitTest { + testDictToObj() { + const myDict = { + "key": 1, + "key1": true, + true: false, + false: true, + nil: 10, + 10: nil, + 10.5: 10.5 + }; + const obj = myDict.toObj(Test()); + this.assertNotNil(obj); + this.assertEquals(obj.key, 1); + this.assertEquals(obj.getAttribute("true"), false); + this.assertEquals(obj.getAttribute("false"), true); + this.assertEquals(obj.getAttribute("nil"), 10); + this.assertEquals(obj.getAttribute("10.5"), 10.5); + } +} + +TestDictToObj().run(); \ No newline at end of file diff --git a/tests/ffi/ffi.du b/tests/ffi/ffi.du new file mode 100644 index 00000000..a072f0d4 --- /dev/null +++ b/tests/ffi/ffi.du @@ -0,0 +1,55 @@ +/** + * ffi.du + * + * Testing the ffi api + +Used c code start: + +#include + +Value dictu_ffi_test(DictuVM *vm, int argCount, Value *args) { + if(argCount != 2 || !IS_NUMBER(args[0]) || !IS_NUMBER(args[1])){ + return NIL_VAL; + } + double a = AS_NUMBER(args[0]); + double b = AS_NUMBER(args[1]); + return NUMBER_VAL(a+b); +} +Value dictu_ffi_test_str(DictuVM *vm, int argCount, Value *args) { + return OBJ_VAL(copyString(vm, "Hello From Dictu FFI module!", 28)); +} + +int dictu_ffi_init(DictuVM *vm, Table *method_table) { + defineNative(vm, method_table, "dictuFFITestAdd", dictu_ffi_test); + defineNative(vm, method_table, "dictuFFITestStr", dictu_ffi_test_str); + defineNativeProperty( + vm, method_table, "test", + OBJ_VAL(copyString(vm, "Dictu!", 6))); + return 0; +} + +Used c code end +*/ + +import System; + +from UnitTest import UnitTest; +import FFI; +import Path; + +class TestFFIModule < UnitTest { + testFFIModule() { + const path = Path.join(Path.dirname(__file__), "libs", "test-lib{}{}".format( + System.platform == "darwin" ? System.arch == "x86_64" ? "_64" : "_arm" : "", + FFI.suffix + )); + const mod = FFI.load(path); + this.assertEquals(mod.test, "Dictu!"); + for(var i = 0; i < 40; i+=2) { + this.assertEquals(mod.dictuFFITestAdd(i, i), i+i); + } + this.assertEquals(mod.dictuFFITestStr(), "Hello From Dictu FFI module!"); + } +} + +TestFFIModule().run(); diff --git a/tests/ffi/import.du b/tests/ffi/import.du new file mode 100644 index 00000000..37aebb32 --- /dev/null +++ b/tests/ffi/import.du @@ -0,0 +1,6 @@ +/** + * import.du + * + * General import file for the FFI module tests + */ +import "ffi.du"; diff --git a/tests/ffi/libs/test-lib.dll b/tests/ffi/libs/test-lib.dll new file mode 100644 index 00000000..e1f14b94 Binary files /dev/null and b/tests/ffi/libs/test-lib.dll differ diff --git a/tests/ffi/libs/test-lib.so b/tests/ffi/libs/test-lib.so new file mode 100755 index 00000000..cce02b91 Binary files /dev/null and b/tests/ffi/libs/test-lib.so differ diff --git a/tests/ffi/libs/test-lib_64.dylib b/tests/ffi/libs/test-lib_64.dylib new file mode 100755 index 00000000..84887d6b Binary files /dev/null and b/tests/ffi/libs/test-lib_64.dylib differ diff --git a/tests/ffi/libs/test-lib_arm.dylib b/tests/ffi/libs/test-lib_arm.dylib new file mode 100755 index 00000000..082eceff Binary files /dev/null and b/tests/ffi/libs/test-lib_arm.dylib differ diff --git a/tests/io/copyFile.du b/tests/io/copyFile.du index 60d38836..2048aa59 100644 --- a/tests/io/copyFile.du +++ b/tests/io/copyFile.du @@ -48,6 +48,19 @@ class TestSystemCopyFile < UnitTest { this.assertNotNil(res); this.assertSuccess(res); } + + testCopyFileToDevNull() { + with(Path.join(this.tmpDir, srcFile), 'w') { + file.write("lots and lots of temp data!"); + } + + const srcFullPath = Path.join(this.tmpDir, srcFile); + + const res = IO.copyFile(srcFullPath, IO.devNull); + + this.assertNotNil(res); + this.assertSuccess(res); + } } TestSystemCopyFile().run(); \ No newline at end of file diff --git a/tests/lists/import.du b/tests/lists/import.du index bd51de8e..1640beb6 100644 --- a/tests/lists/import.du +++ b/tests/lists/import.du @@ -23,6 +23,7 @@ import "remove.du"; import "reverse.du"; import "slicing.du"; import "sort.du"; +import "sortFunc.du"; import "splice.du"; import "subscript.du"; import "toBool.du"; diff --git a/tests/lists/insert.du b/tests/lists/insert.du index c8e7d090..0883da17 100644 --- a/tests/lists/insert.du +++ b/tests/lists/insert.du @@ -23,6 +23,12 @@ class TestListInsert < UnitTest { x.insert(0, 0); this.assertEquals(x, [0, 1, 2, 3, 4, 5, 6, 7]); + + x.insert(8, -1); + this.assertEquals(x, [0, 1, 2, 3, 4, 5, 6, 7, 8]); + + x.insert(28, -4); + this.assertEquals(x, [0, 1, 2, 3, 4, 5, 28, 6, 7, 8]); } } diff --git a/tests/lists/pop.du b/tests/lists/pop.du index 04e73c3a..5d34d336 100644 --- a/tests/lists/pop.du +++ b/tests/lists/pop.du @@ -23,6 +23,14 @@ class TestListPop < UnitTest { y = x.pop(0); this.assertEquals(y, 1); this.assertEquals(x, [2, 3, 4]); + + y = x.pop(-2); + this.assertEquals(y, 3); + this.assertEquals(x, [2, 4]); + + y = x.pop(-2); + this.assertEquals(y, 2); + this.assertEquals(x, [4]); } } diff --git a/tests/lists/sortFunc.du b/tests/lists/sortFunc.du new file mode 100644 index 00000000..d05d381c --- /dev/null +++ b/tests/lists/sortFunc.du @@ -0,0 +1,24 @@ +/** + * sortFunc.du + * + * Testing list sorting with custom callback + */ +from UnitTest import UnitTest; +class A { + init(var name, var n) {} +} + +class TestListSortFunc < UnitTest { + testListSortCallback() { + var list1 = [[1, "Dictu"], [-1, "SomeValue"], [5, "!"], [4, "Awesome"], [2, "is"]]; + list1.sortFunc(def(a,b) => a[0] - b[0]); + this.assertEquals(list1, [[-1, "SomeValue"], [1, "Dictu"], [2, "is"], [4, "Awesome"], [5, "!"]]); + } + testListSortCallbackClass() { + var list = [A("T", 10), A("U", 15), A("C", 5), A("D", 1), A("I", 3)]; + list.sortFunc(def(a,b) => a.n - b.n); + this.assertEquals(list.map(def (entry) => entry.n), [1, 3, 5, 10, 15]); + this.assertEquals(list.map(def (entry) => entry.name), ["D", "I", "C", "T", "U"]); + } +} +TestListSortFunc().run(); \ No newline at end of file diff --git a/tests/process/exec.du b/tests/process/exec.du index 853162bd..835c0805 100644 --- a/tests/process/exec.du +++ b/tests/process/exec.du @@ -25,4 +25,4 @@ class TestProcessExec < UnitTest { } } -TestProcessExec().run(); \ No newline at end of file +TestProcessExec().run(); diff --git a/tests/process/import.du b/tests/process/import.du index 894e9ea7..158164f6 100644 --- a/tests/process/import.du +++ b/tests/process/import.du @@ -5,4 +5,5 @@ */ import "exec.du"; -import "run.du"; \ No newline at end of file +import "kill.du"; +import "run.du"; diff --git a/tests/process/kill.du b/tests/process/kill.du new file mode 100644 index 00000000..c528e31e --- /dev/null +++ b/tests/process/kill.du @@ -0,0 +1,47 @@ +/** + * kill.du + * + * Testing the Process.kill() function + * + * kill() receives a PID as a Number and optional signal, as a Number (on *NIX) to be passed to the process. + */ +from UnitTest import UnitTest; + +import IO; +import Process; +import System; + +class TestProcessKill < UnitTest { + testProcessKillError() { + const res = Process.kill(9999999999999999).unwrapError(); + this.assertEquals(res, "No such process"); + } + + // TODO: Look into this test + testProcessKillNoSignal_skipped() { + Process.exec(["sleep", "100"]); + + const out = Process.run(["pgrep", "sleep"], true).unwrap(); + const pids = out.split("\n"); + + if (pids.len() > 1) { + for (var i = 0; i < pids.len(); i += 1) { + var pid = pids[i].replace("\n", ""); + pid = pid.toNumber().unwrap(); + const res = Process.kill(pid, 0); + this.assertSuccess(res); + this.assertNil(res.unwrap()); + } + } else { + var pid = pids[0].replace("\n", ""); + pid = pid.toNumber().unwrap(); + const res = Process.kill(pid, 0); + this.assertSuccess(res); + this.assertNil(res.unwrap()); + } + } +} + +if (System.platform != "windows") { + TestProcessKill().run(); +} diff --git a/tests/runTests.du b/tests/runTests.du index b239da43..d6da7d06 100644 --- a/tests/runTests.du +++ b/tests/runTests.du @@ -53,6 +53,8 @@ import "random/import.du"; import "hashlib/import.du"; import "object/import.du"; import "term/import.du"; +import "buffer/import.du"; +import "ffi/import.du"; // If we got here no runtime errors were thrown, therefore all tests passed. print("All tests passed successfully!"); diff --git a/tests/sets/import.du b/tests/sets/import.du index 523e86d5..d936e64e 100644 --- a/tests/sets/import.du +++ b/tests/sets/import.du @@ -14,3 +14,4 @@ import "len.du"; import "toString.du"; import "toBool.du"; import "containsAll.du"; +import "values.du"; \ No newline at end of file diff --git a/tests/sets/values.du b/tests/sets/values.du new file mode 100644 index 00000000..a8e771b7 --- /dev/null +++ b/tests/sets/values.du @@ -0,0 +1,33 @@ +/** + * values.du + * + * Testing the set.values() method + * + * .values() returns a list of all values in the set + */ +from UnitTest import UnitTest; + +class TestSetValues < UnitTest { + testSetValues() { + const s = set(); + s.add("dictu"); + var values = s.values(); + this.assertEquals(values.len(), 1); + this.assertEquals(values[0], "dictu"); + + s.add("!"); + values = s.values(); + this.assertTruthy(values.contains("dictu")); + this.assertTruthy(values.contains("!")); + + + s.add(22); + values = s.values(); + this.assertTruthy(values.contains("dictu")); + this.assertTruthy(values.contains("!")); + this.assertTruthy(values.contains(22)); + + } +} + +TestSetValues().run(); diff --git a/tests/strings/collapseSpaces.du b/tests/strings/collapseSpaces.du new file mode 100644 index 00000000..d885ee3f --- /dev/null +++ b/tests/strings/collapseSpaces.du @@ -0,0 +1,20 @@ +/** + * collapseSpaces.du + * + * Testing the str.collapseSpaces() method + * + * .collapseSpaces() returns a string with extraneous spaces removed. + */ +from UnitTest import UnitTest; + +class TestStringCollapseSpaces < UnitTest { + testStringCollapseSpaces() { + const testString = "This is a huge string of a lot of spaces."; + const expected = "This is a huge string of a lot of spaces."; + const res = testString.collapseSpaces(); + this.assertEquals(res, expected); + this.assertNotEquals(testString, expected); + } +} + +TestStringCollapseSpaces().run(); diff --git a/tests/strings/concat.du b/tests/strings/concat.du index c25a5f76..9518698c 100644 --- a/tests/strings/concat.du +++ b/tests/strings/concat.du @@ -17,4 +17,4 @@ class TestStringConcat < UnitTest { } } -TestStringConcat().run(); \ No newline at end of file +TestStringConcat().run(); diff --git a/tests/strings/findLast.du b/tests/strings/findLast.du new file mode 100644 index 00000000..1f7d04ea --- /dev/null +++ b/tests/strings/findLast.du @@ -0,0 +1,17 @@ +/** + * findLast.du + * + * Testing the str.findLast() method + * + * .findLast() returns the last index of the given string + */ +from UnitTest import UnitTest; + +class TestStringLastIndexOf < UnitTest { + testStringLower() { + this.assertEquals("woolly woolly mammoth".findLast("woolly"), 7); + this.assertEquals("mammoth".findLast("woolly"), -1); + } +} + +TestStringLastIndexOf().run(); \ No newline at end of file diff --git a/tests/strings/import.du b/tests/strings/import.du index 151d4745..766fc8ec 100644 --- a/tests/strings/import.du +++ b/tests/strings/import.du @@ -12,6 +12,7 @@ import "concat.du"; import "startsWith.du"; import "endsWith.du"; import "find.du"; +import "findLast.du"; import "contains.du"; import "strip.du"; import "format.du"; @@ -25,3 +26,8 @@ import "toBool.du"; import "escapeCodes.du"; import "repeat.du"; import "title.du"; +import "isUpper.du"; +import "isLower.du"; +import "wordCount.du"; +import "collapseSpaces.du"; +import "wrap.du"; diff --git a/tests/strings/isLower.du b/tests/strings/isLower.du new file mode 100644 index 00000000..5b94fd34 --- /dev/null +++ b/tests/strings/isLower.du @@ -0,0 +1,21 @@ +/** + * isLower.du + * + * Testing the str.isLower() method + * + * .isLower() returns a boolean indicating that the given string is + * an lower case letter. + */ +from UnitTest import UnitTest; + +class TestStringIsLower < UnitTest { + testStringIsLower() { + this.assertTruthy("d".isLower()); + this.assertTruthy("dog".isLower()); + this.assertTruthy("g00d!".isLower()); + this.assertFalsey("D".isLower()); + this.assertFalsey("Maple".isLower()); + } +} + +TestStringIsLower().run(); diff --git a/tests/strings/isUpper.du b/tests/strings/isUpper.du new file mode 100644 index 00000000..f7d3294c --- /dev/null +++ b/tests/strings/isUpper.du @@ -0,0 +1,20 @@ +/** + * isUpper.du + * + * Testing the str.isUpper() method + * + * .isUpper() returns a boolean indicating that the given string is + * an upper case letter. + */ +from UnitTest import UnitTest; + +class TestStringIsUpper < UnitTest { + testStringIsUpper() { + this.assertTruthy("D".isUpper()); + this.assertTruthy("DOG".isUpper()); + this.assertTruthy("G00D!".isUpper()); + this.assertFalsey("Maple".isUpper()); + } +} + +TestStringIsUpper().run(); diff --git a/tests/strings/wordCount.du b/tests/strings/wordCount.du new file mode 100644 index 00000000..6f93cfde --- /dev/null +++ b/tests/strings/wordCount.du @@ -0,0 +1,19 @@ +/** + * wordCount.du + * + * Testing the str.wordCount() method + * + * .wordCount() returns the number of words in the given string. + */ +from UnitTest import UnitTest; + +class TestStringWordCount < UnitTest { + testStringWordCount() { + this.assertEquals("".wordCount(), 0); + this.assertEquals("This".wordCount(), 1); + this.assertEquals("This is a sentence".wordCount(), 4); + this.assertEquals("This is an even longer sentence".wordCount(), 6); + } +} + +TestStringWordCount().run(); diff --git a/tests/strings/wrap.du b/tests/strings/wrap.du new file mode 100644 index 00000000..442b0465 --- /dev/null +++ b/tests/strings/wrap.du @@ -0,0 +1,21 @@ +/** + * wrap.du + * + * Testing the str.wrap() method + * + * .wrap() returns a new string with new lines inserted at the given length. + */ +from UnitTest import UnitTest; + +class TestStringWrap < UnitTest { + const maxLen = 80; + + testStringWrap() { + const testString = "This is a really really long string that will need to be broken up for whatever reason the caller has determined. Not out business as to why, but we can provide the functionality for them to do so."; + const res = testString.wrap(this.maxLen); + const idx = res.find("\n"); + this.assertTruthy(res.find("\n") <= this.maxLen); + } +} + +TestStringWrap().run(); diff --git a/tests/system/constants.du b/tests/system/constants.du index 59450051..91e91a5e 100644 --- a/tests/system/constants.du +++ b/tests/system/constants.du @@ -26,6 +26,14 @@ class TestSystemConstants < UnitTest { this.assertType(System.platform, 'string'); this.assertTruthy(System.platform.len() > 0); } + + /** + * arch stores the current system archictecture as a string + */ + testSysArch() { + this.assertType(System.arch, 'string'); + this.assertTruthy(System.arch.len() > 0); + } } TestSystemConstants().run(); \ No newline at end of file diff --git a/tests/system/import.du b/tests/system/import.du index 218f88fb..e00da45f 100644 --- a/tests/system/import.du +++ b/tests/system/import.du @@ -16,6 +16,7 @@ import "process.du"; import "mkdir.du"; import "constants.du"; import "chmod.du"; +import "rename.du"; import "chown.du"; import "uname.du"; import "mkdirTemp.du"; diff --git a/tests/system/rename.du b/tests/system/rename.du new file mode 100644 index 00000000..6e6ee24f --- /dev/null +++ b/tests/system/rename.du @@ -0,0 +1,28 @@ +/** + * rename.du + * + * Testing the System.rename() function + * + * rename() renames a file from the system + */ +from UnitTest import UnitTest; + +import System; + +class TestSystemRename < UnitTest { + setUp() { + with ("old_file", "w") { + file.write("test"); + } + } + + tearDown() { + System.remove("new_file"); + } + + testSystemRename() { + this.assertTruthy(System.rename("old_file", "new_file").success()); + } +} + +TestSystemRename().run(); diff --git a/vcpkg.json b/vcpkg.json index faba1783..db7a1175 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,6 @@ { "name": "dictu", - "version": "0.28.0", + "version": "0.30.0", "dependencies": [ "curl" ]