{"id":47976201,"url":"https://github.com/aki-null/epsilon-script","last_synced_at":"2026-04-04T10:55:34.073Z","repository":{"id":37884388,"uuid":"235038550","full_name":"aki-null/epsilon-script","owner":"aki-null","description":"Embeddable expression engine for C# with functions and allocation-free execution","archived":false,"fork":false,"pushed_at":"2025-12-08T14:03:31.000Z","size":846,"stargazers_count":18,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-04-04T10:55:28.103Z","etag":null,"topics":["c-sharp","dotnet","expression-evaluator","math","unity"],"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/aki-null.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-01-20T07:01:05.000Z","updated_at":"2025-12-08T14:03:35.000Z","dependencies_parsed_at":"2025-12-06T20:02:52.146Z","dependency_job_id":null,"html_url":"https://github.com/aki-null/epsilon-script","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/aki-null/epsilon-script","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aki-null%2Fepsilon-script","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aki-null%2Fepsilon-script/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aki-null%2Fepsilon-script/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aki-null%2Fepsilon-script/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aki-null","download_url":"https://codeload.github.com/aki-null/epsilon-script/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aki-null%2Fepsilon-script/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31397055,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T10:20:44.708Z","status":"ssl_error","status_checked_at":"2026-04-04T10:20:06.846Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["c-sharp","dotnet","expression-evaluator","math","unity"],"created_at":"2026-04-04T10:55:32.742Z","updated_at":"2026-04-04T10:55:34.056Z","avatar_url":"https://github.com/aki-null.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# EpsilonScript\n\n[![Readme_ja](https://img.shields.io/badge/EpsilonScript-%E6%97%A5%E6%9C%AC%E8%AA%9E-E87A90)](https://github.com/aki-null/epsilon-script/blob/master/README_ja.md)\n\nEpsilonScript is an embeddable expression engine for C# with functions and allocation-free execution.\n\nSupports .NET Standard 2.1. See [changelog](CHANGELOG.md) for version history.\n\n**Basic expressions:**\n```c#\nvar compiler = new Compiler();\nvar script = compiler.Compile(\"10 + 20 * 2\");\nscript.Execute();\nConsole.WriteLine(script.IntegerValue); // 50\n```\n\n**With variables:**\n```c#\nvar variables = new DictionaryVariableContainer\n{\n    [\"health\"] = new VariableValue(100),\n    [\"damage\"] = new VariableValue(25)\n};\nvar script = compiler.Compile(\"health - damage\", Compiler.Options.Immutable, variables);\nscript.Execute();\nConsole.WriteLine(script.IntegerValue); // 75\n```\n\n**Swapping variable containers:**\n```c#\n// Compile once\nvar script = compiler.Compile(\"health - damage\", Compiler.Options.Immutable);\n\n// Execute with different containers\nvar player1 = new DictionaryVariableContainer\n{\n    [\"health\"] = new VariableValue(100),\n    [\"damage\"] = new VariableValue(25)\n};\nvar player2 = new DictionaryVariableContainer\n{\n    [\"health\"] = new VariableValue(80),\n    [\"damage\"] = new VariableValue(30)\n};\n\nscript.Execute(player1);\nConsole.WriteLine(script.IntegerValue); // 75\n\nscript.Execute(player2);\nConsole.WriteLine(script.IntegerValue); // 50\n```\n\n**With custom functions:**\n```c#\n// Weapon effectiveness table - too complex to express inline\ncompiler.AddCustomFunction(CustomFunction.Create(\"weapon_effectiveness\",\n    (string weaponType, string armorType) =\u003e (weaponType, armorType) switch\n    {\n        (\"hammer\", \"heavy\") =\u003e 1.5f,\n        (\"hammer\", \"light\") =\u003e 0.8f,\n        (\"sword\", \"heavy\") =\u003e 0.8f,\n        (\"sword\", \"light\") =\u003e 1.2f,\n        _ =\u003e 1.0f\n    }));\n\nvar variables = new DictionaryVariableContainer\n{\n    [\"base_damage\"] = new VariableValue(100),\n    [\"weapon\"] = new VariableValue(\"hammer\"),\n    [\"armor\"] = new VariableValue(\"heavy\")\n};\n\nvar script = compiler.Compile(\n    \"base_damage * weapon_effectiveness(weapon, armor)\",\n    Compiler.Options.Immutable);\nscript.Execute(variables);\nConsole.WriteLine(script.FloatValue); // 150\n```\n\n## Table of Contents\n\n- [Features](#features)\n- [Installation](#installation)\n  - [Unity](#unity)\n- [Arithmetic](#arithmetic)\n- [Comparison](#comparison)\n- [Variables](#variables)\n- [Functions](#functions)\n- [Strings](#strings)\n- [Expression Sequencing](#expression-sequencing)\n- [Numeric Precision](#numeric-precision)\n- [Heap Allocations](#heap-allocations)\n- [Thread Safety](#thread-safety)\n- [Caching Compiler](#caching-compiler)\n- [Motivation](#motivation)\n- [Development](#development)\n\n## Features\n- Simple syntax\n- Arithmetic (`+`, `-`, `*`, `/`, `%`) and boolean operators (`\u0026\u0026`, `||`, `!`, comparisons)\n- Dynamic typing with assignment operators (`=`, `+=`, `-=`, `*=`, `/=`, `%=`)\n- String concatenation\n- Custom functions with overloading\n- Configurable numeric precision (int/long, float/double/decimal)\n- Zero allocations after compilation (except string operations with variables)\n- Immutable mode blocks variable changes; NoAlloc mode blocks runtime allocations\n- Compile-time optimization (constant folding, deterministic functions, dead code elimination)\n- Swap variable containers at runtime (compile once, execute with different data)\n- Unity support\n\n## Installation\n\n### Unity\n\nInstall via OpenUPM:\n\n- Package is available on [OpenUPM](https://openupm.com). If you have [openupm-cli](https://github.com/openupm/openupm-cli#openupm-cli) installed, run this in your Unity project root:\n  ```\n  openupm add com.akinull.epsilonscript\n  ```\n- To update, specify the version you want:\n  ```\n  openupm add com.akinull.epsilonscript@2.1.0\n  ```\n\nInstall via UPM (Git URL):\n\n1. Open **Window \u003e Package Manager**\n2. Click the **+** button in the top-left corner\n3. Select **Add package from git URL**\n4. Enter: `https://github.com/aki-null/epsilon-script-unity.git`\n\nAlternatively, add it to your `Packages/manifest.json`:\n\n```json\n{\n  \"dependencies\": {\n    \"com.akinull.epsilonscript\": \"https://github.com/aki-null/epsilon-script-unity.git\"\n  }\n}\n```\n\n## Arithmetic\n\nSupports basic operators (`+`, `-`, `*`, `/`, `%`) and parentheses.\n\n```c#\nvar compiler = new Compiler();\nvar script = compiler.Compile(\"(1 + 2 + 3 * 2) * 2\", Compiler.Options.Immutable);\nscript.Execute();\nConsole.WriteLine(script.IntegerValue);  // 18\n```\n\n## Comparison\n\nSupports comparison (`==`, `!=`, `\u003c`, `\u003c=`, `\u003e`, `\u003e=`) and logical operators (`!`, `\u0026\u0026`, `||`).\n\n```c#\nvar compiler = new Compiler();\nvar script = compiler.Compile(\"10 \u003e= 5 \u0026\u0026 10 \u003c 50\");\nscript.Execute();\nConsole.WriteLine(script.BooleanValue);  // True\n```\n\n## Variables\n\nVariables support assignment (`=`) and compound operators (`+=`, `-=`, `*=`, `/=`, `%=`).\n\nVariables are stored in an `IVariableContainer` (use `DictionaryVariableContainer` for a basic implementation).\n\n```c#\nvar compiler = new Compiler();\nVariableId valId = \"val\"; // Implicit conversion from string\nvar variables = new DictionaryVariableContainer { [valId] = new VariableValue(43.0f) };\nvar script = compiler.Compile(\"val = val * 10.0\", Compiler.Options.None, variables);\nscript.Execute();\nConsole.WriteLine(variables[valId].FloatValue);  // 430.0\n```\n\n`VariableId` provides a simple string-like interface while using integer IDs internally for fast lookups. Recommended for performance-critical code.\n\nVariables can't be defined in scripts - this keeps expressions simple.\n\n### String-based Variable Access\n\nIf performance isn't critical, you can use strings directly:\n\n```c#\nvar compiler = new Compiler();\nvar variables = new DictionaryVariableContainer { [\"val\"] = new VariableValue(43.0f) };\nvar script = compiler.Compile(\"val = val * 10.0\", Compiler.Options.None, variables);\nscript.Execute();\nConsole.WriteLine(variables[\"val\"].FloatValue);\n```\n\nString lookups are slower than `VariableId` because of internal conversion overhead.\n\n### Variables with Periods\n\nVariable names can contain periods (`.`) for organizing related values:\n\n```c#\nvar compiler = new Compiler();\nvar variables = new DictionaryVariableContainer\n{\n    [\"user.name\"] = new VariableValue(\"John\"),\n    [\"user.level\"] = new VariableValue(5),\n    [\"config.server.port\"] = new VariableValue(8080),\n    [\"config.server.host\"] = new VariableValue(\"localhost\")\n};\n\nvar script = compiler.Compile(\"user.name + ':' + config.server.host\", Compiler.Options.Immutable, variables);\nscript.Execute();\nConsole.WriteLine(script.StringValue);  // \"John:localhost\"\n```\n\n### Immutable Mode\n\nTwo modes for variables:\n\nMutable Mode (Default):\n- Variables can be modified\n\nImmutable Mode:\n- Blocks all variable modifications\n- Compile-time error if you use assignment operators\n\n```c#\n// Immutable mode - only reads variables\nvar script1 = compiler.Compile(\"health - damage\", Compiler.Options.Immutable, variables);\nscript1.Execute(); // Works\n\n// Mutable mode - can modify variables\nvar script2 = compiler.Compile(\"health -= damage\", Compiler.Options.None, variables);\nscript2.Execute(); // Works\n\n// Immutable mode with assignment - throws exception at compile time\nvar script3 = compiler.Compile(\n    \"health -= damage\", Compiler.Options.Immutable, variables); // Exception!\n```\n\n### Variable Container Override\n\nPass a different `IVariableContainer` to `Execute()` to override variables at runtime. It checks the override container first, then falls back to the compile-time container.\n\nUseful pattern: compile once with globals, execute many times with per-instance data.\n\n```c#\n// Compile with global config\nvar globals = new DictionaryVariableContainer\n{\n  [\"shipping_fee\"] = new VariableValue(5.99f),\n  [\"tax_rate\"] = new VariableValue(0.08f)\n};\nvar script = compiler.Compile(\"price + shipping_fee + price * tax_rate\",\n                               Compiler.Options.None, globals);\n\n// Execute for each instance\nforeach (var user in users)\n{\n  var instanceVars = new DictionaryVariableContainer\n  {\n    [\"price\"] = new VariableValue(user.CartTotal)  // Override with instance value\n    // shipping_fee and tax_rate fall back to globals\n  };\n  script.Execute(instanceVars);\n  Console.WriteLine($\"Total: ${script.FloatValue}\");\n}\n```\n\n### Dynamic Typing\n\nVariable types are determined at runtime, not compile time.\n\nCompile once and execute with different types. The expression `a + b` adds numbers or concatenates strings depending on the data:\n\n```c#\nvar script = compiler.Compile(\"a + b\", Compiler.Options.None, null);\n\n// Execute with floats - performs addition\nvar floatVars = new DictionaryVariableContainer\n{\n  [\"a\"] = new VariableValue(1.5f),\n  [\"b\"] = new VariableValue(2.3f)\n};\nscript.Execute(floatVars);\nConsole.WriteLine(script.FloatValue);  // 3.8\n\n// Execute with strings (same compiled script) - performs concatenation\nvar stringVars = new DictionaryVariableContainer\n{\n  [\"a\"] = new VariableValue(\"Hello\"),\n  [\"b\"] = new VariableValue(\" World\")\n};\nscript.Execute(stringVars);\nConsole.WriteLine(script.StringValue);  // \"Hello World\"\n```\n\n## Functions\n\nDefine custom functions in C# to extend scripts with your own logic.\n\n```c#\nvar compiler = new Compiler();\ncompiler.AddCustomFunction(\n    CustomFunction.Create(\"clamp\", (float val, float min, float max) =\u003e\n        Math.Max(min, Math.Min(max, val))));\n\nvar variables = new DictionaryVariableContainer { [\"damage\"] = new VariableValue(50) };\nvar script = compiler.Compile(\n    \"clamp(damage * 1.5, 10, 100)\", Compiler.Options.Immutable, variables);\nscript.Execute();\nConsole.WriteLine(script.FloatValue); // 75\n```\n\n### Function Requirements\n\nCustom functions shouldn't mutate state. Reading external data is fine, but don't modify anything.\n\n```c#\n// Allowed: pure calculation\nCustomFunction.Create(\"square\", (float x) =\u003e x * x)\n\n// Allowed: read-only external access\nCustomFunction.Create(\"get_health\", () =\u003e player.Health)\n\n// Allowed: non-deterministic but no mutation\nCustomFunction.Create(\"rand\", (float max) =\u003e Random.Range(0.0f, max))\n\n// Forbidden: mutates external state\nCustomFunction.Create(\"set_score\", (int score) =\u003e { gameState.Score = score; return score; })\n```\n\nCustom functions support 0-5 parameters.\n\nParameter types must match the function signature. Mismatched types throw a runtime error. Integers auto-convert to floats when needed.\n\n### Built-in Functions\n\n- Trigonometric: `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`, `sinh`, `cosh`, `tanh`\n- Math: `sqrt`, `abs`, `floor`, `ceil`, `trunc`, `pow`, `min`, `max`\n- String: `lower`, `upper`, `len`\n- Utility: `ifelse` (ternary operator alternative)\n\nSee [Compiler.cs](https://github.com/aki-null/epsilon-script/blob/master/EpsilonScript/Compiler.cs) for the complete list.\n\n### Overloading\n\nFunctions can share the same name with different parameter types or counts.\n\nBuilt-in functions like `abs`, `min`, `max`, and `ifelse` use overloading.\n\n### Deterministic Functions\n\nMark functions as **deterministic** (same inputs = same output) to enable compile-time optimization.\n\nPass `isDeterministic: true` to enable:\n\n```c#\ncompiler.AddCustomFunction(\n    CustomFunction.Create(\"sin\", (float v) =\u003e MathF.Sin(v), isDeterministic: true));\n\n// Compile time: sin(1.5708) -\u003e ~1.0\n// The compiled script contains just the constant value\nvar script = compiler.Compile(\"sin(1.5708) * 10\");\n```\n\nDeterministic functions with constant parameters get pre-evaluated at compile time. For example, you can create a const() function to fetch configuration values and bake them into scripts:\n\n```c#\nvar config = new Dictionary\u003cstring, float\u003e { [\"GRAVITY\"] = 9.8f };\n\ncompiler.AddCustomFunction(\n    CustomFunction.Create(\"const\", (string name) =\u003e config[name], isDeterministic: true));\n\nvar variables = new DictionaryVariableContainer { [\"jump_force\"] = new VariableValue(20) };\nvar script = compiler.Compile(\"jump_force - const('GRAVITY')\", Compiler.Options.Immutable, variables);\n\n// Compile time: const('GRAVITY') -\u003e 9.8\n// Runtime: evaluates 20 - 9.8 = 10.2\nscript.Execute();\nConsole.WriteLine(script.FloatValue); // 10.2\n```\n\nOnly const() calls with constant values get pre-evaluated. Variables like `jump_force` remain dynamic and evaluate at runtime.\n\n### Method Groups\n\nYou can use method groups instead of lambdas:\n\n```c#\npublic int GetScore(string level) =\u003e CalculateScore(level);\n\n// Method group instead of lambda\ncompiler.AddCustomFunction(CustomFunction.Create\u003cstring, int\u003e(\"score\", GetScore));\n```\n\nMethod groups with parameters need explicit generic type parameters (as shown above). Zero-parameter method groups work without them:\n\n```c#\npublic int GetConstant() =\u003e 42;\n\n// Zero parameters - type inference works\ncompiler.AddCustomFunction(CustomFunction.Create(\"constant\", GetConstant));\n```\n\n### Contextual Custom Functions\n\nContextual functions read variables from the execution environment without requiring them as parameters.\n\n```c#\nvar compiler = new Compiler();\n\n// Function reads 'day' from context\ncompiler.AddCustomFunction(\n    CustomFunction.CreateContextual(\n        \"IsMon\",\n        \"day\",\n        (int day) =\u003e day % 7 == 1));\n\nvar variables = new DictionaryVariableContainer\n{\n    [\"day\"] = new VariableValue(1)\n};\n\nvar script = compiler.Compile(\"IsMon()\", Compiler.Options.Immutable, variables);\nscript.Execute();\nConsole.WriteLine(script.BooleanValue); // True\n```\n\nFunctions can combine context variables with script parameters:\n\n```c#\ncompiler.AddCustomFunction(\n    CustomFunction.CreateContextual(\n        \"IsAfter\",\n        \"currentDay\",\n        (int current, int target) =\u003e current \u003e target));\n\nvar script = compiler.Compile(\"IsAfter(5)\", Compiler.Options.Immutable, variables);\n```\n\nSupports up to 3 context variables and 3 script parameters.\n\n### Bulk Function Registration\n\nUse `AddCustomFunctionRange` to register multiple functions at once:\n\n```c#\nvar compiler = new Compiler();\n\nvar functions = new[]\n{\n    CustomFunction.Create(\"double\", (int x) =\u003e x * 2),\n    CustomFunction.Create(\"triple\", (int x) =\u003e x * 3),\n    CustomFunction.Create(\"square\", (int x) =\u003e x * x)\n};\n\ncompiler.AddCustomFunctionRange(functions);\n\nvar script = compiler.Compile(\"double(5) + triple(3) + square(2)\");\nscript.Execute();\nConsole.WriteLine(script.IntegerValue); // 10 + 9 + 4 = 23\n```\n\n## Strings\n\nStrings work with both double (`\"...\"`) and single (`'...'`) quotes:\n\n```\n\"Hello World\"\n'Hello World'\n\"It's working\"\n'He said \"hello\"'\n```\n\nNote: No escape sequences are supported. Backslashes and other special characters are treated as literal characters. This makes it easy to write paths.\n\n### String Operations\n\nStrings support concatenation, mixing with numbers, and comparison:\n\n```c#\n// Concatenation\n\"Hello \" + \"World\"  // \"Hello World\"\n\n// Mix strings and numbers\n\"Debug: \" + 128  // \"Debug: 128\"\n\n// Comparison\n\"Hello\" == \"Hello\"  // true\n```\n\n### Using Strings in Functions\n\n```c#\nvar compiler = new Compiler();\ncompiler.AddCustomFunction(CustomFunction.Create(\"read_save_data\",\n  (string flag) =\u003e SaveData.Instance.GetIntegerData(flag)));\nvar script = compiler.Compile(\"read_save_data('LVL00_PLAYCOUNT') \u003e 5\", Compiler.Options.Immutable);\nscript.Execute();\nConsole.WriteLine(script.BooleanValue);  // True (if GetIntegerData returns 10)\n```\n\n## Expression Sequencing\n\nUse semicolons (`;`) to run multiple expressions. Execution returns the value of the last expression.\n\n```c#\nvar compiler = new Compiler();\nVariableId xId = \"x\";\nVariableId yId = \"y\";\nvar variables = new DictionaryVariableContainer\n{\n  [xId] = new VariableValue(5),\n  [yId] = new VariableValue(10)\n};\nvar script = compiler.Compile(\n    \"x = x + 1; y = y * 2; x + y\",\n    Compiler.Options.None,\n    variables);\nscript.Execute();\nConsole.WriteLine(script.IntegerValue); // 26 (x is 6, y is 20)\n```\n\n## Numeric Precision\n\nSet integer and floating-point precision when creating the compiler.\n\n### Precision Options\n\n**Integer Precision:**\n- `Integer` (default): 32-bit int\n- `Long`: 64-bit long\n\n**Float Precision:**\n- `Float` (default): 32-bit float\n- `Double`: 64-bit double\n- `Decimal`: 128-bit decimal\n\n### Usage\n\n```c#\n// Default: 32-bit int and float\nvar compiler = new Compiler();\n\n// High precision: 64-bit long and 128-bit decimal\nvar preciseCompiler = new Compiler(\n    Compiler.IntegerPrecision.Long,\n    Compiler.FloatPrecision.Decimal);\n\nvar script = preciseCompiler.Compile(\"0.1 + 0.2\");\nscript.Execute();\nConsole.WriteLine(script.DecimalValue); // 0.3\n```\n\nOperations use the configured precision automatically.\n\n### Custom Functions and Precision\n\nMatch your custom function types to the compiler's precision:\n\n```c#\n// Compiler uses Double precision\nvar compiler = new Compiler(\n    Compiler.IntegerPrecision.Integer,\n    Compiler.FloatPrecision.Double);\n\n// Function must use double, not float\ncompiler.AddCustomFunction(CustomFunction.Create(\"calc\", (double x) =\u003e x * 2.5));\n\nvar script = compiler.Compile(\"calc(10.5)\");\nscript.Execute();\nConsole.WriteLine(script.DoubleValue); // 26.25\n```\n\n## Heap Allocations\n\nEpsilonScript avoids allocations after compilation, with a few exceptions:\n\nString concatenation with variables allocates:\n```csharp\n'Debug: ' + variable  // Allocates at runtime\n```\n\n`ToString()` calls from type conversions allocate:\n```csharp\nstringVar = 42  // Calls ToString(), allocates\n```\n\nCustom functions allocate if their implementation allocates.\n\nConstants get optimized away at compile time:\n```csharp\n'BUILD_FLAG_' + 4  // Optimized to 'BUILD_FLAG_4', no runtime allocation\n```\n\n### Enforcing Zero Allocations\n\n`Compiler.Options.NoAlloc` throws RuntimeException if code allocates at runtime:\n\n```csharp\n// No allocations:\nvar script = compiler.Compile(\"x * 2 + 1\", Compiler.Options.NoAlloc, variables);\nscript.Execute();\n\n// Throws RuntimeException:\ncompiler.Compile(\"'Debug: ' + variable\", Compiler.Options.NoAlloc, variables).Execute();\ncompiler.Compile(\"stringVar = 42\", Compiler.Options.NoAlloc, variables).Execute();\n\n// No exception - optimized to constant:\ncompiler.Compile(\"'BUILD_FLAG_' + 4\", Compiler.Options.NoAlloc).Execute();\n```\n\nNoAlloc mode does not validate custom function internals.\n\n## Thread Safety\n\nEach thread needs its own `Compiler` instance.\n\n### Recommended Pattern\n\nEach thread creates its own `Compiler`, `CompiledScript`, and `DictionaryVariableContainer`:\n\n```csharp\nParallel.For(0, 100, i =\u003e\n{\n    var compiler = new Compiler();\n    var variables = new DictionaryVariableContainer\n    {\n        [\"x\"] = new VariableValue(10),\n        [\"y\"] = new VariableValue(i)\n    };\n    var script = compiler.Compile(\"x + y\", Compiler.Options.Immutable, variables);\n    script.Execute();\n    Console.WriteLine(script.IntegerValue);\n});\n```\n\n### Unsafe Usage\n\n**Sharing a Compiler across threads:**\n```csharp\nvar compiler = new Compiler(); // UNSAFE: shared across threads\nParallel.For(0, 100, i =\u003e\n{\n    var variables = new DictionaryVariableContainer\n    {\n        [\"x\"] = new VariableValue(10),\n        [\"y\"] = new VariableValue(i)\n    };\n    var script = compiler.Compile(\"x + y\", Compiler.Options.Immutable, variables); // Causes data races\n});\n```\n\n**Sharing a CompiledScript across threads:**\n```csharp\nvar compiler = new Compiler();\nvar script = compiler.Compile(\"x + y\", Compiler.Options.Immutable); // UNSAFE: shared execution state\nParallel.For(0, 100, i =\u003e\n{\n    var variables = new DictionaryVariableContainer\n    {\n        [\"x\"] = new VariableValue(10),\n        [\"y\"] = new VariableValue(i)\n    };\n    script.Execute(variables); // May produce incorrect results\n});\n```\n\n### Guidelines\n\n- Create a new `Compiler` instance per thread\n- Create a new `CompiledScript` per thread\n- Create a new `DictionaryVariableContainer` per thread\n\n## Caching Compiler\n\n`CachingCompiler` wraps `Compiler` and caches compiled scripts by source text, compiler options, and variable container identity. Use it when compiling the same script repeatedly; otherwise stick with `Compiler` directly.\n\n```csharp\nvar caching = new CachingCompiler();\nvar script1 = caching.Compile(\"damage * 2\", Compiler.Options.Immutable);\nvar script2 = caching.Compile(\"damage * 2\", Compiler.Options.Immutable);\n\nConsole.WriteLine(ReferenceEquals(script1, script2)); // True\n```\n\nPre-hashing to reuse a key:\n```csharp\nvar caching = new CachingCompiler();\nvar source = CachedSourceText.From(\"damage * 2\");\n\nvar script1 = caching.Compile(source, Compiler.Options.Immutable);\nvar script2 = caching.Compile(source, Compiler.Options.Immutable);\n```\n\nNotes:\n- Cache keys include the variable container by reference; different containers do not share entries.\n- Adding custom functions clears the cache because constant folding selects overloads at compile time.\n\n## Motivation\n\nGame designers need to express logic. A quest might require \"the player fought no monsters and has the key\". Damage calculation might depend on weapon type and armor type. These conditions are too complex for simple data tables but don't need full scripting languages.\n\nThe usual options don't fit well. Pure data (Excel, JSON, Unity serialization) means asking programmers to add new columns or special cases for each new rule. Full scripting languages (Lua, Python) give designers freedom to write anything—including infinite loops, performance problems, or code that breaks game systems in subtle ways.\n\nEpsilonScript narrows the scope to just expressions. No loops. No variable declarations. Just evaluate an expression and return a result. This constraint is the point: designers can express complex calculations and conditions, but can't write code that spirals into maintenance problems.\n\nExpressions need to run fast because they run often—hundreds of times per frame in game loops. Compilation happens once, producing a reusable script that executes without reparsing or allocating memory. The variable container pattern means you compile once, then execute it for every entity that needs that calculation.\n\nThe syntax deliberately omits features that hurt readability. No ternary operator—use `ifelse(condition, true_value, false_value)` instead. No implicit behaviors that require remembering special cases. Programmers control exactly what functions exist and what they do. The result is expressions that everyone on the team can read.\n\nWithin visual scripting systems, EpsilonScript handles the case where connecting nodes becomes tedious. Wiring up `base_damage * weapon_effectiveness(weapon, armor) * range_modifier` with individual nodes creates clutter. An expression node keeps the graph focused on control flow while delegating calculations to text, which is clearer for that purpose.\n\n## Development\n\n### T4 Template Code Generation\n\nCustom function implementations use T4 templates to reduce maintenance burden and ensure consistency.\n\n**Generated files**:\n- `EpsilonScript/Function/CustomFunction.Generated.cs`\n- `EpsilonScript/Function/CustomFunction.Contextual.Generated.cs`\n\n**Template files**:\n- `EpsilonScript/Function/CustomFunction.Generated.tt`\n- `EpsilonScript/Function/CustomFunction.Contextual.Generated.tt`\n\n#### Prerequisites\n\nInstall the T4 command-line tool:\n```bash\ndotnet tool install -g dotnet-t4\n```\n\n#### Regenerating Code\n\nAfter modifying templates, regenerate the code:\n```bash\ncd EpsilonScript/Function\nt4 CustomFunction.Generated.tt\nt4 CustomFunction.Contextual.Generated.tt\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faki-null%2Fepsilon-script","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faki-null%2Fepsilon-script","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faki-null%2Fepsilon-script/lists"}