zeolite-lang 0.16.0.0 → 0.16.1.0
raw patch · 41 files changed
+818/−528 lines, 41 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- ChangeLog.md +31/−0
- base/Category_Bool.cpp +6/−3
- base/Category_Char.cpp +6/−3
- base/Category_Float.cpp +6/−3
- base/Category_Int.cpp +6/−3
- base/Category_String.cpp +6/−3
- base/category-source.hpp +4/−1
- lib/container/search-tree.0rp +19/−5
- lib/container/search-tree.0rt +88/−10
- lib/container/search-tree.0rx +65/−21
- lib/container/type-map.0rt +9/−30
- lib/container/type-map.0rx +9/−3
- lib/file/src/Extension_RawFileReader.cpp +1/−1
- lib/file/src/Extension_RawFileWriter.cpp +1/−1
- lib/file/tests.0rt +7/−21
- lib/testing/helpers.0rp +22/−0
- lib/testing/helpers.0rx +42/−15
- lib/testing/tests.0rt +71/−0
- lib/thread/.zeolite-module +0/−10
- lib/thread/barrier.0rp +2/−0
- lib/thread/mutex.0rp +0/−63
- lib/thread/mutex.0rt +0/−103
- lib/thread/src/Extension_Enumerated.cpp +32/−8
- lib/thread/src/Extension_MutexLock.cpp +0/−91
- lib/thread/src/Extension_ProcessThread.cpp +1/−1
- lib/thread/src/Extension_SimpleMutex.cpp +0/−77
- lib/thread/src/Extension_ThreadCondition.cpp +1/−1
- lib/thread/thread.0rt +64/−0
- lib/util/.zeolite-module +8/−0
- lib/util/extra.0rt +4/−12
- lib/util/input.0rt +1/−3
- lib/util/input.0rx +2/−5
- lib/util/iterator.0rt +7/−21
- lib/util/mutex.0rp +63/−0
- lib/util/mutex.0rt +50/−0
- lib/util/src/Extension_MutexLock.cpp +91/−0
- lib/util/src/Extension_SimpleMutex.cpp +77/−0
- src/CompilerCxx/CxxFiles.hs +11/−6
- src/CompilerCxx/Procedure.hs +3/−2
- src/Module/ProcessMetadata.hs +1/−1
- zeolite-lang.cabal +1/−1
ChangeLog.md view
@@ -1,5 +1,36 @@ # Revision history for zeolite-lang +## 0.16.1.0 -- 2021-04-02++### Language++* **[fix]** Fixes a regression where a race condition in function dispatching+ would cause an error saying that a category does not implement the function+ that was being called.++* **[behavior]** Optimizes calls to `@type` functions when made from `@value`+ functions of the same type.++* **[behavior]** Optimizes `@value` initialization (e.g., `Type{ }`) when done+ from `@type` functions of the same type.++### Libraries++* **[new]** Moves `Mutex`, `SimpleMutex`, and `MutexLock` from `lib/thread`+ to `lib/util`. This is because they provide basic concurrency protection to+ objects that should not need to depend on `-lpthread`.++ Note that this change *is not* breaking because `lib/util` was already a+ public dependency of `lib/thread`. In other words, if a module already used+ `lib/thread` then it got `lib/util` for free. This means that modules only+ needing mutex functionality from `lib/thread` can instead use `lib/util`.++* **[new]** Adds `getForward` and `getReverse` to `SearchTree`, to allow the+ caller to iterate after looking up an entry.++* **[new]** Adds `checkEmpty`, `checkPresent`, and `checkOptional` to `Testing`+ in `lib/testing`.+ ## 0.16.0.0 -- 2021-04-01 ### Language
base/Category_Bool.cpp view
@@ -96,7 +96,8 @@ DispatchSingle<CallType>(&GetCategory_Formatted(), &Type_Bool::Params_Formatted), DispatchSingle<CallType>(), };- const DispatchSingle<CallType>* const call = DispatchSelect(&category, all_calls);+ std::atomic_bool table_lock{0};+ const DispatchSingle<CallType>* const call = DispatchSelect(table_lock, &category, all_calls); if (call) { (this->*call->value)(args); return true;@@ -122,7 +123,8 @@ DispatchTable<CallType>(Functions_Equals, Table_Equals), DispatchTable<CallType>(), };- const DispatchTable<CallType>* const table = DispatchSelect(label.collection, all_tables);+ std::atomic_bool table_lock{0};+ const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables); if (table) { if (label.function_num < 0 || label.function_num >= table->size) { FAIL() << "Bad function call " << label;@@ -162,7 +164,8 @@ DispatchTable<CallType>(Functions_Formatted, Table_Formatted), DispatchTable<CallType>(), };- const DispatchTable<CallType>* const table = DispatchSelect(label.collection, all_tables);+ std::atomic_bool table_lock{0};+ const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables); if (table) { if (label.function_num < 0 || label.function_num >= table->size) { FAIL() << "Bad function call " << label;
base/Category_Char.cpp view
@@ -102,7 +102,8 @@ DispatchSingle<CallType>(&GetCategory_Formatted(), &Type_Char::Params_Formatted), DispatchSingle<CallType>(), };- const DispatchSingle<CallType>* const call = DispatchSelect(&category, all_calls);+ std::atomic_bool table_lock{0};+ const DispatchSingle<CallType>* const call = DispatchSelect(table_lock, &category, all_calls); if (call) { (this->*call->value)(args); return true;@@ -132,7 +133,8 @@ DispatchTable<CallType>(Functions_LessThan, Table_LessThan), DispatchTable<CallType>(), };- const DispatchTable<CallType>* const table = DispatchSelect(label.collection, all_tables);+ std::atomic_bool table_lock{0};+ const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables); if (table) { if (label.function_num < 0 || label.function_num >= table->size) { FAIL() << "Bad function call " << label;@@ -177,7 +179,8 @@ DispatchTable<CallType>(Functions_Formatted, Table_Formatted), DispatchTable<CallType>(), };- const DispatchTable<CallType>* const table = DispatchSelect(label.collection, all_tables);+ std::atomic_bool table_lock{0};+ const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables); if (table) { if (label.function_num < 0 || label.function_num >= table->size) { FAIL() << "Bad function call " << label;
base/Category_Float.cpp view
@@ -97,7 +97,8 @@ DispatchSingle<CallType>(&GetCategory_Formatted(), &Type_Float::Params_Formatted), DispatchSingle<CallType>(), };- const DispatchSingle<CallType>* const call = DispatchSelect(&category, all_calls);+ std::atomic_bool table_lock{0};+ const DispatchSingle<CallType>* const call = DispatchSelect(table_lock, &category, all_calls); if (call) { (this->*call->value)(args); return true;@@ -127,7 +128,8 @@ DispatchTable<CallType>(Functions_LessThan, Table_LessThan), DispatchTable<CallType>(), };- const DispatchTable<CallType>* const table = DispatchSelect(label.collection, all_tables);+ std::atomic_bool table_lock{0};+ const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables); if (table) { if (label.function_num < 0 || label.function_num >= table->size) { FAIL() << "Bad function call " << label;@@ -168,7 +170,8 @@ DispatchTable<CallType>(Functions_Formatted, Table_Formatted), DispatchTable<CallType>(), };- const DispatchTable<CallType>* const table = DispatchSelect(label.collection, all_tables);+ std::atomic_bool table_lock{0};+ const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables); if (table) { if (label.function_num < 0 || label.function_num >= table->size) { FAIL() << "Bad function call " << label;
base/Category_Int.cpp view
@@ -102,7 +102,8 @@ DispatchSingle<CallType>(&GetCategory_Int(), &Type_Int::Params_Int), DispatchSingle<CallType>(), };- const DispatchSingle<CallType>* const call = DispatchSelect(&category, all_calls);+ std::atomic_bool table_lock{0};+ const DispatchSingle<CallType>* const call = DispatchSelect(table_lock, &category, all_calls); if (call) { (this->*call->value)(args); return true;@@ -132,7 +133,8 @@ DispatchTable<CallType>(Functions_LessThan, Table_LessThan), DispatchTable<CallType>(), };- const DispatchTable<CallType>* const table = DispatchSelect(label.collection, all_tables);+ std::atomic_bool table_lock{0};+ const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables); if (table) { if (label.function_num < 0 || label.function_num >= table->size) { FAIL() << "Bad function call " << label;@@ -177,7 +179,8 @@ DispatchTable<CallType>(Functions_Formatted, Table_Formatted), DispatchTable<CallType>(), };- const DispatchTable<CallType>* const table = DispatchSelect(label.collection, all_tables);+ std::atomic_bool table_lock{0};+ const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables); if (table) { if (label.function_num < 0 || label.function_num >= table->size) { FAIL() << "Bad function call " << label;
base/Category_String.cpp view
@@ -109,7 +109,8 @@ DispatchSingle<CallType>(&GetCategory_DefaultOrder(), &Type_String::Params_DefaultOrder), DispatchSingle<CallType>(), };- const DispatchSingle<CallType>* const call = DispatchSelect(&category, all_calls);+ std::atomic_bool table_lock{0};+ const DispatchSingle<CallType>* const call = DispatchSelect(table_lock, &category, all_calls); if (call) { (this->*call->value)(args); return true;@@ -143,7 +144,8 @@ DispatchTable<CallType>(Functions_String, Table_String), DispatchTable<CallType>(), };- const DispatchTable<CallType>* const table = DispatchSelect(label.collection, all_tables);+ std::atomic_bool table_lock{0};+ const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables); if (table) { if (label.function_num < 0 || label.function_num >= table->size) { FAIL() << "Bad function call " << label;@@ -197,7 +199,8 @@ DispatchTable<CallType>(Functions_SubSequence, Table_SubSequence), DispatchTable<CallType>(), };- const DispatchTable<CallType>* const table = DispatchSelect(label.collection, all_tables);+ std::atomic_bool table_lock{0};+ const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables); if (table) { if (label.function_num < 0 || label.function_num >= table->size) { FAIL() << "Bad function call " << label;
base/category-source.hpp view
@@ -20,6 +20,7 @@ #define CATEGORY_SOURCE_HPP_ #include <algorithm>+#include <atomic> #include <iostream> // For occasional debugging output in generated code. #include <map> #include <mutex>@@ -269,11 +270,13 @@ }; template<class T, int S>-const T* DispatchSelect(const void* key, T(&table)[S]) {+const T* DispatchSelect(std::atomic_bool& lock, const void* key, T(&table)[S]) { if (S < 2) return nullptr;+ while (lock.exchange(true)); if (table[0].key != nullptr) { std::sort(table, table+S); }+ lock.exchange(false); int i = 1, j = S; while (j-i > 1) { const int k = (i+j)/2;
lib/container/search-tree.0rp view
@@ -30,22 +30,36 @@ // Create an empty tree. @type new () -> (#self) - // Traverse the tree in the reverse order of defaultOrder().+ // Traverse in the default order. // // Notes:+ // - This is from DefaultOrder, but is made explicit for documentation. // - Traversal of the entire tree is amortized O(n); however, the cost of any // particular iteration could be up to O(log n). // - The overall memory cost is O(log n).- @value reverseOrder () -> (optional Order<KeyValue<#k,#v>>)+ @value defaultOrder () -> (optional Order<KeyValue<#k,#v>>) - // Traverse in the default order.+ // Traverse the tree in the reverse order of defaultOrder(). // // Notes:- // - This is from DefaultOrder, but is made explicit for documentation. // - Traversal of the entire tree is amortized O(n); however, the cost of any // particular iteration could be up to O(log n). // - The overall memory cost is O(log n).- @value defaultOrder () -> (optional Order<KeyValue<#k,#v>>)+ @value reverseOrder () -> (optional Order<KeyValue<#k,#v>>)++ // Start forward traversal (same as defaultOrder()) from the specified key.+ //+ // Notes:+ // - If the key does not exist in the SearchTree, the position right after+ // where it would be (in the forward direction) is returned.+ @value getForward (#k) -> (optional Order<KeyValue<#k,#v>>)++ // Start reverse traversal (same as reverseOrder()) from the specified key.+ //+ // Notes:+ // - If the key does not exist in the SearchTree, the position right after+ // where it would be (in the reverse direction) is returned.+ @value getReverse (#k) -> (optional Order<KeyValue<#k,#v>>) } // A validated binary search tree for key-value storage.
lib/container/search-tree.0rt view
@@ -23,20 +23,16 @@ unittest integrationTest { [KVWriter<Int,Int>&KVReader<Int,Int>] tree <- ValidatedTree<Int,Int>.new() Int count <- 30+ $ReadOnly[count]$ // Insert values.- scoped {- Int i <- 0- } in while (i < count) {+ traverse (Counter.zeroIndexed(count) -> Int i) { Int new <- ((i + 13) * 3547) % count \ tree.set(new,i)- } update {- i <- i+1 } - scoped {- Int i <- 0- } in while (i < count) {+ // Check and remove values.+ traverse (Counter.zeroIndexed(count) -> Int i) { Int new <- ((i + 13) * 3547) % count scoped { optional Int value <- tree.get(new)@@ -58,8 +54,6 @@ .writeTo(SimpleOutput.error()) } \ tree.remove(new)- } update {- i <- i+1 } } @@ -105,4 +99,88 @@ } \ Testing.checkEquals<?>(index,0)+}++unittest getForward {+ SearchTree<Int,Int> tree <- SearchTree<Int,Int>.new()+ Int hash <- 13+ Int max <- 20+ $ReadOnly[hash,max]$++ // Populate the tree in a pseudo-random order.+ traverse (Counter.zeroIndexed(max) -> Int i) {+ \ ((i*hash)%max) `tree.set` i+ }++ // Validate the traversal order from every starting point.+ traverse (Counter.zeroIndexed(max) -> Int i) {+ Int index <- i+ traverse (tree.getForward(index) -> KeyValue<Int,Int> entry) {+ \ Testing.checkEquals<?>(entry.getKey(),index)+ \ Testing.checkEquals<?>((entry.getValue()*hash)%max,entry.getKey())+ index <- index+1+ }+ \ Testing.checkEquals<?>(index,20)+ }+}++unittest getForwardNotFound {+ SearchTree<Int,Int> tree <- SearchTree<Int,Int>.new()+ \ tree.set(1,1).set(3,3).set(5,5)++ scoped {+ optional Order<KeyValue<Int,Int>> start <- tree.getForward(4)+ } in if (!present(start)) {+ fail("Failed")+ } else {+ \ Testing.checkEquals<?>(require(start).get().getKey(),5)+ }++ scoped {+ optional Order<KeyValue<Int,Int>> start <- tree.getForward(6)+ } in if (present(start)) {+ fail("Failed")+ }+}++unittest getReverse {+ SearchTree<Int,Int> tree <- SearchTree<Int,Int>.new()+ Int hash <- 13+ Int max <- 20+ $ReadOnly[hash,max]$++ // Populate the tree in a pseudo-random order.+ traverse (Counter.zeroIndexed(max) -> Int i) {+ \ ((i*hash)%max) `tree.set` i+ }++ // Validate the traversal order from every starting point.+ traverse (Counter.zeroIndexed(max) -> Int i) {+ Int index <- i+ traverse (tree.getReverse(index) -> KeyValue<Int,Int> entry) {+ \ Testing.checkEquals<?>(entry.getKey(),index)+ \ Testing.checkEquals<?>((entry.getValue()*hash)%max,entry.getKey())+ index <- index-1+ }+ \ Testing.checkEquals<?>(index,-1)+ }+}++unittest getReverseNotFound {+ SearchTree<Int,Int> tree <- SearchTree<Int,Int>.new()+ \ tree.set(1,1).set(3,3).set(5,5)++ scoped {+ optional Order<KeyValue<Int,Int>> start <- tree.getReverse(2)+ } in if (!present(start)) {+ fail("Failed")+ } else {+ \ Testing.checkEquals<?>(require(start).get().getKey(),1)+ }++ scoped {+ optional Order<KeyValue<Int,Int>> start <- tree.getReverse(0)+ } in if (present(start)) {+ fail("Failed")+ } }
lib/container/search-tree.0rx view
@@ -44,6 +44,14 @@ reverseOrder () { return ReverseTreeOrder:create<?,?>(root) }++ getForward (k) {+ return ForwardTreeOrder:seek<?,?>(k,root)+ }++ getReverse (k) {+ return ReverseTreeOrder:seek<?,?>(k,root)+ } } define ValidatedTree {@@ -85,6 +93,8 @@ } define Node {+ $ReadOnly[key]$+ @value Int height @value #k key @value #v value@@ -334,13 +344,15 @@ setHigher (h) { higher <- h } } -concrete TreeKeyValue<#k,#v> {+concrete TreeKeyValue<|#k,#v> { refines KeyValue<#k,#v> @category create<#k,#v> (#k,#v) -> (TreeKeyValue<#k,#v>) } define TreeKeyValue {+ $ReadOnly[key,value]$+ @value #k key @value #v value @@ -357,13 +369,14 @@ } } -concrete ForwardTreeOrder<#k,#v> {+concrete ForwardTreeOrder<|#k,#v> { refines Order<KeyValue<#k,#v>>- #k defines LessThan<#k> - @category create<#k,#v>+ @category create<#k,#v> (optional Node<#k,#v>) -> (optional ForwardTreeOrder<#k,#v>)++ @category seek<#k,#v> #k defines LessThan<#k>- (optional Node<#k,#v>) -> (optional ForwardTreeOrder<#k,#v>)+ (#k,optional Node<#k,#v>) -> (optional ForwardTreeOrder<#k,#v>) } define ForwardTreeOrder {@@ -372,27 +385,42 @@ @value Node<#k,#v> node @value optional ForwardTreeOrder<#k,#v> prev - create (node) {+ create (node) (current) { optional Node<#k,#v> node2 <- node- optional ForwardTreeOrder<#k,#v> current <- empty+ current <- empty while (present(node2)) { current <- ForwardTreeOrder<#k,#v>{ require(node2), current } node2 <- require(node2).getLower() }- return current } - next () {+ seek (key,node) (current) {+ optional Node<#k,#v> node2 <- node+ current <- empty+ while (present(node2)) {+ if (require(node2).getKey() `#k.lessThan` key) {+ // Skip node2 in the traversal, since it's before key.+ node2 <- require(node2).getHigher()+ } elif (key `#k.lessThan` require(node2).getKey()) {+ current <- ForwardTreeOrder<#k,#v>{ require(node2), current }+ node2 <- require(node2).getLower()+ } else {+ current <- ForwardTreeOrder<#k,#v>{ require(node2), current }+ break+ }+ }+ }++ next () (current) { // Algorithm: // 1. Pop self from the stack. // 2. Traverse lower to the bottom starting from the higher child of self. optional Node<#k,#v> node2 <- node.getHigher()- optional ForwardTreeOrder<#k,#v> current <- prev+ current <- prev while (present(node2)) { current <- ForwardTreeOrder<#k,#v>{ require(node2), current } node2 <- require(node2).getLower() }- return current } get () {@@ -400,13 +428,14 @@ } } -concrete ReverseTreeOrder<#k,#v> {+concrete ReverseTreeOrder<|#k,#v> { refines Order<KeyValue<#k,#v>>- #k defines LessThan<#k> - @category create<#k,#v>+ @category create<#k,#v> (optional Node<#k,#v>) -> (optional ReverseTreeOrder<#k,#v>)++ @category seek<#k,#v> #k defines LessThan<#k>- (optional Node<#k,#v>) -> (optional ReverseTreeOrder<#k,#v>)+ (#k,optional Node<#k,#v>) -> (optional ReverseTreeOrder<#k,#v>) } define ReverseTreeOrder {@@ -415,27 +444,42 @@ @value Node<#k,#v> node @value optional ReverseTreeOrder<#k,#v> prev - create (node) {+ create (node) (current) { optional Node<#k,#v> node2 <- node- optional ReverseTreeOrder<#k,#v> current <- empty+ current <- empty while (present(node2)) { current <- ReverseTreeOrder<#k,#v>{ require(node2), current } node2 <- require(node2).getHigher() }- return current } - next () {+ seek (key,node) (current) {+ optional Node<#k,#v> node2 <- node+ current <- empty+ while (present(node2)) {+ if (require(node2).getKey() `#k.lessThan` key) {+ current <- ReverseTreeOrder<#k,#v>{ require(node2), current }+ node2 <- require(node2).getHigher()+ } elif (key `#k.lessThan` require(node2).getKey()) {+ // Skip node2 in the traversal, since it's after key.+ node2 <- require(node2).getLower()+ } else {+ current <- ReverseTreeOrder<#k,#v>{ require(node2), current }+ break+ }+ }+ }++ next () (current) { // Algorithm: // 1. Pop self from the stack. // 2. Traverse higher to the bottom starting from the lower child of self. optional Node<#k,#v> node2 <- node.getLower()- optional ReverseTreeOrder<#k,#v> current <- prev+ current <- prev while (present(node2)) { current <- ReverseTreeOrder<#k,#v>{ require(node2), current } node2 <- require(node2).getHigher() }- return current } get () {
lib/container/type-map.0rt view
@@ -27,21 +27,21 @@ TypeKey<String> keyString <- TypeKey<String>.new() TypeKey<Value> keyValue <- TypeKey<Value>.new() - \ Helper.check<?>(tree,keyInt,empty)- \ Helper.check<?>(tree,keyString,empty)- \ Helper.check<?>(tree,keyValue,empty)+ \ Testing.checkOptional<?>(tree.get<?>(keyInt), empty)+ \ Testing.checkOptional<?>(tree.get<?>(keyString),empty)+ \ Testing.checkOptional<?>(tree.get<?>(keyValue), empty) \ tree.set<?>(keyInt,1) \ tree.set<?>(keyString,"a") \ tree.set<?>(keyValue,Value.new()) - \ Helper.check<?>(tree,keyInt,1)- \ Helper.check<?>(tree,keyString,"a")- \ Helper.check<?>(tree,keyValue,Value.new())+ \ Testing.checkOptional<?>(tree.get<?>(keyInt), 1)+ \ Testing.checkOptional<?>(tree.get<?>(keyString),"a")+ \ Testing.checkOptional<?>(tree.get<?>(keyValue), Value.new()) \ tree.remove(keyString) - \ Helper.check<?>(tree,keyString,empty)+ \ Testing.checkOptional<?>(tree.get<?>(keyString),empty) } unittest wrongReturnType {@@ -51,10 +51,10 @@ // Added as Formatted, which means it cannot be retrieved as String later. \ tree.set<Formatted>(keyString,"a")- \ Helper.check<String>(tree,keyString,empty)+ \ Testing.checkOptional<?>(tree.get<String>(keyString),empty) \ tree.set<String>(keyString,"a")- \ Helper.check<String>(tree,keyString,"a")+ \ Testing.checkOptional<?>(tree.get<String>(keyString),"a") } concrete Value {@@ -75,26 +75,5 @@ equals (_,_) { return true- }-}--concrete Helper {- @type check<#x>- #x requires Formatted- #x defines Equals<#x>- (TypeMap,TypeKey<#x>,optional #x) -> ()-}--define Helper {- check (tree,key,expected) {- scoped {- optional #x value <- tree.get<#x>(key)- } in if (present(value) && present(expected)) {- \ Testing.checkEquals<?>(require(value),require(expected))- } elif (!present(value) && present(expected)) {- fail(key.formatted() + " was expected but not found")- } elif (present(value) && !present(expected)) {- fail(key.formatted() + " was found but not expected")- } } }
lib/container/type-map.0rx view
@@ -45,12 +45,18 @@ } define TypeKey {- // TODO: Protect the counter with a mutex.- @category Int counter <- 0+ $ReadOnly[counterMutex,index]$++ @category Mutex counterMutex <- SimpleMutex.new()+ @category Int counter <- 0 @value Int index new () {- return TypeKey<#x>{ (counter <- counter+1) }+ scoped {+ MutexLock lock <- MutexLock.lock(counterMutex)+ } cleanup {+ \ lock.freeResource()+ } in return TypeKey<#x>{ (counter <- counter+1) } } formatted () {
lib/file/src/Extension_RawFileReader.cpp view
@@ -35,7 +35,7 @@ ReturnTuple Call_open(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final { TRACE_FUNCTION("RawFileReader.open")- return ReturnTuple(CreateValue_RawFileReader(CreateType_RawFileReader(Params<0>::Type()), args));+ return ReturnTuple(CreateValue_RawFileReader(shared_from_this(), args)); } };
lib/file/src/Extension_RawFileWriter.cpp view
@@ -35,7 +35,7 @@ ReturnTuple Call_open(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final { TRACE_FUNCTION("RawFileWriter.open")- return ReturnTuple(CreateValue_RawFileWriter(CreateType_RawFileWriter(Params<0>::Type()), args));+ return ReturnTuple(CreateValue_RawFileWriter(shared_from_this(), args)); } };
lib/file/tests.0rt view
@@ -28,16 +28,12 @@ } cleanup { \ writer.freeResource() } in {- if (present(writer.getFileError())) {- fail(require(writer.getFileError()))- }+ \ Testing.checkEmpty(writer.getFileError()) Int writeSize <- writer.writeBlock(data) if (writeSize != data.size()) { fail(writeSize) }- if (present(writer.getFileError())) {- fail(require(writer.getFileError()))- }+ \ Testing.checkEmpty(writer.getFileError()) } scoped {@@ -45,9 +41,7 @@ } cleanup { \ reader.freeResource() } in {- if (present(reader.getFileError())) {- fail(require(reader.getFileError()))- }+ \ Testing.checkEmpty(reader.getFileError()) scoped { String data2 <- reader.readBlock(8)@@ -59,9 +53,7 @@ } in if (data2 != "some\x00data") { fail("\"" + data2 + "\"") }- if (present(reader.getFileError())) {- fail(require(reader.getFileError()))- }+ \ Testing.checkEmpty(reader.getFileError()) if (!reader.pastEnd()) { fail("more data in file")@@ -89,9 +81,7 @@ if (data != "") { fail("\"" + data + "\"") }- if (present(reader.getFileError())) {- fail(require(reader.getFileError()))- }+ \ Testing.checkEmpty(reader.getFileError()) if (!reader.pastEnd()) { fail("more data in file") }@@ -99,17 +89,13 @@ unittest readMissing { RawFileReader reader <- RawFileReader.open("do-not-create-this-file")- if (!present(reader.getFileError())) {- fail("expected file error")- }+ \ Testing.checkPresent(reader.getFileError()) } unittest unwritable { RawFileWriter writer <- RawFileWriter.open("testfile") \ writer.freeResource()- if (!present(writer.getFileError())) {- fail("expected file error")- }+ \ Testing.checkPresent(writer.getFileError()) }
lib/testing/helpers.0rp view
@@ -30,6 +30,28 @@ #x defines Equals<#x> (#x,#x) -> () + // Check the value for empty. Crashes if present.+ //+ // Args:+ // - optional Formatted: Actual value.+ @type checkEmpty (optional Formatted) -> ()++ // Check the value for present. Crashes if empty.+ //+ // Args:+ // - optional any: Actual value.+ @type checkPresent (optional any) -> ()++ // Check the optional values for equality. Crashes if they are not equal.+ //+ // Args:+ // - optional #x: Actual value.+ // - optional #x: Expected value.+ @type checkOptional<#x>+ #x requires Formatted+ #x defines Equals<#x>+ (optional #x,optional #x) -> ()+ // Check that the value is within a range. Crashes if it is not in the range. // // Args:
lib/testing/helpers.0rx view
@@ -22,30 +22,57 @@ checkEquals (x,y) { if (!#x.equals(x,y)) { fail(String.builder()- .append(x.formatted())- .append(" is not equal to ")- .append(y.formatted())- .build())+ .append(x.formatted())+ .append(" is not equal to ")+ .append(y.formatted())+ .build()) } } + checkEmpty (x) {+ if (present(x)) {+ fail(String.builder()+ .append(require(x).formatted())+ .append(" is not empty")+ .build())+ }+ }++ checkPresent (x) {+ if (!present(x)) {+ fail(String.builder()+ .append("expected non-empty value")+ .build())+ }+ }++ checkOptional (x,y) {+ if (present(x) && present(y)) {+ \ checkEquals<?>(require(x),require(y))+ } elif (!present(x) && present(y)) {+ fail("empty is not equal to " + require(y).formatted())+ } elif (present(x) && !present(y)) {+ fail(require(x).formatted() + " is not equal to empty")+ }+ }+ checkBetween (x,l,h) { if (!lessEquals<?>(l,h)) { fail(String.builder()- .append("Upper limit ")- .append(l.formatted())- .append(" is not at or above lower limit ")- .append(h.formatted())- .build())+ .append("Upper limit ")+ .append(l.formatted())+ .append(" is not at or above lower limit ")+ .append(h.formatted())+ .build()) } if (!lessEquals<?>(l,x) || !lessEquals<?>(x,h)) { fail(String.builder()- .append(x.formatted())- .append(" is not between ")- .append(l.formatted())- .append(" and ")- .append(h.formatted())- .build())+ .append(x.formatted())+ .append(" is not between ")+ .append(l.formatted())+ .append(" and ")+ .append(h.formatted())+ .build()) } }
lib/testing/tests.0rt view
@@ -36,6 +36,77 @@ } +testcase "checkEmpty success" {+ success+}++unittest test {+ \ Testing.checkEmpty(empty)+}+++testcase "checkEmpty fail" {+ crash+ require stderr "13"+ require stderr "empty"+}++unittest test {+ \ Testing.checkEmpty(13)+}+++testcase "checkPresent success" {+ success+}++unittest test {+ \ Testing.checkPresent(13)+}+++testcase "checkPresent fail" {+ crash+ require stderr "empty"+}++unittest test {+ \ Testing.checkPresent(empty)+}+++testcase "checkOptional success" {+ success+}++unittest test {+ \ Testing.checkOptional<Int>(empty,empty)+ \ Testing.checkOptional<?>(13,13)+}+++testcase "checkOptional fail empty" {+ crash+ require stderr "13"+ require stderr "empty"+}++unittest test {+ \ Testing.checkOptional<?>(13,empty)+}+++testcase "checkOptional fail present" {+ crash+ require stderr "13"+ require stderr "empty"+}++unittest test {+ \ Testing.checkOptional<?>(empty,13)+}++ testcase "checkBetween success" { success }
lib/thread/.zeolite-module view
@@ -4,15 +4,10 @@ "lib/util" ] private_deps: [- "lib/container" "lib/testing" ] extra_files: [ category_source {- source: "lib/thread/src/Extension_MutexLock.cpp"- categories: [MutexLock]- }- category_source { source: "lib/thread/src/Extension_ProcessThread.cpp" categories: [ProcessThread] }@@ -21,17 +16,12 @@ categories: [Realtime] } category_source {- source: "lib/thread/src/Extension_SimpleMutex.cpp"- categories: [SimpleMutex]- }- category_source { source: "lib/thread/src/Extension_ThreadCondition.cpp" categories: [ThreadCondition] } category_source { source: "lib/thread/src/Extension_Enumerated.cpp" categories: [EnumeratedBarrier EnumeratedWait]- requires: [Stack Vector] } ] mode: incremental {
lib/thread/barrier.0rp view
@@ -24,6 +24,8 @@ // A thread barrier validated using thread enumeration. concrete EnumeratedBarrier {+ refines ReadAt<BarrierWait>+ // Create a new barrier for a fixed number of threads. // // Args:
− lib/thread/mutex.0rp
@@ -1,63 +0,0 @@-/* ------------------------------------------------------------------------------Copyright 2021 Kevin P. Barry--Licensed under the Apache License, Version 2.0 (the "License");-you may not use this file except in compliance with the License.-You may obtain a copy of the License at-- http://www.apache.org/licenses/LICENSE-2.0--Unless required by applicable law or agreed to in writing, software-distributed under the License is distributed on an "AS IS" BASIS,-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.-See the License for the specific language governing permissions and-limitations under the License.------------------------------------------------------------------------------ */--// Author: Kevin P. Barry [ta0kira@gmail.com]--// A blocking mutex.-//-// Notes:-// - Most use-cases should use MutexLock instead of explicit calls to lock() and-// unlock(), to mitigate the risk of deadlocks.-@value interface Mutex {- // Lock the mutex.- lock () -> (#self)-- // Unlock the mutex.- unlock () -> (#self)-}--// A simple mutex with no deadlock prevention.-concrete SimpleMutex {- refines Mutex-- // Create a new mutex.- @type new () -> (Mutex)-}--// An automatic mutex lock.-//-// Notes:-// - freeResource() must be called; otherwise, there will be a crash. This is to-// catch code that could cause a deadlock.-// - freeResource() must only be called once per MutexLock.-//-// Example:-//-// scoped {-// MutexLock lock <- MutexLock.lock(myMutex)-// $Hidden[myMutex]$ // Hide the mutex to prevent further locking attempts.-// } cleanup {-// \ lock.freeResource()-// } in {-// $Hidden[lock]$ // Nothing else needs to see the lock.-// // your code-// }-concrete MutexLock {- refines PersistentResource-- // Create a new lock and lock the Mutex.- @type lock (Mutex) -> (MutexLock)-}
− lib/thread/mutex.0rt
@@ -1,103 +0,0 @@-/* ------------------------------------------------------------------------------Copyright 2021 Kevin P. Barry--Licensed under the Apache License, Version 2.0 (the "License");-you may not use this file except in compliance with the License.-You may obtain a copy of the License at-- http://www.apache.org/licenses/LICENSE-2.0--Unless required by applicable law or agreed to in writing, software-distributed under the License is distributed on an "AS IS" BASIS,-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.-See the License for the specific language governing permissions and-limitations under the License.------------------------------------------------------------------------------ */--// Author: Kevin P. Barry [ta0kira@gmail.com]--testcase "MutexLock crashes if not freed" {- crash- require "not freed"-}--unittest test {- Mutex mutex <- SimpleMutex.new()- \ MutexLock.lock(mutex)-}---testcase "MutexLock crashes if freed twice" {- crash- require "freed multiple times"-}--unittest test {- Mutex mutex <- SimpleMutex.new()- MutexLock lock <- MutexLock.lock(mutex)- \ lock.freeResource()- \ lock.freeResource()-}---testcase "mutex integration test" {- success-}--unittest test {- Mutex mutex <- SimpleMutex.new()- Value value <- Value.create()- Thread evenThread <- ProcessThread.from(EvenOrOdd.create(mutex,true, 10,value)).start()- Thread oddThread <- ProcessThread.from(EvenOrOdd.create(mutex,false,10,value)).start()- \ evenThread.join()- \ oddThread.join()- \ Testing.checkEquals<?>(value.get(),20)-}--concrete EvenOrOdd {- refines Routine-- // Args:- // - Mutex: Locks the Value.- // - Bool: Increment only on even if true, or on odd if false.- // - Int: Number of increment operations.- // - Value: Value to increment.- @type create (Mutex,Bool,Int,Value) -> (Routine)-}--define EvenOrOdd {- @value Mutex mutex- @value Bool even- @value Int count- @value Value value-- create (m,e,c,v) {- return EvenOrOdd{ m, e, c, v }- }-- run () {- scoped {- Int i <- 0- } in while (i < count) {- $Hidden[count]$- scoped {- MutexLock lock <- MutexLock.lock(mutex)- $Hidden[mutex]$- if (value.getInUse()) {- fail("value in use")- } else {- \ value.setInUse(true)- }- } cleanup {- \ value.setInUse(false)- \ lock.freeResource()- } in {- $Hidden[lock]$- if ((value.get()%2 == 0) ^ !even) {- i <- i+1- \ value.increment()- }- }- }- }-}
lib/thread/src/Extension_Enumerated.cpp view
@@ -17,6 +17,7 @@ // Author: Kevin P. Barry [ta0kira@gmail.com] #include <atomic>+#include <vector> #include <pthread.h> #include <string.h>@@ -27,8 +28,6 @@ #include "Category_BarrierWait.hpp" #include "Category_EnumeratedBarrier.hpp" #include "Category_Int.hpp"-#include "Category_Stack.hpp"-#include "Category_Vector.hpp" namespace { @@ -153,6 +152,9 @@ namespace ZEOLITE_PUBLIC_NAMESPACE { #endif // ZEOLITE_PUBLIC_NAMESPACE +S<TypeValue> CreateValue_EnumeratedBarrier(+ S<Type_EnumeratedBarrier> parent, std::vector<S<TypeValue>> w);+ struct ExtCategory_EnumeratedBarrier : public Category_EnumeratedBarrier { }; @@ -165,20 +167,38 @@ if (Var_arg1 < 0) { FAIL() << "Invalid barrier thread count " << Var_arg1; }- S<TypeValue> vector = GetCategory_Vector().Call(- Function_Vector_create,- ParamTuple(GetType_EnumeratedWait(Params<0>::Type())),- ArgTuple()).Only();+ std::vector<S<TypeValue>> waits; S<Barrier> barrier(Var_arg1? new Barrier(Var_arg1) : nullptr); for (int i = 0; i < Var_arg1; ++i) { S<TypeValue> wait = CreateValue_EnumeratedWait( CreateType_EnumeratedWait(Params<0>::Type()), barrier, i);- TypeValue::Call(vector, Function_Stack_push, ParamTuple(), ArgTuple(wait));+ waits.push_back(wait); }- return ReturnTuple(vector);+ return ReturnTuple(CreateValue_EnumeratedBarrier(shared_from_this(), std::move(waits))); } }; +struct ExtValue_EnumeratedBarrier : public Value_EnumeratedBarrier {+ inline ExtValue_EnumeratedBarrier(S<Type_EnumeratedBarrier> p, std::vector<S<TypeValue>> w)+ : Value_EnumeratedBarrier(p), waits(std::move(w)) {}++ ReturnTuple Call_readAt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {+ TRACE_FUNCTION("EnumeratedBarrier.readAt")+ const PrimInt Var_arg1 = (args.At(0))->AsInt();+ if (Var_arg1 < 0 || Var_arg1 >= waits.size()) {+ FAIL() << "index " << Var_arg1 << " is out of bounds";+ }+ return ReturnTuple(waits[Var_arg1]);+ }++ ReturnTuple Call_size(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {+ TRACE_FUNCTION("EnumeratedBarrier.size")+ return ReturnTuple(Box_Int(waits.size()));+ }++ const std::vector<S<TypeValue>> waits;+};+ Category_EnumeratedBarrier& CreateCategory_EnumeratedBarrier() { static auto& category = *new ExtCategory_EnumeratedBarrier(); return category;@@ -186,6 +206,10 @@ S<Type_EnumeratedBarrier> CreateType_EnumeratedBarrier(Params<0>::Type params) { static const auto cached = S_get(new ExtType_EnumeratedBarrier(CreateCategory_EnumeratedBarrier(), Params<0>::Type())); return cached;+}+S<TypeValue> CreateValue_EnumeratedBarrier(+ S<Type_EnumeratedBarrier> parent, std::vector<S<TypeValue>> w) {+ return S_get(new ExtValue_EnumeratedBarrier(parent, std::move(w))); } #ifdef ZEOLITE_PUBLIC_NAMESPACE
− lib/thread/src/Extension_MutexLock.cpp
@@ -1,91 +0,0 @@-/* ------------------------------------------------------------------------------Copyright 2021 Kevin P. Barry--Licensed under the Apache License, Version 2.0 (the "License");-you may not use this file except in compliance with the License.-You may obtain a copy of the License at-- http://www.apache.org/licenses/LICENSE-2.0--Unless required by applicable law or agreed to in writing, software-distributed under the License is distributed on an "AS IS" BASIS,-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.-See the License for the specific language governing permissions and-limitations under the License.------------------------------------------------------------------------------ */--// Author: Kevin P. Barry [ta0kira@gmail.com]--#include <mutex>--#include "category-source.hpp"-#include "Streamlined_MutexLock.hpp"-#include "Category_Mutex.hpp"-#include "Category_MutexLock.hpp"-#include "Category_PersistentResource.hpp"--#ifdef ZEOLITE_PUBLIC_NAMESPACE-namespace ZEOLITE_PUBLIC_NAMESPACE {-#endif // ZEOLITE_PUBLIC_NAMESPACE--S<TypeValue> CreateValue_MutexLock(S<Type_MutexLock> parent, const ValueTuple& args);--struct ExtCategory_MutexLock : public Category_MutexLock {-};--struct ExtType_MutexLock : public Type_MutexLock {- inline ExtType_MutexLock(Category_MutexLock& p, Params<0>::Type params) : Type_MutexLock(p, params) {}-- ReturnTuple Call_lock(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {- TRACE_FUNCTION("MutexLock.lock")- return ReturnTuple(CreateValue_MutexLock(CreateType_MutexLock(Params<0>::Type()), args));- }-};--struct ExtValue_MutexLock : public Value_MutexLock {- inline ExtValue_MutexLock(S<Type_MutexLock> p, const ValueTuple& args)- : Value_MutexLock(p), mutex(args.Only()) {- TypeValue::Call(mutex, Function_Mutex_lock, ParamTuple(), ArgTuple());- }-- ReturnTuple Call_freeResource(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {- TRACE_FUNCTION("MutexLock.freeResource")- TRACE_CREATION- if (!mutex) {- FAIL() << "MutexLock freed multiple times";- } else {- TypeValue::Call(mutex, Function_Mutex_unlock, ParamTuple(), ArgTuple());- mutex = nullptr;- }- return ReturnTuple();- }-- ~ExtValue_MutexLock() {- if (mutex) {- TRACE_CREATION- TypeValue::Call(mutex, Function_Mutex_unlock, ParamTuple(), ArgTuple());- mutex = nullptr;- FAIL() << "MutexLock not freed with freeResource()";- }- }-- S<TypeValue> mutex;- CAPTURE_CREATION("MutexLock")-};--Category_MutexLock& CreateCategory_MutexLock() {- static auto& category = *new ExtCategory_MutexLock();- return category;-}-S<Type_MutexLock> CreateType_MutexLock(Params<0>::Type params) {- static const auto cached = S_get(new ExtType_MutexLock(CreateCategory_MutexLock(), Params<0>::Type()));- return cached;-}-S<TypeValue> CreateValue_MutexLock(S<Type_MutexLock> parent, const ValueTuple& args) {- return S_get(new ExtValue_MutexLock(parent, args));-}--#ifdef ZEOLITE_PUBLIC_NAMESPACE-} // namespace ZEOLITE_PUBLIC_NAMESPACE-using namespace ZEOLITE_PUBLIC_NAMESPACE;-#endif // ZEOLITE_PUBLIC_NAMESPACE
lib/thread/src/Extension_ProcessThread.cpp view
@@ -41,7 +41,7 @@ ReturnTuple Call_from(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final { TRACE_FUNCTION("ProcessThread.from")- return ReturnTuple(CreateValue_ProcessThread(CreateType_ProcessThread(Params<0>::Type()), args));+ return ReturnTuple(CreateValue_ProcessThread(shared_from_this(), args)); } };
− lib/thread/src/Extension_SimpleMutex.cpp
@@ -1,77 +0,0 @@-/* ------------------------------------------------------------------------------Copyright 2021 Kevin P. Barry--Licensed under the Apache License, Version 2.0 (the "License");-you may not use this file except in compliance with the License.-You may obtain a copy of the License at-- http://www.apache.org/licenses/LICENSE-2.0--Unless required by applicable law or agreed to in writing, software-distributed under the License is distributed on an "AS IS" BASIS,-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.-See the License for the specific language governing permissions and-limitations under the License.------------------------------------------------------------------------------ */--// Author: Kevin P. Barry [ta0kira@gmail.com]--#include <mutex>--#include "category-source.hpp"-#include "Streamlined_SimpleMutex.hpp"-#include "Category_Mutex.hpp"-#include "Category_SimpleMutex.hpp"--#ifdef ZEOLITE_PUBLIC_NAMESPACE-namespace ZEOLITE_PUBLIC_NAMESPACE {-#endif // ZEOLITE_PUBLIC_NAMESPACE--S<TypeValue> CreateValue_SimpleMutex(S<Type_SimpleMutex> parent);--struct ExtCategory_SimpleMutex : public Category_SimpleMutex {-};--struct ExtType_SimpleMutex : public Type_SimpleMutex {- inline ExtType_SimpleMutex(Category_SimpleMutex& p, Params<0>::Type params) : Type_SimpleMutex(p, params) {}-- ReturnTuple Call_new(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {- TRACE_FUNCTION("SimpleMutex.new")- return ReturnTuple(CreateValue_SimpleMutex(CreateType_SimpleMutex(Params<0>::Type())));- }-};--struct ExtValue_SimpleMutex : public Value_SimpleMutex {- inline ExtValue_SimpleMutex(S<Type_SimpleMutex> p) : Value_SimpleMutex(p) {}-- ReturnTuple Call_lock(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {- TRACE_FUNCTION("SimpleMutex.lock")- mutex.lock();- return ReturnTuple(Var_self);- }-- ReturnTuple Call_unlock(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {- TRACE_FUNCTION("SimpleMutex.unlock")- mutex.unlock();- return ReturnTuple(Var_self);- }-- std::mutex mutex;-};--Category_SimpleMutex& CreateCategory_SimpleMutex() {- static auto& category = *new ExtCategory_SimpleMutex();- return category;-}-S<Type_SimpleMutex> CreateType_SimpleMutex(Params<0>::Type params) {- static const auto cached = S_get(new ExtType_SimpleMutex(CreateCategory_SimpleMutex(), Params<0>::Type()));- return cached;-}-S<TypeValue> CreateValue_SimpleMutex(S<Type_SimpleMutex> parent) {- return S_get(new ExtValue_SimpleMutex(parent));-}--#ifdef ZEOLITE_PUBLIC_NAMESPACE-} // namespace ZEOLITE_PUBLIC_NAMESPACE-using namespace ZEOLITE_PUBLIC_NAMESPACE;-#endif // ZEOLITE_PUBLIC_NAMESPACE
lib/thread/src/Extension_ThreadCondition.cpp view
@@ -42,7 +42,7 @@ ReturnTuple Call_new(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final { TRACE_FUNCTION("ThreadCondition.new")- return ReturnTuple(CreateValue_ThreadCondition(CreateType_ThreadCondition(Params<0>::Type())));+ return ReturnTuple(CreateValue_ThreadCondition(shared_from_this())); } };
lib/thread/thread.0rt view
@@ -78,6 +78,70 @@ } +testcase "ProcessThread mutex integration test" {+ success+}++unittest test {+ Mutex mutex <- SimpleMutex.new()+ Value value <- Value.create()+ Thread evenThread <- ProcessThread.from(EvenOrOdd.create(mutex,true, 10,value)).start()+ Thread oddThread <- ProcessThread.from(EvenOrOdd.create(mutex,false,10,value)).start()+ \ evenThread.join()+ \ oddThread.join()+ \ Testing.checkEquals<?>(value.get(),20)+}++concrete EvenOrOdd {+ refines Routine++ // Args:+ // - Mutex: Locks the Value.+ // - Bool: Increment only on even if true, or on odd if false.+ // - Int: Number of increment operations.+ // - Value: Value to increment.+ @type create (Mutex,Bool,Int,Value) -> (Routine)+}++define EvenOrOdd {+ @value Mutex mutex+ @value Bool even+ @value Int count+ @value Value value++ create (m,e,c,v) {+ return EvenOrOdd{ m, e, c, v }+ }++ run () {+ scoped {+ Int i <- 0+ } in while (i < count) {+ $Hidden[count]$+ scoped {+ MutexLock lock <- MutexLock.lock(mutex)+ $Hidden[mutex]$+ if (value.getInUse()) {+ fail("value in use")+ } else {+ \ value.setInUse(true)+ }+ } cleanup {+ \ value.setInUse(false)+ \ lock.freeResource()+ } in {+ $Hidden[lock]$+ if ((value.get()%2 == 0) ^ !even) {+ i <- i+1+ \ value.increment()+ }+ }+ }+ }+}+++ testcase "Argv available in Thread" { success args "arg1"
lib/util/.zeolite-module view
@@ -15,8 +15,16 @@ categories: [Argv] } category_source {+ source: "lib/util/src/Extension_MutexLock.cpp"+ categories: [MutexLock]+ }+ category_source { source: "lib/util/src/Extension_SimpleInput.cpp" categories: [SimpleInput]+ }+ category_source {+ source: "lib/util/src/Extension_SimpleMutex.cpp"+ categories: [SimpleMutex] } category_source { source: "lib/util/src/Extension_SimpleOutput.cpp"
lib/util/extra.0rt view
@@ -23,9 +23,7 @@ unittest global { Int count <- Argv.global().size()- if (count != 5) {- fail(count)- }+ \ Testing.checkEquals<?>(count,5) \ Testing.checkEquals<?>(Argv.global().readAt(0),"testcase") \ Testing.checkEquals<?>(Argv.global().readAt(1),"arg1") \ Testing.checkEquals<?>(Argv.global().readAt(2),"arg2")@@ -36,9 +34,7 @@ unittest subSequence { ReadAt<String> argv <- Argv.global().subSequence(2,2) Int count <- argv.size()- if (count != 2) {- fail(count)- }+ \ Testing.checkEquals<?>(count,2) \ Testing.checkEquals<?>(argv.readAt(0),"arg2") \ Testing.checkEquals<?>(argv.readAt(1),"arg3") }@@ -70,17 +66,13 @@ unittest withValue { ErrorOr<Int> value <- ErrorOr:value<Int>(10)- if (value.isError()) {- fail("Failed")- }+ \ Testing.checkEquals<?>(value.isError(),false) \ Testing.checkEquals<?>(value.getValue(),10) } unittest withError { ErrorOr<String> value <- ErrorOr:error("error message")- if (!value.isError()) {- fail("Failed")- }+ \ Testing.checkEquals<?>(value.isError(),true) \ Testing.checkEquals<?>(value.getError().formatted(),"error message") }
lib/util/input.0rt view
@@ -30,9 +30,7 @@ fail("past end") } String value <- reader.readNextLine()- if (value != line) {- fail(value)- }+ \ Testing.checkEquals<?>(value,line) } }
lib/util/input.0rx view
@@ -56,16 +56,13 @@ @value findNewline () -> (Int) findNewline () {- scoped {- Int position <- 0- } in while (position < buffer.size()) {+ traverse (Counter.zeroIndexed(buffer.size()) -> Int position) {+ $ReadOnly[position]$ Char c <- buffer.readAt(position) // TODO: Maybe the line separator should be a factory argument. if (c == '\n' || c == '\r') { return position }- } update {- position <- position+1 } return -1 }
lib/util/iterator.0rt view
@@ -27,16 +27,12 @@ ReadIterator<Char> iter <- AutoReadIterator:from<?>(s) } in while (!iter.pastForwardEnd()) { $ReadOnly[count,iter]$- if (iter.readCurrent() != s.readAt(count)) {- fail(iter.readCurrent())- }+ \ Testing.checkEquals<?>(iter.readCurrent(),s.readAt(count)) } update { count <- count+1 iter <- iter.forward() }- if (count != s.size()) {- fail(count)- }+ \ Testing.checkEquals<?>(count,s.size()) } unittest reverseRead {@@ -51,9 +47,7 @@ count <- count-1 iter <- iter.reverse() }- if (count != -1) {- fail(count)- }+ \ Testing.checkEquals<?>(count,-1) } @@ -74,9 +68,7 @@ count <- count+1 iter <- iter.forward() }- if (count != s.size()) {- fail(count)- }+ \ Testing.checkEquals<?>(count,s.size()) } unittest reverseWrite {@@ -92,9 +84,7 @@ count <- count-1 iter <- iter.reverse() }- if (count != -1) {- fail(count)- }+ \ Testing.checkEquals<?>(count,-1) } concrete WriteChecker {@@ -139,9 +129,7 @@ count <- count+1 iter <- iter.forward() }- if (count != s.size()) {- fail(count)- }+ \ Testing.checkEquals<?>(count,s.size()) } unittest reverseReadWrite {@@ -158,9 +146,7 @@ count <- count-1 iter <- iter.reverse() }- if (count != -1) {- fail(count)- }+ \ Testing.checkEquals<?>(count,-1) } concrete ReadWriteChecker {
+ lib/util/mutex.0rp view
@@ -0,0 +1,63 @@+/* -----------------------------------------------------------------------------+Copyright 2021 Kevin P. Barry++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++ http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+----------------------------------------------------------------------------- */++// Author: Kevin P. Barry [ta0kira@gmail.com]++// A blocking mutex.+//+// Notes:+// - Most use-cases should use MutexLock instead of explicit calls to lock() and+// unlock(), to mitigate the risk of deadlocks.+@value interface Mutex {+ // Lock the mutex.+ lock () -> (#self)++ // Unlock the mutex.+ unlock () -> (#self)+}++// A simple mutex with no deadlock prevention.+concrete SimpleMutex {+ refines Mutex++ // Create a new mutex.+ @type new () -> (Mutex)+}++// An automatic mutex lock.+//+// Notes:+// - freeResource() must be called; otherwise, there will be a crash. This is to+// catch code that could cause a deadlock.+// - freeResource() must only be called once per MutexLock.+//+// Example:+//+// scoped {+// MutexLock lock <- MutexLock.lock(myMutex)+// $Hidden[myMutex]$ // Hide the mutex to prevent further locking attempts.+// } cleanup {+// \ lock.freeResource()+// } in {+// $Hidden[lock]$ // Nothing else needs to see the lock.+// // your code+// }+concrete MutexLock {+ refines PersistentResource++ // Create a new lock and lock the Mutex.+ @type lock (Mutex) -> (MutexLock)+}
+ lib/util/mutex.0rt view
@@ -0,0 +1,50 @@+/* -----------------------------------------------------------------------------+Copyright 2021 Kevin P. Barry++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++ http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+----------------------------------------------------------------------------- */++// Author: Kevin P. Barry [ta0kira@gmail.com]++testcase "SimpleMutex tests" {+ success+}++unittest lockUnlock {+ Mutex mutex <- SimpleMutex.new()+ \ mutex.lock().unlock().lock().unlock()+}+++testcase "MutexLock crashes if not freed" {+ crash+ require "not freed"+}++unittest test {+ Mutex mutex <- SimpleMutex.new()+ \ MutexLock.lock(mutex)+}+++testcase "MutexLock crashes if freed twice" {+ crash+ require "freed multiple times"+}++unittest test {+ Mutex mutex <- SimpleMutex.new()+ MutexLock lock <- MutexLock.lock(mutex)+ \ lock.freeResource()+ \ lock.freeResource()+}
+ lib/util/src/Extension_MutexLock.cpp view
@@ -0,0 +1,91 @@+/* -----------------------------------------------------------------------------+Copyright 2021 Kevin P. Barry++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++ http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+----------------------------------------------------------------------------- */++// Author: Kevin P. Barry [ta0kira@gmail.com]++#include <mutex>++#include "category-source.hpp"+#include "Streamlined_MutexLock.hpp"+#include "Category_Mutex.hpp"+#include "Category_MutexLock.hpp"+#include "Category_PersistentResource.hpp"++#ifdef ZEOLITE_PUBLIC_NAMESPACE+namespace ZEOLITE_PUBLIC_NAMESPACE {+#endif // ZEOLITE_PUBLIC_NAMESPACE++S<TypeValue> CreateValue_MutexLock(S<Type_MutexLock> parent, const ValueTuple& args);++struct ExtCategory_MutexLock : public Category_MutexLock {+};++struct ExtType_MutexLock : public Type_MutexLock {+ inline ExtType_MutexLock(Category_MutexLock& p, Params<0>::Type params) : Type_MutexLock(p, params) {}++ ReturnTuple Call_lock(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {+ TRACE_FUNCTION("MutexLock.lock")+ return ReturnTuple(CreateValue_MutexLock(CreateType_MutexLock(Params<0>::Type()), args));+ }+};++struct ExtValue_MutexLock : public Value_MutexLock {+ inline ExtValue_MutexLock(S<Type_MutexLock> p, const ValueTuple& args)+ : Value_MutexLock(p), mutex(args.Only()) {+ TypeValue::Call(mutex, Function_Mutex_lock, ParamTuple(), ArgTuple());+ }++ ReturnTuple Call_freeResource(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {+ TRACE_FUNCTION("MutexLock.freeResource")+ TRACE_CREATION+ if (!mutex) {+ FAIL() << "MutexLock freed multiple times";+ } else {+ TypeValue::Call(mutex, Function_Mutex_unlock, ParamTuple(), ArgTuple());+ mutex = nullptr;+ }+ return ReturnTuple();+ }++ ~ExtValue_MutexLock() {+ if (mutex) {+ TRACE_CREATION+ TypeValue::Call(mutex, Function_Mutex_unlock, ParamTuple(), ArgTuple());+ mutex = nullptr;+ FAIL() << "MutexLock not freed with freeResource()";+ }+ }++ S<TypeValue> mutex;+ CAPTURE_CREATION("MutexLock")+};++Category_MutexLock& CreateCategory_MutexLock() {+ static auto& category = *new ExtCategory_MutexLock();+ return category;+}+S<Type_MutexLock> CreateType_MutexLock(Params<0>::Type params) {+ static const auto cached = S_get(new ExtType_MutexLock(CreateCategory_MutexLock(), Params<0>::Type()));+ return cached;+}+S<TypeValue> CreateValue_MutexLock(S<Type_MutexLock> parent, const ValueTuple& args) {+ return S_get(new ExtValue_MutexLock(parent, args));+}++#ifdef ZEOLITE_PUBLIC_NAMESPACE+} // namespace ZEOLITE_PUBLIC_NAMESPACE+using namespace ZEOLITE_PUBLIC_NAMESPACE;+#endif // ZEOLITE_PUBLIC_NAMESPACE
+ lib/util/src/Extension_SimpleMutex.cpp view
@@ -0,0 +1,77 @@+/* -----------------------------------------------------------------------------+Copyright 2021 Kevin P. Barry++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++ http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+----------------------------------------------------------------------------- */++// Author: Kevin P. Barry [ta0kira@gmail.com]++#include <mutex>++#include "category-source.hpp"+#include "Streamlined_SimpleMutex.hpp"+#include "Category_Mutex.hpp"+#include "Category_SimpleMutex.hpp"++#ifdef ZEOLITE_PUBLIC_NAMESPACE+namespace ZEOLITE_PUBLIC_NAMESPACE {+#endif // ZEOLITE_PUBLIC_NAMESPACE++S<TypeValue> CreateValue_SimpleMutex(S<Type_SimpleMutex> parent);++struct ExtCategory_SimpleMutex : public Category_SimpleMutex {+};++struct ExtType_SimpleMutex : public Type_SimpleMutex {+ inline ExtType_SimpleMutex(Category_SimpleMutex& p, Params<0>::Type params) : Type_SimpleMutex(p, params) {}++ ReturnTuple Call_new(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {+ TRACE_FUNCTION("SimpleMutex.new")+ return ReturnTuple(CreateValue_SimpleMutex(CreateType_SimpleMutex(Params<0>::Type())));+ }+};++struct ExtValue_SimpleMutex : public Value_SimpleMutex {+ inline ExtValue_SimpleMutex(S<Type_SimpleMutex> p) : Value_SimpleMutex(p) {}++ ReturnTuple Call_lock(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {+ TRACE_FUNCTION("SimpleMutex.lock")+ mutex.lock();+ return ReturnTuple(Var_self);+ }++ ReturnTuple Call_unlock(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {+ TRACE_FUNCTION("SimpleMutex.unlock")+ mutex.unlock();+ return ReturnTuple(Var_self);+ }++ std::mutex mutex;+};++Category_SimpleMutex& CreateCategory_SimpleMutex() {+ static auto& category = *new ExtCategory_SimpleMutex();+ return category;+}+S<Type_SimpleMutex> CreateType_SimpleMutex(Params<0>::Type params) {+ static const auto cached = S_get(new ExtType_SimpleMutex(CreateCategory_SimpleMutex(), Params<0>::Type()));+ return cached;+}+S<TypeValue> CreateValue_SimpleMutex(S<Type_SimpleMutex> parent) {+ return S_get(new ExtValue_SimpleMutex(parent));+}++#ifdef ZEOLITE_PUBLIC_NAMESPACE+} // namespace ZEOLITE_PUBLIC_NAMESPACE+using namespace ZEOLITE_PUBLIC_NAMESPACE;+#endif // ZEOLITE_PUBLIC_NAMESPACE
src/CompilerCxx/CxxFiles.hs view
@@ -388,7 +388,7 @@ ] where members = filter ((== CategoryScope). dmScope) $ dcMembers d defineConcreteType fs t = concatM [- return $ onlyCode $ "struct " ++ typeName (getCategoryName t) ++ " : public " ++ typeBase ++ " {",+ return $ onlyCode $ "struct " ++ className ++ " : public " ++ typeBase ++ ", std::enable_shared_from_this<" ++ className ++ "> {", fmap indentCompiled $ inlineTypeConstructor t, return declareTypeOverrides, return $ declareTypeArgGetters t,@@ -396,7 +396,9 @@ return $ indentCompiled $ createParams $ getCategoryParams t, return $ onlyCode $ " " ++ categoryName (getCategoryName t) ++ "& parent;", return $ onlyCode "};"- ]+ ] where+ className = typeName (getCategoryName t)+ defineConcreteValue r params fs t d = concatM [ return $ onlyCode $ "struct " ++ valueName (getCategoryName t) ++ " : public " ++ valueBase ++ " {", fmap indentCompiled $ inlineValueConstructor t d,@@ -421,7 +423,7 @@ return $ onlyCode "};" ] defineAbstractType t = concatM [- return $ onlyCode $ "struct " ++ typeName (getCategoryName t) ++ " : public " ++ typeBase ++ " {",+ return $ onlyCode $ "struct " ++ className ++ " : public " ++ typeBase ++ ", std::enable_shared_from_this<" ++ className ++ "> {", fmap indentCompiled $ inlineTypeConstructor t, return declareTypeOverrides, return $ declareTypeArgGetters t,@@ -430,7 +432,8 @@ return $ indentCompiled $ createParams $ getCategoryParams t, return $ onlyCode $ " " ++ categoryName (getCategoryName t) ++ "& parent;", return $ onlyCode "};"- ]+ ] where+ className = typeName (getCategoryName t) defineAbstractValue t = concatM [ return $ onlyCode $ "struct " ++ valueName (getCategoryName t) ++ " : public " ++ valueBase ++ " {", fmap indentCompiled $ abstractValueConstructor t,@@ -722,7 +725,8 @@ [" DispatchTable<CallType>(),"," };"] dispatchKeyValue (n2,_) = " DispatchTable<CallType>(" ++ collectionName n2 ++ ", " ++ tableName n2 ++ ")," select = [- " const DispatchTable<CallType>* const table = DispatchSelect(label.collection, all_tables);",+ " std::atomic_bool table_lock{0};",+ " const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables);", " if (table) {", " if (label.function_num < 0 || label.function_num >= table->size) {", " FAIL() << \"Bad function call \" << label;",@@ -786,7 +790,8 @@ ] ++ map dispatchKeyValue ((getCategoryName t):refines) ++ [ " DispatchSingle<CallType>(),", " };",- " const DispatchSingle<CallType>* const call = DispatchSelect(&category, all_calls);",+ " std::atomic_bool table_lock{0};",+ " const DispatchSingle<CallType>* const call = DispatchSelect(table_lock, &category, all_calls);", " if (call) {", " (this->*call->value)(args);", " return true;",
src/CompilerCxx/Procedure.hs view
@@ -631,6 +631,7 @@ UnwrappedSingle $ valueCreator (tiName t') ++ "(" ++ typeInstance ++ ", " ++ es'' ++ ")") where getType _ True ValueScope _ = "parent"+ getType _ True TypeScope _ = "shared_from_this()" getType t2 _ _ params = typeCreator (tiName t2) ++ "(" ++ params ++ ")" -- Single expression, but possibly multi-return. getValues [(Positional ts,e)] = return (ts,useAsArgs e)@@ -940,8 +941,8 @@ return $ callName (sfName f) ++ "(Var_self, " ++ ps2 ++ ", " ++ es2 ++ ")" assemble Nothing _ TypeScope TypeScope ps2 es2 = return $ callName (sfName f) ++ "(Param_self, " ++ ps2 ++ ", " ++ es2 ++ ")"- assemble Nothing _ ValueScope TypeScope ps2 es2 =- return $ typeBase ++ "::Call(parent, " ++ functionName f ++ ", " ++ ps2 ++ ", " ++ es2 ++ ")"+ assemble Nothing scoped ValueScope TypeScope ps2 es2 =+ return $ scoped ++ callName (sfName f) ++ "(parent, "++ ps2 ++ ", " ++ es2 ++ ")" assemble Nothing scoped _ _ ps2 es2 = return $ scoped ++ callName (sfName f) ++ "(" ++ ps2 ++ ", " ++ es2 ++ ")" assemble (Just e2) _ _ ValueScope ps2 es2 =
src/Module/ProcessMetadata.hs view
@@ -232,7 +232,7 @@ case p `Map.lookup` ca of Just m2 -> continue m2 [] Nothing -> do- errorFromIO $ hPutStrLn stderr $ "Loading metadata for dependency \"" ++ p ++ "\"."+ errorFromIO $ hPutStrLn stderr $ "Loading metadata for module \"" ++ p ++ "\"." m2 <- loadMetadata ca p let ds = getDeps m2 continue m2 ds
zeolite-lang.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.2 name: zeolite-lang-version: 0.16.0.0+version: 0.16.1.0 synopsis: Zeolite is a statically-typed, general-purpose programming language. description: