Explorar o código

Merge branch 'feat/keep'

* feat/keep:
  fix building
  Add support for keep highest/keep lowest
Sam Jaffe %!s(int64=2) %!d(string=hai) anos
pai
achega
718459ddd7

+ 9 - 0
.gitmodules

@@ -0,0 +1,9 @@
+[submodule "external/expect"]
+	path = external/expect
+	url = ssh://git@gogs.sjaffe.name:3000/sjjaffe/cpp-expect.git
+[submodule "external/scope_guard"]
+	path = external/scope_guard
+	url = ssh://git@gogs.sjaffe.name:3000/sjjaffe/cpp-scope-guard.git
+[submodule "external/shared_random_generator"]
+	path = external/shared_random_generator
+	url = ssh://git@gogs.sjaffe.name:3000/sjjaffe/cpp-shared-random-generator.git

+ 182 - 14
dice-roll.xcodeproj/project.pbxproj

@@ -9,9 +9,8 @@
 /* Begin PBXBuildFile section */
 		CD38F50D21C83926007A732C /* die.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CDED6A3021B2F2DC00AB91D0 /* die.cxx */; };
 		CD38F50E21C83929007A732C /* roll.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD8F1ABD21B31E9E00CBB3CA /* roll.cxx */; };
-		CD38F50F21C83936007A732C /* libdice.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CD38F50921C83912007A732C /* libdice.dylib */; };
-		CD38F51221C8397A007A732C /* libshared_random_generator.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CDED6A6021B2F89900AB91D0 /* libshared_random_generator.dylib */; };
-		CD38F52B21C87771007A732C /* libdice.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CD38F50921C83912007A732C /* libdice.dylib */; };
+		CD38F50F21C83936007A732C /* libdice.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CD38F50921C83912007A732C /* libdice.a */; };
+		CD38F52B21C87771007A732C /* libdice.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CD38F50921C83912007A732C /* libdice.a */; };
 		CD38F53521C87799007A732C /* parser_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD38F53421C87799007A732C /* parser_test.cxx */; };
 		CD38F53721C89493007A732C /* exception.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD38F53621C89493007A732C /* exception.cxx */; };
 		CD38F53921C922E2007A732C /* roll_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD38F53821C922E2007A732C /* roll_test.cxx */; };
@@ -19,7 +18,8 @@
 		CD38F54F21C945C2007A732C /* terminal_helper.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD38F54E21C945C2007A732C /* terminal_helper.cxx */; };
 		CD38F55721C9482A007A732C /* main.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD38F55621C9482A007A732C /* main.cxx */; };
 		CD38F56221C94872007A732C /* terminal_helper.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD38F54E21C945C2007A732C /* terminal_helper.cxx */; };
-		CD38F56521C94A00007A732C /* libdice.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CD38F50921C83912007A732C /* libdice.dylib */; };
+		CD38F56521C94A00007A732C /* libdice.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CD38F50921C83912007A732C /* libdice.a */; };
+		CD5B3FD229D8ED5D0028CD41 /* libshared_random_generator.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CD5B3F6B29D8EBD90028CD41 /* libshared_random_generator.a */; };
 		CDC7489B25312DD6008D9D1D /* GoogleMock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDC7489425312DBF008D9D1D /* GoogleMock.framework */; };
 		CDED6A2721B2F28A00AB91D0 /* main.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CDED6A2621B2F28A00AB91D0 /* main.cxx */; };
 		CDEE78F725B336B000F195F9 /* parser.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CDEE78F625B336B000F195F9 /* parser.cxx */; };
@@ -50,6 +50,69 @@
 			remoteGlobalIDString = CD38F50821C83912007A732C;
 			remoteInfo = dice;
 		};
+		CD5B3F5429D8EBD10028CD41 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = CD5B3F4E29D8EBD10028CD41 /* scope_guard.xcodeproj */;
+			proxyType = 2;
+			remoteGlobalIDString = CDD215D129CCFA1E00A4582C;
+			remoteInfo = scope_guard;
+		};
+		CD5B3F5629D8EBD10028CD41 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = CD5B3F4E29D8EBD10028CD41 /* scope_guard.xcodeproj */;
+			proxyType = 2;
+			remoteGlobalIDString = CDD215EA29CCFA5000A4582C;
+			remoteInfo = "scope_guard-test";
+		};
+		CD5B3F5E29D8EBD50028CD41 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = CD5B3F5829D8EBD50028CD41 /* expect.xcodeproj */;
+			proxyType = 2;
+			remoteGlobalIDString = CDD476BD29C5423B00BDB829;
+			remoteInfo = expect;
+		};
+		CD5B3F6029D8EBD50028CD41 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = CD5B3F5829D8EBD50028CD41 /* expect.xcodeproj */;
+			proxyType = 2;
+			remoteGlobalIDString = CDEC1E8A235248390091D9F2;
+			remoteInfo = "expect-test";
+		};
+		CD5B3F6A29D8EBD90028CD41 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = CD5B3F6229D8EBD90028CD41 /* shared_random_generator.xcodeproj */;
+			proxyType = 2;
+			remoteGlobalIDString = CDED6A4221B2F5A700AB91D0;
+			remoteInfo = shared_random_generator;
+		};
+		CD5B3F6C29D8EBD90028CD41 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = CD5B3F6229D8EBD90028CD41 /* shared_random_generator.xcodeproj */;
+			proxyType = 2;
+			remoteGlobalIDString = CD89E51324E6F3FD008167A8;
+			remoteInfo = "shared_random_generator-test";
+		};
+		CD5B3F6E29D8EBF00028CD41 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = CD5B3F5829D8EBD50028CD41 /* expect.xcodeproj */;
+			proxyType = 1;
+			remoteGlobalIDString = CDD476BC29C5423B00BDB829;
+			remoteInfo = expect;
+		};
+		CD5B3F7029D8EBF00028CD41 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = CD5B3F4E29D8EBD10028CD41 /* scope_guard.xcodeproj */;
+			proxyType = 1;
+			remoteGlobalIDString = CDD215D029CCFA1E00A4582C;
+			remoteInfo = scope_guard;
+		};
+		CD5B3F7229D8EBF00028CD41 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = CD5B3F6229D8EBD90028CD41 /* shared_random_generator.xcodeproj */;
+			proxyType = 1;
+			remoteGlobalIDString = CDED6A4121B2F5A700AB91D0;
+			remoteInfo = shared_random_generator;
+		};
 		CDC7489325312DBF008D9D1D /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
 			containerPortal = CDC7488C25312DBF008D9D1D /* GoogleMock.xcodeproj */;
@@ -102,7 +165,7 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
-		CD38F50921C83912007A732C /* libdice.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libdice.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
+		CD38F50921C83912007A732C /* libdice.a */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libdice.a; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD38F52621C87771007A732C /* dice-td.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "dice-td.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD38F52A21C87771007A732C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		CD38F53421C87799007A732C /* parser_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = parser_test.cxx; sourceTree = "<group>"; };
@@ -112,6 +175,9 @@
 		CD38F54E21C945C2007A732C /* terminal_helper.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = terminal_helper.cxx; sourceTree = "<group>"; };
 		CD38F55421C9482A007A732C /* stateful_dice */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = stateful_dice; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD38F55621C9482A007A732C /* main.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = main.cxx; sourceTree = "<group>"; };
+		CD5B3F4E29D8EBD10028CD41 /* scope_guard.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = scope_guard.xcodeproj; path = external/scope_guard/scope_guard.xcodeproj; sourceTree = "<group>"; };
+		CD5B3F5829D8EBD50028CD41 /* expect.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = expect.xcodeproj; path = external/expect/expect.xcodeproj; sourceTree = "<group>"; };
+		CD5B3F6229D8EBD90028CD41 /* shared_random_generator.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = shared_random_generator.xcodeproj; path = external/shared_random_generator/shared_random_generator.xcodeproj; sourceTree = "<group>"; };
 		CD8F1ABD21B31E9E00CBB3CA /* roll.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = roll.cxx; sourceTree = "<group>"; };
 		CDC7488C25312DBF008D9D1D /* GoogleMock.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GoogleMock.xcodeproj; path = "../../../gmock-xcode-master/GoogleMock.xcodeproj"; sourceTree = "<group>"; };
 		CDED6A2321B2F28A00AB91D0 /* simple_dice */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = simple_dice; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -132,7 +198,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				CD38F51221C8397A007A732C /* libshared_random_generator.dylib in Frameworks */,
+				CD5B3FD229D8ED5D0028CD41 /* libshared_random_generator.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -141,7 +207,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				CDC7489B25312DD6008D9D1D /* GoogleMock.framework in Frameworks */,
-				CD38F52B21C87771007A732C /* libdice.dylib in Frameworks */,
+				CD38F52B21C87771007A732C /* libdice.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -149,7 +215,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				CD38F56521C94A00007A732C /* libdice.dylib in Frameworks */,
+				CD38F56521C94A00007A732C /* libdice.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -157,7 +223,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				CD38F50F21C83936007A732C /* libdice.dylib in Frameworks */,
+				CD38F50F21C83936007A732C /* libdice.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -188,6 +254,33 @@
 			path = simple_dice;
 			sourceTree = "<group>";
 		};
+		CD5B3F4F29D8EBD10028CD41 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				CD5B3F5529D8EBD10028CD41 /* libscope_guard.a */,
+				CD5B3F5729D8EBD10028CD41 /* scope_guard-test.xctest */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		CD5B3F5929D8EBD50028CD41 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				CD5B3F5F29D8EBD50028CD41 /* libexpect.a */,
+				CD5B3F6129D8EBD50028CD41 /* expect-test.xctest */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		CD5B3F6329D8EBD90028CD41 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				CD5B3F6B29D8EBD90028CD41 /* libshared_random_generator.a */,
+				CD5B3F6D29D8EBD90028CD41 /* shared_random_generator-test.xctest */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
 		CDC7488D25312DBF008D9D1D /* Products */ = {
 			isa = PBXGroup;
 			children = (
@@ -203,6 +296,9 @@
 			isa = PBXGroup;
 			children = (
 				CDC7488C25312DBF008D9D1D /* GoogleMock.xcodeproj */,
+				CD5B3F5829D8EBD50028CD41 /* expect.xcodeproj */,
+				CD5B3F4E29D8EBD10028CD41 /* scope_guard.xcodeproj */,
+				CD5B3F6229D8EBD90028CD41 /* shared_random_generator.xcodeproj */,
 				CDEE78D225B333A700F195F9 /* dice-roll */,
 				CDED6A2D21B2F2B200AB91D0 /* test */,
 				CDED6A2521B2F28A00AB91D0 /* src */,
@@ -218,7 +314,7 @@
 			isa = PBXGroup;
 			children = (
 				CDED6A2321B2F28A00AB91D0 /* simple_dice */,
-				CD38F50921C83912007A732C /* libdice.dylib */,
+				CD38F50921C83912007A732C /* libdice.a */,
 				CD38F52621C87771007A732C /* dice-td.xctest */,
 				CD38F55421C9482A007A732C /* stateful_dice */,
 			);
@@ -284,11 +380,14 @@
 			buildRules = (
 			);
 			dependencies = (
+				CD5B3F6F29D8EBF00028CD41 /* PBXTargetDependency */,
+				CD5B3F7129D8EBF00028CD41 /* PBXTargetDependency */,
+				CD5B3F7329D8EBF00028CD41 /* PBXTargetDependency */,
 			);
 			name = dice;
 			productName = dice;
-			productReference = CD38F50921C83912007A732C /* libdice.dylib */;
-			productType = "com.apple.product-type.library.dynamic";
+			productReference = CD38F50921C83912007A732C /* libdice.a */;
+			productType = "com.apple.product-type.library.static";
 		};
 		CD38F52521C87771007A732C /* dice-td */ = {
 			isa = PBXNativeTarget;
@@ -379,10 +478,22 @@
 			productRefGroup = CDED6A2421B2F28A00AB91D0 /* Products */;
 			projectDirPath = "";
 			projectReferences = (
+				{
+					ProductGroup = CD5B3F5929D8EBD50028CD41 /* Products */;
+					ProjectRef = CD5B3F5829D8EBD50028CD41 /* expect.xcodeproj */;
+				},
 				{
 					ProductGroup = CDC7488D25312DBF008D9D1D /* Products */;
 					ProjectRef = CDC7488C25312DBF008D9D1D /* GoogleMock.xcodeproj */;
 				},
+				{
+					ProductGroup = CD5B3F4F29D8EBD10028CD41 /* Products */;
+					ProjectRef = CD5B3F4E29D8EBD10028CD41 /* scope_guard.xcodeproj */;
+				},
+				{
+					ProductGroup = CD5B3F6329D8EBD90028CD41 /* Products */;
+					ProjectRef = CD5B3F6229D8EBD90028CD41 /* shared_random_generator.xcodeproj */;
+				},
 			);
 			projectRoot = "";
 			targets = (
@@ -395,6 +506,48 @@
 /* End PBXProject section */
 
 /* Begin PBXReferenceProxy section */
+		CD5B3F5529D8EBD10028CD41 /* libscope_guard.a */ = {
+			isa = PBXReferenceProxy;
+			fileType = archive.ar;
+			path = libscope_guard.a;
+			remoteRef = CD5B3F5429D8EBD10028CD41 /* PBXContainerItemProxy */;
+			sourceTree = BUILT_PRODUCTS_DIR;
+		};
+		CD5B3F5729D8EBD10028CD41 /* scope_guard-test.xctest */ = {
+			isa = PBXReferenceProxy;
+			fileType = wrapper.cfbundle;
+			path = "scope_guard-test.xctest";
+			remoteRef = CD5B3F5629D8EBD10028CD41 /* PBXContainerItemProxy */;
+			sourceTree = BUILT_PRODUCTS_DIR;
+		};
+		CD5B3F5F29D8EBD50028CD41 /* libexpect.a */ = {
+			isa = PBXReferenceProxy;
+			fileType = archive.ar;
+			path = libexpect.a;
+			remoteRef = CD5B3F5E29D8EBD50028CD41 /* PBXContainerItemProxy */;
+			sourceTree = BUILT_PRODUCTS_DIR;
+		};
+		CD5B3F6129D8EBD50028CD41 /* expect-test.xctest */ = {
+			isa = PBXReferenceProxy;
+			fileType = wrapper.cfbundle;
+			path = "expect-test.xctest";
+			remoteRef = CD5B3F6029D8EBD50028CD41 /* PBXContainerItemProxy */;
+			sourceTree = BUILT_PRODUCTS_DIR;
+		};
+		CD5B3F6B29D8EBD90028CD41 /* libshared_random_generator.a */ = {
+			isa = PBXReferenceProxy;
+			fileType = "compiled.mach-o.dylib";
+			path = libshared_random_generator.a;
+			remoteRef = CD5B3F6A29D8EBD90028CD41 /* PBXContainerItemProxy */;
+			sourceTree = BUILT_PRODUCTS_DIR;
+		};
+		CD5B3F6D29D8EBD90028CD41 /* shared_random_generator-test.xctest */ = {
+			isa = PBXReferenceProxy;
+			fileType = wrapper.cfbundle;
+			path = "shared_random_generator-test.xctest";
+			remoteRef = CD5B3F6C29D8EBD90028CD41 /* PBXContainerItemProxy */;
+			sourceTree = BUILT_PRODUCTS_DIR;
+		};
 		CDC7489425312DBF008D9D1D /* GoogleMock.framework */ = {
 			isa = PBXReferenceProxy;
 			fileType = wrapper.framework;
@@ -496,6 +649,21 @@
 			target = CD38F50821C83912007A732C /* dice */;
 			targetProxy = CD38F56321C949FB007A732C /* PBXContainerItemProxy */;
 		};
+		CD5B3F6F29D8EBF00028CD41 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			name = expect;
+			targetProxy = CD5B3F6E29D8EBF00028CD41 /* PBXContainerItemProxy */;
+		};
+		CD5B3F7129D8EBF00028CD41 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			name = scope_guard;
+			targetProxy = CD5B3F7029D8EBF00028CD41 /* PBXContainerItemProxy */;
+		};
+		CD5B3F7329D8EBF00028CD41 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			name = shared_random_generator;
+			targetProxy = CD5B3F7229D8EBF00028CD41 /* PBXContainerItemProxy */;
+		};
 /* End PBXTargetDependency section */
 
 /* Begin XCBuildConfiguration section */
@@ -587,7 +755,7 @@
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_OBJC_ARC = YES;
@@ -652,7 +820,7 @@
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_OBJC_ARC = YES;

+ 0 - 63
dice-roll.xcodeproj/xcshareddata/xcschemes/dice-td.xcscheme

@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Scheme
-   LastUpgradeVersion = "1230"
-   version = "1.3">
-   <BuildAction
-      parallelizeBuildables = "YES"
-      buildImplicitDependencies = "YES">
-   </BuildAction>
-   <TestAction
-      buildConfiguration = "Debug"
-      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
-      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      shouldUseLaunchSchemeArgsEnv = "YES"
-      codeCoverageEnabled = "YES"
-      onlyGenerateCoverageForSpecifiedTargets = "YES">
-      <CodeCoverageTargets>
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "CD38F50821C83912007A732C"
-            BuildableName = "libdice.dylib"
-            BlueprintName = "dice"
-            ReferencedContainer = "container:dice-roll.xcodeproj">
-         </BuildableReference>
-      </CodeCoverageTargets>
-      <Testables>
-         <TestableReference
-            skipped = "NO">
-            <BuildableReference
-               BuildableIdentifier = "primary"
-               BlueprintIdentifier = "CD38F52521C87771007A732C"
-               BuildableName = "dice-td.xctest"
-               BlueprintName = "dice-td"
-               ReferencedContainer = "container:dice-roll.xcodeproj">
-            </BuildableReference>
-         </TestableReference>
-      </Testables>
-   </TestAction>
-   <LaunchAction
-      buildConfiguration = "Debug"
-      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
-      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      launchStyle = "0"
-      useCustomWorkingDirectory = "NO"
-      ignoresPersistentStateOnLaunch = "NO"
-      debugDocumentVersioning = "YES"
-      debugServiceExtension = "internal"
-      allowLocationSimulation = "YES">
-   </LaunchAction>
-   <ProfileAction
-      buildConfiguration = "Release"
-      shouldUseLaunchSchemeArgsEnv = "YES"
-      savedToolIdentifier = ""
-      useCustomWorkingDirectory = "NO"
-      debugDocumentVersioning = "YES">
-   </ProfileAction>
-   <AnalyzeAction
-      buildConfiguration = "Debug">
-   </AnalyzeAction>
-   <ArchiveAction
-      buildConfiguration = "Release"
-      revealArchiveInOrganizer = "YES">
-   </ArchiveAction>
-</Scheme>

+ 4 - 4
dice-roll.xcodeproj/xcshareddata/xcschemes/dice.xcscheme

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1230"
+   LastUpgradeVersion = "1240"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
@@ -15,7 +15,7 @@
             <BuildableReference
                BuildableIdentifier = "primary"
                BlueprintIdentifier = "CD38F50821C83912007A732C"
-               BuildableName = "libdice.dylib"
+               BuildableName = "libdice.a"
                BlueprintName = "dice"
                ReferencedContainer = "container:dice-roll.xcodeproj">
             </BuildableReference>
@@ -33,7 +33,7 @@
          <BuildableReference
             BuildableIdentifier = "primary"
             BlueprintIdentifier = "CD38F50821C83912007A732C"
-            BuildableName = "libdice.dylib"
+            BuildableName = "libdice.a"
             BlueprintName = "dice"
             ReferencedContainer = "container:dice-roll.xcodeproj">
          </BuildableReference>
@@ -72,7 +72,7 @@
          <BuildableReference
             BuildableIdentifier = "primary"
             BlueprintIdentifier = "CD38F50821C83912007A732C"
-            BuildableName = "libdice.dylib"
+            BuildableName = "libdice.a"
             BlueprintName = "dice"
             ReferencedContainer = "container:dice-roll.xcodeproj">
          </BuildableReference>

+ 1 - 0
external/expect

@@ -0,0 +1 @@
+Subproject commit ff7cd322bd883b085d383253ceefb27933e6d6cf

+ 1 - 0
external/scope_guard

@@ -0,0 +1 @@
+Subproject commit dda916e01f91e13a91e20f5628bfe389d557d246

+ 1 - 0
external/shared_random_generator

@@ -0,0 +1 @@
+Subproject commit b8ac109be9d78b1ce02926e24e7b722d1f571e32

+ 6 - 0
include/dice-roll/die.h

@@ -21,9 +21,15 @@ template <typename T> static sign sgn(T val) {
 
 int sgn(sign);
 
+struct keep {
+  enum { All, Highest, Lowest } method;
+  int amount;
+};
+
 struct die {
   sign sgn;
   int num, sides;
+  keep keep{keep::All, 0};
 };
 
 struct mod {

+ 4 - 0
include/dice-roll/parser.h

@@ -86,6 +86,10 @@ private:
    */
   void parse_const(sign s, int value);
   void parse_dc(char token);
+  void parse_keep(char token);
+
+  int read(int value, bool error_on_eof);
+  int read() { return read(0, true); }
 };
 
 }

+ 5 - 6
include/dice-roll/random.h

@@ -8,14 +8,13 @@
 
 #pragma once
 
-#include <shared_random_generator/random.h>
+#include <random/distribution.h>
+#include <random/random.h>
 
 namespace dice { namespace engine {
-class random : private ::engine::random_number_generator {
-  using super = ::engine::random_number_generator;
-
+class random : private ::engine::random::Random {
 public:
-  using super::random_number_generator;
+  using ::engine::random::Random::Random;
   /**
    * @brief Roll 1dN
    * @param sides The number of sides (N) on a die. e.g. d20
@@ -24,7 +23,7 @@ public:
    * @throws Produces UB if sides = 0
    */
   unsigned int roll(unsigned int sides) const {
-    return super::exclusive(sides) + 1;
+    return (*this)(::engine::random::Uniform(1u, sides));
   }
 };
 }}

+ 12 - 1
src/io.cxx

@@ -41,10 +41,21 @@ std::ostream & operator<<(std::ostream & out, difficulty_class::test t) {
   }
 }
 
+std::ostream & operator<<(std::ostream & out, keep const & k) {
+  switch (k.method) {
+  case keep::Highest:
+    return out << 'k' << 'h' << k.amount;
+  case keep::Lowest:
+    return out << 'k' << 'l' << k.amount;
+  default:
+    return out;
+  }
+}
+
 std::ostream & operator<<(std::ostream & out, dice const & d) {
   if (d.num != 1) out << d.num << '{';
   for (die const & di : d.of) {
-    out << di.sgn << di.num << 'd' << di.sides;
+    out << di.sgn << di.num << 'd' << di.sides << di.keep;
   }
   for (mod m : d.modifier) {
     out << m.sign << m.value;

+ 28 - 6
src/parser.cxx

@@ -29,9 +29,28 @@ void parser::parse_dN(sign s, int value) {
     throw unexpected_token("Expected a number of sides", is_.tellg());
   }
   is_ >> dice_.of.back().sides;
+  if (std::strchr("kK", is_.peek())) {
+    is_.get();
+    parse_keep(is_.get());
+  }
   parse_impl(sign::ZERO);
 }
 
+void parser::parse_keep(char token) {
+  switch (token) {
+  case 'h':
+  case 'H':
+    dice_.of.back().keep = {keep::Highest, read()};
+    break;
+  case 'l':
+  case 'L':
+    dice_.of.back().keep = {keep::Lowest, read()};
+    break;
+  default:
+    throw unexpected_token("Expected high/low in keep expression", is_.tellg());
+  }
+}
+
 void parser::parse_const(sign s, int value) {
   if (value) { // Zero is not a modifier we care about
     dice_.modifier.push_back({s, std::abs(value)});
@@ -55,16 +74,20 @@ void parser::parse_dc(char token) {
   }
 }
 
-void parser::parse_impl(sign s) {
-  advance_over_whitespace(is_);
+int parser::read(int value, bool error_on_eof) {
   // By defaulting this to zero, we can write a more elegant handling of
   // expressions like 1d4+1d6+5+1
-  int value = 0;
   if (isnumber(is_.peek())) {
     is_ >> value;
-  } else if (is_.peek() == EOF && s != sign::ZERO) {
+  } else if (is_.peek() == EOF && error_on_eof) {
     throw unexpected_token("Unexpected EOF while parsing", -1);
   }
+  return value;
+}
+
+void parser::parse_impl(sign s) {
+  advance_over_whitespace(is_);
+  int const value = read(0, s != sign::ZERO);
   advance_over_whitespace(is_);
   switch (is_.peek()) {
   case 'd':
@@ -89,9 +112,8 @@ void parser::parse_impl(sign s) {
 
 dice parser::parse() {
   dice_ = {};
-  int value{1};
   advance_over_whitespace(is_);
-  if (isnumber(is_.peek())) { is_ >> value; }
+  int const value = read(1, false);
   advance_over_whitespace(is_);
 
   switch (is_.peek()) {

+ 12 - 0
src/roll.cxx

@@ -32,6 +32,18 @@ die_roll roll_impl(die const & d, engine::random & gen) {
   for (int i = 0; i < d.num; ++i) {
     hits.push_back(gen.roll(d.sides));
   }
+  switch (d.keep.method) {
+  case keep::Highest:
+    for (int i = 0; i < d.num - d.keep.amount; ++i) {
+      hits.erase(std::min_element(hits.begin(), hits.end()));
+    }
+  case keep::Lowest:
+    for (int i = 0; i < d.num - d.keep.amount; ++i) {
+      hits.erase(std::max_element(hits.begin(), hits.end()));
+    }
+  default:
+    break;
+  }
   return {d.sgn, hits};
 }
 

+ 6 - 0
test/xcode_gtest_helper.h

@@ -10,9 +10,15 @@
 
 #if defined(__APPLE__)
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wquoted-include-in-framework-header"
+#pragma clang diagnostic ignored "-Wcomma"
+
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#pragma clang diagnostic pop
+
 #if defined(TARGET_OS_OSX)
 // This is a hack to allow XCode to properly display failures when running
 // unit tests.