{"id":17494418,"url":"https://github.com/shijbey/repraxis","last_synced_at":"2025-04-10T22:18:17.681Z","repository":{"id":214901687,"uuid":"712756533","full_name":"ShiJbey/RePraxis","owner":"ShiJbey","description":"An in-memory logical database solution for games, based on the Praxis language used in the Versu engine.","archived":false,"fork":false,"pushed_at":"2024-11-27T16:31:17.000Z","size":107,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-10T22:18:10.788Z","etag":null,"topics":["database","games","in-memory-database","logic-programming","praxis","versu"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ShiJbey.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-01T05:56:02.000Z","updated_at":"2024-11-27T16:28:59.000Z","dependencies_parsed_at":"2024-01-05T21:29:13.487Z","dependency_job_id":"f8bdc8c7-1e31-406e-b21f-328c92c47750","html_url":"https://github.com/ShiJbey/RePraxis","commit_stats":null,"previous_names":["shijbey/repraxis"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ShiJbey%2FRePraxis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ShiJbey%2FRePraxis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ShiJbey%2FRePraxis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ShiJbey%2FRePraxis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ShiJbey","download_url":"https://codeload.github.com/ShiJbey/RePraxis/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248305855,"owners_count":21081577,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","games","in-memory-database","logic-programming","praxis","versu"],"created_at":"2024-10-19T13:25:49.683Z","updated_at":"2025-04-10T22:18:17.660Z","avatar_url":"https://github.com/ShiJbey.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Re:Praxis: In-Memory Logic Database for Games\n\nRe:Praxis is an in-memory logic database solution for creating simple databases for games and applications. It is a reconstruction of Praxis, the exclusion logic-based language used by the [Versu social simulation engine](https://versu.com/). Users store information using strings called *sentences*, and the system parses these to create an internal database tree. Users can then query for patterns in the data using the same syntax used to store information.\n\n## Table of Contents\n\n- [Re:Praxis: In-Memory Logic Database for Games](#repraxis-in-memory-logic-database-for-games)\n  - [Table of Contents](#table-of-contents)\n  - [Installation](#installation)\n  - [Creating a New Database](#creating-a-new-database)\n  - [Storing Information](#storing-information)\n  - [Deleting Information](#deleting-information)\n  - [Asserting Information](#asserting-information)\n  - [Querying the Database](#querying-the-database)\n    - [Query statement types](#query-statement-types)\n      - [Assertion statement](#assertion-statement)\n      - [Not-statement (negation)](#not-statement-negation)\n      - [Relational statements](#relational-statements)\n  - [Adding Access Listeners](#adding-access-listeners)\n  - [Building From Source](#building-from-source)\n  - [References](#references)\n\n## Installation\n\n1. Find and Download the latest release of Re:Praxis under the [Releases](https://github.com/ShiJbey/RePraxis/releases) page. Select the `RePraxis_X.Y.Z.zip` entry under \"Assets\" (where `X.Y.Z` corresponds to the Re:Praxis version).\n2. Unzip the download to produce a `RePraxis` directory. This directory should contain the `RePraxis.dll`, `RePraxis.pdb`, and `RePraxis.deps.json` files generated from building the source code.\n3. Copy this directory into your project.\n   - If you're using Unity, place this directory within a directory named `Plugins` within your `Assets` folder.\n\n## Creating a New Database\n\nCreating a new database is the first thing you need to do. A `RePraxisDatabase` instance is responsible for managing all the data and providing data to query.\n\n```csharp\nusing RePraxis;\n\n// Construct a new database\nRePraxisDatabase db = new RePraxisDatabase();\n```\n\n## Storing Information\n\nStoring information to the database is easy. There are no SQL-like tables or property-driven documents. Re:Praxis stores information using strings of text called sentences. Sentences are broken into nodes using the dot (`.`) and exclusion (`!`) operators (See the example code below).\n\nYou can think of the internal structure of the database as a tree. Nodes followed by the dot operator can have more than one child, and those followed by the exclusion operator can only have a single child (See the example below).\n\n```csharp\n// The sentence below enters the fact that we have a symbol \"ashley\" that has\n// a child symbol \"age\", and \"age\" can have only one child value, which is currently\n// set to 32\ndb.Insert( \"ashley.age!32\" );\n\n// The sentence below enters another fact into the database. It has a symbol ashley\n// with a property likes, with a child \"mike\", which indicates that ashley likes\n// mike\ndb.Insert( \"ashley.likes.mike\" );\n```\n\nThis setup allows us to express and store various things like character stats and relationships. For more information about the syntax, please see [this presentation](https://versublog.files.wordpress.com/2014/05/praxis.pdf) by Richard Evans on the original Praxis language.\n\n## Deleting Information\n\nConversely, we can remove information from the database using the same syntax. The code below removes this entry from the database and any other data entries prefixed with `\"ashley.likes.mike\"`.\n\n```csharp\ndb.Delete( \"ashley.likes.mike\" );\n```\n\n## Asserting Information\n\nUsers can check if the database has a piece of data using the `RePraxisDatabase.Assert` method. `Assert` will return false if the data is not in the database, the values differ, or the cardinalities (`.` or `!`) don't match.\n\n```csharp\ndb.Assert( \"ashley.dislikes.mike\" );\n// Returns false\n\ndb.Assert( \"ashley.likes.mike\" );\n// Returns true\n\ndb.Assert( \"ashley.likes\" );\n// Returns true since there are entries with this as a prefix\n```\n\n## Querying the Database\n\nFinally, the most powerful part of this database solution is the ability to query for patterns using variables. Queries have an extended syntax allowing variables, negations, and relational operations. The example below creates a database and fills it with information about relationships between some characters and a player. Then we create a new query that looks for valid bindings for the variables: `?speaker`, `?other`, `?r0`, and `?r1`.\n\nNote that variables are specified using a question mark (`?`). The question mark **does not** replace the `.` or `!`.\n\n```csharp\nusing RePraxis;\n\nRePraxisDatabase db = new RePraxisDatabase();\n\n// Add new information into the database\ndb.Insert( \"astrid.relationships.jordan.reputation!30\" );\ndb.Insert( \"astrid.relationships.jordan.tags.rivalry\" );\ndb.Insert( \"astrid.relationships.jordan.tags.friend\" );\ndb.Insert( \"astrid.relationships.britt.reputation!-10\" );\ndb.Insert( \"astrid.relationships.britt.tags.ex_lover\" );\ndb.Insert( \"astrid.relationships.lee.reputation!20\" );\ndb.Insert( \"astrid.relationships.lee.tags.friend\" );\ndb.Insert( \"player.relationships.jordan.reputation!-20\" );\ndb.Insert( \"player.relationships.jordan.tags.enemy\" );\n\n// Query for a pattern and get valid bindings for variables\nQueryResult result =\n    new DBQuery()\n        .Where( \"?speaker.relationships.?other.reputation!?r0\" )\n        .Where( \"gt ?r0 10\" )\n        .Where( \"player.relationships.?other.reputation!?r1\" )\n        .Where( \"lt ?r1 0\" )\n        .Where( \"neq ?speaker player\" )\n        .Run( db );\n\nif ( result.Success )\n{\n    // Print the results\n    Console.WriteLine(result.ToPrettyString());\n\n    foreach (Dictionary\u003cstring, object\u003e binding in result.Bindings)\n    {\n        // Do something\n    }\n}\n\n\n// Optionally you could also pass initial bindings to a query to limit the results\n\nresult = new DBQuery()\n    .Where( \"astrid.relationships.?other.reputation!?r\" )\n    .Where( \"gte ?r 10\" )\n    .Run( db, new Dictionary\u003cstring, object\u003e() { { \"?other\", \"lee\" } } );\n```\n\nMore examples are available under the `tests` directory.\n\n### Query statement types\n\nStatements in the query are ran sequentially in the same order they were provided. Some statements have slightly different semantics based depending if they contain variables or are the first statement in the query.\n\n#### Assertion statement\n\nThis statement when used without variables will check if the statement holds true within the database. If the statement does not hold true, the query will stop evaluating and return a failed result.\n\n```csharp\nresult = new DBQuery()\n    .Where( \"astrid.relationships.jordan.reputation!30\" )\n```\n\nIf variables are provided, the statement will find all entries in the database that can bind to those variables. All bindings are saved to the query result. If no valid bindings are found, the query ends and returns a failed result. If an assertion with variables has preceding statements or initial bindings were provided to the query, the statement will filter the existing bindings for those that satisfy it.\n\n```csharp\n// Try to find all ?other, where astrid's reputation to that ?other is 30\nresult = new DBQuery()\n    .Where( \"astrid.relationships.?other.reputation!30\" )\n```\n\n```csharp\n// You can also use multiple variables in the same statement\n// Below we bind the target of all astrid's relationships and their corresponding\n// reputation value, to the variables ?other and ?rep.\nresult = new DBQuery()\n    .Where( \"astrid.relationships.?other.reputation!?rep\" )\n```\n\n```csharp\n// Below we get all astrid's relationships with the 'friend' tag (jordan and lee)\n// Next we filter those bindings for those where the reputation is 20\n// This will limit the result to ?other=lee\nresult = new DBQuery()\n    .Where( \"astrid.relationships.?other.tags.friend\" )\n    .Where( \"astrid.relationships.?other.reputation!20\" )\n```\n\n#### Not-statement (negation)\n\nNot-statements are slightly tricky to understand. Their meaning changes based on the existence of variables, existing bindings, and their position in the statement order. Also while you may use variables within a not statement, that statement can never bind new values to variables, it is purely for filtering.\n\nIf a not-statement appears as the first statement in a query, and the statement does not have any variables, the statement succeeds if the sentence being negated does not appear in the database. This case is not affected by the statement's position in the order. If the statement fails, the entire query fails.\n\n```csharp\n// The following succeeds because \"astrid.relationships.jordan.reputation!20\" is\n// not a true statement in the database\nvar result = new DBQuery()\n    .Where( \"not astrid.relationships.jordan.reputation!20\" )\n    .Run( db );\n```\n\n```csharp\n// The following fails because \"astrid.relationships.jordan.reputation!30\" is\n// a true statement in the database\nvar result = new DBQuery()\n    .Where( \"not astrid.relationships.jordan.reputation!30\" )\n    .Run( db );\n```\n\nNot-statements that contain variables change meaning based on existing bindings/execution order. If a not-statement appears first in the query and has variables, then the statement passes if there does not exist an entry in the database that makes the sentence in the statement true. See the example below.\n\n```csharp\n// This query succeeds when there does not exist an ?other that astrid has a relationship with\nvar result = new DBQuery()\n    .Where( \"not astrid.relationships.?other\" )\n    .Run( db );\n```\n\nConsider the query below. When working with values, you might be tempted to think that the query below binds all `?other` where the reputation is not 15. That is **incorrect**. Like the query above this statement passes if there is no `?other` for which astrid's reputation toward them is 15.\n\n```csharp\n// Passes when: for all relationships astrid has with all ?others,\n// no relationship has a reputation of 15\nvar result = new DBQuery()\n    .Where( \"not astrid.relationships.?other.reputation!15\" )\n    .Run( db );\n```\n\nIf you wanted to get all relationships to other where reputation does not equal 15, you need to use two separate statements. The code below first binds all \"others\" and their corresponding reputation values, then it filters for those not equal to 15. We discuss relational statements in the next section.\n\n```csharp\nvar result = new DBQuery()\n    .Where( \"astrid.relationships.?other.reputation!?rep\" )\n    .Where( \"neq ?rep 15\" )\n    .Run( db );\n```\n\nIf the not-statement is preceded by other queries or initial bindings are provided, then the statement filters all intermediate query bindings for those where the statement does not hold.\n\n```csharp\n// Given that ?other is britt, the statement \"astrid.relationships.?other.reputation!30\"\n// is not true. So, the query passes.\nvar result = new DBQuery()\n    .Where( \"not astrid.relationships.?other.reputation!30\" )\n    .Run( db, new Dictionary\u003cstring, object\u003e()\n    {\n        {\"?other\", \"britt\"}\n    } );\n```\n\n```csharp\n// First the query binds all ?others that astrid has a relationship with,\n// then the not-statement filters those results for those where the statement\n// is not true. This query would return britt and lee as valid bindings of other\nvar result = new DBQuery()\n    .Where( \"astrid.relationships.?other\" )\n    .Where( \"not astrid.relationships.?other.reputation!30\" )\n    .Run( db );\n```\n\n#### Relational statements\n\nRelational statements are used to check for equality/inequality. Each statement starts with an operation name followed by two values. These values must be be variables, single symbols (strings), or integers/floats. You **cannot** pass a sentence as a parameter. You should first bind your value of interest to a variable, then use it in a relational statement.\n\n- `eq a b`: Checks if `a` is equal to `b`\n- `neq a b`: Checks if `a` is not equal to `b`\n- `gt a b`: Checks if `a` is greater than `b`\n- `lt a b`: Checks if `a` is less than to `b`\n- `gte a b`: Checks if `a` is greater than or equal to `b`\n- `lte a b`: Checks if `a` is less than or equal to `b`\n\nBelow is an example query provided earlier that uses the `neq` (not equal) operator to filter results.\n\n```csharp\nvar result = new DBQuery()\n    .Where( \"astrid.relationships.?other.reputation!?rep\" )\n    .Where( \"neq ?rep 15\" )\n    .Run( db );\n```\n\n## Adding Access Listeners\n\nYou can add callback functions that run before a query or asset statement accesses a specific path in the database. For some applications, these are used to recalculate character stat data in realtime before querying it. The following is an example.\n\n```csharp\nclass Player\n{\n    public int reputation;\n}\n\nvar player = new Player() { reputation = 10 };\n\nvar db = new RePraxisDatabase();\n\n// Whenever we query for the player's reputation, the given\n// callback function executes and updates the reputation value\n// in the database before it's accessed.\ndb.AddBeforeAccessListener(\"player.reputation\", (database) =\u003e {\n    database.Insert($\"player.reputation!{player.reputation}\");\n});\n\ndb.Assert(\"player.reputation!10\"); // True\ndb.Assert(\"player.reputation!25\"); // False\nplayer.reputation = 25; // Update the player instance only\ndb.Assert(\"player.reputation!25\"); // True\n\n```\n\n## Building From Source\n\nBuilding Re:Praxis from source requires that you have .Net net installed. Run the following commands, and new `RePraxis.dll` and `RePraxis.pdb` files will be generated within the `dist` directory.\n\n```bash\n# Step 1: Clone the repository\ngit clone https://github.com/ShiJBey/RePraxis.git\n\n# Step 2: Change to the project repository\ncd RePraxis\n\n# Step 3: Build using dotnet CLI\ndotnet build\n```\n\n## References\n\n- \u003chttps://versu.com/about/how-versu-works/\u003e\n- \u003chttps://github.com/JamesDameris/Wyclef\u003e\n- \u003chttps://github.com/mkremins/praxish\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshijbey%2Frepraxis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshijbey%2Frepraxis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshijbey%2Frepraxis/lists"}