packages feed

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 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: