commit 52ed309701e677c4d8f7c667ed458a60b7bddba0 Author: Zhongwei Li Date: Sat Nov 29 18:25:07 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..b7436bc --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "east", + "description": "Write and validate East code - a portable, type-safe functional language", + "version": "1.0.0", + "author": { + "name": "Elara AI Pty Ltd", + "email": "support@elara.ai" + }, + "skills": [ + "./skills" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..85f315f --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# east + +Write and validate East code - a portable, type-safe functional language diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..dffa418 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,57 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:elaraai/east-plugin:", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "1268acb361cf8aabde9df2969c9755f4139f5021", + "treeHash": "40fcc7095ee35799e0b9312a4b39e9480312aef6f73cb1989de479f8e7b46677", + "generatedAt": "2025-11-28T10:16:44.350811Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "east", + "description": "Write and validate East code - a portable, type-safe functional language", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "221577e5818fd60a0e29d67aca4961e8eca761461e0de2d87ceffb3dfd30bf2b" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "20e364aee32b6520e8fca299c4de99530fbeea75eb8c596b37fd796b5bad172a" + }, + { + "path": "skills/east-development/USAGE.md", + "sha256": "223cb82ba317267add3cbd9a9873885d8120aaa1841eb6997fab78f45b6df798" + }, + { + "path": "skills/east-development/STDLIB.md", + "sha256": "766ff1a74bda0491997bf96ba31313115fac34a459ccbc02281cfef04057e05f" + }, + { + "path": "skills/east-development/SKILL.md", + "sha256": "2999054f675f379cde14857cf501dee6caa770ca75eaa8960fec23e52aab896e" + }, + { + "path": "skills/east-development/examples/01-basic-functions.md", + "sha256": "345b79bcfae4c013c6c6d15a22797d96af62481be6b8cc9d1bbe9b921c988dd5" + } + ], + "dirSha256": "40fcc7095ee35799e0b9312a4b39e9480312aef6f73cb1989de479f8e7b46677" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/east-development/SKILL.md b/skills/east-development/SKILL.md new file mode 100644 index 0000000..e53832c --- /dev/null +++ b/skills/east-development/SKILL.md @@ -0,0 +1,167 @@ +--- +name: east-development +description: Write East code - a portable, type-safe functional language. Use when writing East functions, working with East IR, or creating portable computations. Automatically validates code with east_compile tool. +--- + +# East Development Skill + +## What is East? + +East is a **statically typed, expression-based functional language** embedded in TypeScript that compiles to portable **IR (Intermediate Representation)**. East enables you to write type-safe, portable computations that can execute in different environments (JavaScript, Julia, Python, etc.) without modification. + +**Key Characteristics:** +- **Portable**: Compiles to IR that runs anywhere +- **Type-safe**: Strong static typing with compile-time guarantees +- **Functional**: Expression-based with immutable data structures +- **Sandboxed**: Controlled execution environment with explicit platform functions + +## When to Use This Skill + +Use the `east-development` skill when: +- User asks to write East functions or East code +- User wants to create portable computations or IR +- User mentions East types, expressions, or the East language +- User needs type-safe functional programming with portability +- User wants to validate East code syntax + +## Quick Example + +Here's a simple East function that adds two integers: + +```typescript +return East.function( + [East.IntegerType, East.IntegerType], + East.IntegerType, + ($, x, y) => { + return $.return(x.add(y)); + } +); +``` + +This can be validated using the `east_compile` tool (provided by this plugin's MCP server). + +## Documentation + +This skill provides comprehensive East documentation: + +- **[USAGE.md](./USAGE.md)** - Complete East developer guide covering: + - All types (Null, Boolean, Integer, Float, String, DateTime, Blob, Array, Set, Dict, Struct, Variant) + - Functions and expressions + - Control flow and operations + - JSON serialization format + - Platform functions + +- **[STDLIB.md](./STDLIB.md)** - Standard library reference covering: + - Formatting utilities (e.g., `Integer.printCommaSeparated()`) + - Conversion functions (e.g., `DateTime.fromEpochMilliseconds()`) + - Generation utilities for each type + +## Validation Workflow + +When writing East code for users, follow this workflow: + +1. **Write the function** using the East TypeScript API (see USAGE.md for syntax) +2. **Validate with `east_compile`** - call the tool to check syntax and generate IR +3. **Fix any errors** - use compilation error messages to correct issues +4. **Return validated code** - provide the working East function to the user + +Example validation: +```json +{ + "typescript_code": "return East.function([East.IntegerType], East.IntegerType, ($, x) => $.return(x.add(1n)))" +} +``` + +## Important: No Imports Required + +When calling `east_compile`, the `East` object is already injected into scope. Your code should: + +- **NOT** include import statements +- **Use** `East.function()`, `East.IntegerType`, etc. directly +- **Return** an East function expression +- **Use** bigint literals for integers (e.g., `1n`, `42n`) + +**Correct:** +```typescript +return East.function([East.IntegerType], East.IntegerType, ($, x) => { + return $.return(x.add(1n)); +}); +``` + +**Incorrect:** +```typescript +import { East } from '@elaraai/east'; // ❌ Don't import +const fn = East.function(...); // ❌ Don't assign, must return +``` + +## Common Patterns + +### Basic Arithmetic +See USAGE.md § Integer and Float sections for arithmetic operations. + +### Working with Collections +See USAGE.md § Array, Set, and Dict sections for collection operations. + +### Structured Data +See USAGE.md § Struct section for working with structured types. + +### Pattern Matching +See USAGE.md § Variant section for sum types and pattern matching. + +### Platform Functions +See USAGE.md § Platform Functions section for declaring external functions. + +## Type System Quick Reference + +**Primitive Types:** +- `East.NullType` - Null value +- `East.BooleanType` - true/false +- `East.IntegerType` - Arbitrary precision integers (use bigint literals: `42n`) +- `East.FloatType` - IEEE 754 double precision floats +- `East.StringType` - Unicode strings +- `East.DateTimeType` - ISO 8601 date-times with timezone +- `East.BlobType` - Binary data + +**Compound Types:** +- `East.ArrayType(T)` - Ordered list of elements +- `East.SetType(T)` - Unordered unique elements +- `East.DictType(K, V)` - Key-value mappings +- `East.StructType({field: Type, ...})` - Product types with named fields +- `East.VariantType({Tag: Type, ...})` - Sum types (tagged unions) + +## Examples + +For worked examples, see: +- USAGE.md - Contains comprehensive examples for each type and operation +- `examples/` directory - Additional practical examples (if present) + +## Tips for Success + +1. **Always validate** - Use `east_compile` to catch errors early +2. **Use bigint literals** - Integers must be `1n`, not `1` +3. **Return the function** - Code must return an `East.function()` call +4. **Check types carefully** - East is strongly typed; mismatches cause compilation errors +5. **Read error messages** - Compilation errors provide specific guidance +6. **Reference docs** - USAGE.md and STDLIB.md contain all the details + +## Common Errors and Solutions + +**"Code must evaluate to an East function"** +- Ensure your code returns `East.function(...)` +- Don't assign to a variable; return directly + +**Type mismatch errors** +- Check that expression types match function signatures +- Verify bigint literals are used for integers +- Ensure operations are called on correct types + +**"Expected bigint" errors** +- Use `42n` not `42` for integer values +- Integer literals must have the `n` suffix + +## Getting Help + +- Review USAGE.md for comprehensive type and operation documentation +- Check STDLIB.md for standard library utilities +- Examine compilation errors for specific guidance +- Test incrementally with `east_compile` diff --git a/skills/east-development/STDLIB.md b/skills/east-development/STDLIB.md new file mode 100644 index 0000000..87ac59e --- /dev/null +++ b/skills/east-development/STDLIB.md @@ -0,0 +1,345 @@ +# East Standard Library + +The East Standard Library provides utility functions that extend East's core expression types with additional formatting, conversion, and generation capabilities. + +--- + +## Table of Contents + +- [Boolean](#boolean) +- [Integer](#integer) +- [Float](#float) +- [String](#string) +- [DateTime](#datetime) +- [Blob](#blob) +- [Array](#array) +- [Set](#set) +- [Dict](#dict) +- [Struct](#struct) +- [Variant](#variant) + +--- + +## Standard Library Expressions + +This section documents the standard library expression available for each East type. These are accessed through the `East` namespace (e.g., `East.Integer.printCommaSeparated()`, `East.DateTime.fromEpochMilliseconds()`). + +For core operations on expressions (like `.add()`, `.multiply()`, `.map()`), see [USAGE.md](./USAGE.md). + +### Boolean + +Boolean expressions currently have no standard library methods. + +--- + +### Integer + +The Integer standard library provides formatting and rounding utilities for integer values. + +**Example:** +```typescript +const formatNumber = East.function([IntegerType], StringType, ($, value) => { + // Format with thousand separators + const commaSeparated = East.Integer.printCommaSeperated(value); + // Alternative ways: + // const commaSeparated = $.let(East.Integer.printCommaSeperated); + // $(commaSeparated(value)); + + // Format with compact units (K, M, B, etc.) + const compact = East.Integer.printCompact(value); + + // Round to nearest multiple + const rounded = East.Integer.roundNearest(value, 10n); + + // Format as ordinal (1st, 2nd, 3rd, etc.) + const ordinal = East.Integer.printOrdinal(rounded); + + $.return(East.str`${commaSeparated} = ${compact}, rounded to ${ordinal}`); +}); + +const compiled = East.compile(formatNumber, {}); +console.log(compiled(1234567n)); // "1,234,567 = 1.23M, rounded to 1234570th" +console.log(compiled(47n)); // "47 = 47, rounded to 50th" +``` + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| **Formatting** | +| `East.Integer.printCommaSeperated(x: IntegerExpr \| bigint): StringExpr` | Format with commas: `"1,234,567"` | `East.Integer.printCommaSeperated(1234567n)` | +| `East.Integer.printCompact(x: IntegerExpr \| bigint): StringExpr` | Business units: `"1.5M"`, `"21K"` | `East.Integer.printCompact(1500000n)` | +| `East.Integer.printCompactSI(x: IntegerExpr \| bigint): StringExpr` | SI units: `"1.5M"`, `"21k"` | `East.Integer.printCompactSI(21000n)` | +| `East.Integer.printCompactComputing(x: IntegerExpr \| bigint): StringExpr` | Binary units (1024): `"1.5Mi"`, `"21ki"` | `East.Integer.printCompactComputing(21504n)` | +| `East.Integer.printOrdinal(x: IntegerExpr \| bigint): StringExpr` | Ordinal: `"1st"`, `"2nd"`, `"3rd"` | `East.Integer.printOrdinal(1n)` | +| `East.Integer.printPercentage(x: IntegerExpr \| bigint): StringExpr` | Format as percentage: `"45%"` | `East.Integer.printPercentage(45n)` | +| **Utilities** | +| `East.Integer.digitCount(x: IntegerExpr \| bigint): IntegerExpr` | Count decimal digits (excluding sign) | `East.Integer.digitCount(1234n)` | +| **Rounding** | +| `East.Integer.roundNearest(x: IntegerExpr \| bigint, step: IntegerExpr \| bigint): IntegerExpr` | Round to nearest multiple of step | `East.Integer.roundNearest(47n, 10n)` | +| `East.Integer.roundUp(x: IntegerExpr \| bigint, step: IntegerExpr \| bigint): IntegerExpr` | Round up (ceiling) to multiple of step | `East.Integer.roundUp(41n, 10n)` | +| `East.Integer.roundDown(x: IntegerExpr \| bigint, step: IntegerExpr \| bigint): IntegerExpr` | Round down (floor) to multiple of step | `East.Integer.roundDown(47n, 10n)` | +| `East.Integer.roundTruncate(x: IntegerExpr \| bigint, step: IntegerExpr \| bigint): IntegerExpr` | Round towards zero to multiple of step | `East.Integer.roundTruncate(-47n, 10n)` | + + +--- + +### Float + +The Float standard library provides rounding, comparison, and formatting utilities for floating-point values. + +**Example:** +```typescript +const formatNumber = East.function([FloatType], StringType, ($, value) => { + // Round to 2 decimal places + const rounded = East.Float.roundToDecimals(value, 2n); + + // Format as currency with $ sign + const currency = East.Float.printCurrency(value); + + // Format with comma separators + const formatted = East.Float.printCommaSeperated(value, 2n); + + // Format as percentage + const percentage = East.Float.printPercentage(value, 1n); + + // Format in compact form (21.5K, 1.82M, etc.) + const compact = East.Float.printCompact(value); + + $.return(East.str`Currency: ${currency}, Formatted: ${formatted}, Compact: ${compact}`); +}); + +const compiled = East.compile(formatNumber, {}); +console.log(compiled(1234567.89)); +// "Currency: $1234567.89, Formatted: 1234567.89, Compact: 1.23M" +``` + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| **Rounding to Integers** | +| `East.Float.roundFloor(x: FloatExpr \| number): IntegerExpr` | Round down to nearest integer (floor) | `East.Float.roundFloor(3.7)` → `3n` | +| `East.Float.roundCeil(x: FloatExpr \| number): IntegerExpr` | Round up to nearest integer (ceiling) | `East.Float.roundCeil(3.2)` → `4n` | +| `East.Float.roundHalf(x: FloatExpr \| number): IntegerExpr` | Round to nearest integer (half-away-from-zero) | `East.Float.roundHalf(3.5)` → `4n` | +| `East.Float.roundTrunc(x: FloatExpr \| number): IntegerExpr` | Truncate towards zero | `East.Float.roundTrunc(-3.7)` → `-3n` | +| **Rounding to Step Values** | +| `East.Float.roundNearest(x: FloatExpr \| number, step: FloatExpr \| number): FloatExpr` | Round to nearest multiple of step | `East.Float.roundNearest(47.3, 10.0)` → `50.0` | +| `East.Float.roundUp(x: FloatExpr \| number, step: FloatExpr \| number): FloatExpr` | Round up (ceiling) to multiple of step | `East.Float.roundUp(41.2, 10.0)` → `50.0` | +| `East.Float.roundDown(x: FloatExpr \| number, step: FloatExpr \| number): FloatExpr` | Round down (floor) to multiple of step | `East.Float.roundDown(47.8, 10.0)` → `40.0` | +| `East.Float.roundTruncate(x: FloatExpr \| number, step: FloatExpr \| number): FloatExpr` | Round towards zero to multiple of step | `East.Float.roundTruncate(-47.8, 10.0)` → `-40.0` | +| `East.Float.roundToDecimals(x: FloatExpr \| number, decimals: IntegerExpr \| bigint): FloatExpr` | Round to specified number of decimal places | `East.Float.roundToDecimals(3.14159, 2n)` → `3.14` | +| **Comparison** | +| `East.Float.approxEqual(x: FloatExpr \| number, y: FloatExpr \| number, epsilon: FloatExpr \| number): BooleanExpr` | Check if two floats are approximately equal within tolerance | `East.Float.approxEqual(0.1, 0.10001, 0.001)` → `true` | +| **Formatting** | +| `East.Float.printCommaSeperated(x: FloatExpr \| number, decimals: IntegerExpr \| bigint): StringExpr` | Format with comma separators | `East.Float.printCommaSeperated(1234.567, 2n)` → `"1,234.57"` | +| `East.Float.printCurrency(x: FloatExpr \| number): StringExpr` | Format as currency with $ and 2 decimals | `East.Float.printCurrency(1234.567)` → `"$1234.57"` | +| `East.Float.printFixed(x: FloatExpr \| number, decimals: IntegerExpr \| bigint): StringExpr` | Format with fixed decimal places | `East.Float.printFixed(3.1, 3n)` → `"3.100"` | +| `East.Float.printCompact(x: FloatExpr \| number): StringExpr` | Business units: `"21.5K"`, `"1.82M"`, `"314B"` | `East.Float.printCompact(1500000.0)` → `"1.5M"` | +| `East.Float.printPercentage(x: FloatExpr \| number, decimals: IntegerExpr \| bigint): StringExpr` | Format as percentage | `East.Float.printPercentage(0.452, 1n)` → `"45.2%"` | + +--- + +### String + +The String standard library provides error formatting utilities. + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| `East.String.printError(message: StringExpr \| string, stack: ArrayExpr>): StringExpr` | Pretty-print error with stack trace | `East.String.printError(errorMsg, stackTrace)` | + + +--- + +### DateTime + +The DateTime standard library provides construction and rounding utilities for date and time values. + +**Example:** +```typescript +const processDate = East.function([DateTimeType], StringType, ($, timestamp) => { + // Create from epoch milliseconds + const epoch = East.DateTime.fromEpochMilliseconds(1710498645123n); + + // Create from components (year, month, day, hour, minute, second, millisecond) + const constructed = East.DateTime.fromComponents(2024n, 3n, 15n, 10n, 30n, 45n, 123n); + + // Round timestamp to start of day + const dayStart = East.DateTime.roundDownDay(timestamp, 1n); + + // Round to nearest hour + const nearestHour = East.DateTime.roundNearestHour(timestamp, 1n); + + // Round to start of ISO week (Monday) + const weekStart = East.DateTime.roundDownWeek(timestamp, 1n); + + $.return(East.str`Day: ${dayStart}, Hour: ${nearestHour}, Week: ${weekStart}`); +}); + +const compiled = East.compile(processDate, {}); +const date = new Date("2024-03-15T14:30:45.123Z"); +console.log(compiled(date)); +// Output: Day: 2024-03-15T00:00:00.000, Hour: 2024-03-15T15:00:00.000, Week: 2024-03-11T00:00:00.000 +``` + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| **Construction** | +| `East.DateTime.fromEpochMilliseconds(ms: IntegerExpr \| bigint): DateTimeExpr` | Create from Unix epoch milliseconds | `East.DateTime.fromEpochMilliseconds(1640000000000n)` | +| `East.DateTime.fromComponents(y: IntegerExpr \| bigint, m: IntegerExpr \| bigint = 1n, d: IntegerExpr \| bigint = 1n, h: IntegerExpr \| bigint = 0n, min: IntegerExpr \| bigint = 0n, s: IntegerExpr \| bigint = 0n, ms: IntegerExpr \| bigint = 0n): DateTimeExpr` | Create from components | `East.DateTime.fromComponents(2025n, 1n, 15n)` | +| **Rounding - Seconds** | +| `East.DateTime.roundNearestSecond(date: DateTimeExpr, step: IntegerExpr \| bigint): DateTimeExpr` | Round to nearest multiple of seconds | `East.DateTime.roundNearestSecond(date, 30n)` | +| `East.DateTime.roundUpSecond(date: DateTimeExpr, step: IntegerExpr \| bigint): DateTimeExpr` | Round up to next multiple of seconds | `East.DateTime.roundUpSecond(date, 15n)` | +| `East.DateTime.roundDownSecond(date: DateTimeExpr, step: IntegerExpr \| bigint): DateTimeExpr` | Round down to previous multiple of seconds | `East.DateTime.roundDownSecond(date, 10n)` | +| **Rounding - Minutes** | +| `East.DateTime.roundNearestMinute(date: DateTimeExpr, step: IntegerExpr \| bigint): DateTimeExpr` | Round to nearest multiple of minutes | `East.DateTime.roundNearestMinute(date, 15n)` | +| `East.DateTime.roundUpMinute(date: DateTimeExpr, step: IntegerExpr \| bigint): DateTimeExpr` | Round up to next multiple of minutes | `East.DateTime.roundUpMinute(date, 5n)` | +| `East.DateTime.roundDownMinute(date: DateTimeExpr, step: IntegerExpr \| bigint): DateTimeExpr` | Round down to previous multiple of minutes | `East.DateTime.roundDownMinute(date, 30n)` | +| **Rounding - Hours** | +| `East.DateTime.roundNearestHour(date: DateTimeExpr, step: IntegerExpr \| bigint): DateTimeExpr` | Round to nearest multiple of hours | `East.DateTime.roundNearestHour(date, 1n)` | +| `East.DateTime.roundUpHour(date: DateTimeExpr, step: IntegerExpr \| bigint): DateTimeExpr` | Round up to next multiple of hours | `East.DateTime.roundUpHour(date, 6n)` | +| `East.DateTime.roundDownHour(date: DateTimeExpr, step: IntegerExpr \| bigint): DateTimeExpr` | Round down to previous multiple of hours | `East.DateTime.roundDownHour(date, 1n)` | +| **Rounding - Days** | +| `East.DateTime.roundNearestDay(date: DateTimeExpr, step: IntegerExpr \| bigint): DateTimeExpr` | Round to nearest multiple of days | `East.DateTime.roundNearestDay(date, 1n)` | +| `East.DateTime.roundUpDay(date: DateTimeExpr, step: IntegerExpr \| bigint): DateTimeExpr` | Round up to next multiple of days | `East.DateTime.roundUpDay(date, 7n)` | +| `East.DateTime.roundDownDay(date: DateTimeExpr, step: IntegerExpr \| bigint): DateTimeExpr` | Round down to previous multiple of days | `East.DateTime.roundDownDay(date, 1n)` | +| **Rounding - Weeks** | +| `East.DateTime.roundNearestWeek(date: DateTimeExpr, step: IntegerExpr \| bigint): DateTimeExpr` | Round to nearest Monday (ISO week start) | `East.DateTime.roundNearestWeek(date, 1n)` | +| `East.DateTime.roundUpWeek(date: DateTimeExpr, step: IntegerExpr \| bigint): DateTimeExpr` | Round up to next Monday (ISO week start) | `East.DateTime.roundUpWeek(date, 1n)` | +| `East.DateTime.roundDownWeek(date: DateTimeExpr, step: IntegerExpr \| bigint): DateTimeExpr` | Round down to previous Monday (ISO week start) | `East.DateTime.roundDownWeek(date, 1n)` | +| **Rounding - Months & Years** | +| `East.DateTime.roundDownMonth(date: DateTimeExpr, step: IntegerExpr \| bigint): DateTimeExpr` | Round down to start of month | `East.DateTime.roundDownMonth(date, 1n)` | +| `East.DateTime.roundDownYear(date: DateTimeExpr, step: IntegerExpr \| bigint): DateTimeExpr` | Round down to start of year | `East.DateTime.roundDownYear(date, 1n)` | + + +--- + +### Blob + +The Blob standard library provides binary encoding utilities. + +**Example:** +```typescript +const serializeValue = East.function([IntegerType], BlobType, ($, value) => { + // Encode value to BEAST binary format (version 1 or 2) + const encoded = East.Blob.encodeBeast(value, 'v2'); + // Alternative: const encoded = East.Blob.encodeBeast(value, 'v1'); + + $.return(encoded); +}); + +const compiled = East.compile(serializeValue, {}); +const blob = compiled(42n); +console.log(blob); // Uint8Array containing BEAST-encoded 42n +``` + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| `East.Blob.encodeBeast(value: Expr, version: 'v1' \| 'v2' = 'v1'): BlobExpr` | Encode value to binary BEAST format (v1 or v2) | `East.Blob.encodeBeast(myValue, 'v2')` | + + +--- + +### Array + +The Array standard library provides generation utilities for creating arrays. + +**Example:** +```typescript +const createSequences = East.function([], ArrayType(IntegerType), $ => { + // Generate integer range [0, 10) with step of 2 + const range = East.Array.range(0n, 10n, 2n); + // Result: [0, 2, 4, 6, 8] + + // Generate 11 equally-spaced floats from 0.0 to 1.0 (inclusive) + const linspace = East.Array.linspace(0.0, 1.0, 11n); + // Result: [0.0, 0.1, 0.2, ..., 0.9, 1.0] + + // Generate array using function (index -> value) + const generated = East.Array.generate(5n, IntegerType, ($, i) => i.multiply(i)); + // Result: [0, 1, 4, 9, 16] + + $.return(range); +}); + +const compiled = East.compile(createSequences, {}); +console.log(compiled()); // [0n, 2n, 4n, 6n, 8n] +``` + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| `East.Array.range(start: IntegerExpr \| bigint, end: IntegerExpr \| bigint, step: IntegerExpr \| bigint = 1n): ArrayExpr` | Generate integer range [start, end) | `East.Array.range(0n, 10n, 2n)` | +| `East.Array.linspace(start: FloatExpr \| number, stop: FloatExpr \| number, size: IntegerExpr \| bigint): ArrayExpr` | Generate equally-spaced floats [start, stop] (inclusive) | `East.Array.linspace(0.0, 1.0, 11n)` | +| `East.Array.generate(size: IntegerExpr \| bigint, valueType: T, valueFn: FunctionType<[IntegerType], T>): ArrayExpr` | Generate n elements using function from index | `East.Array.generate(10n, IntegerType, ($, i) => i.multiply(2n))` | + + +--- + +### Set + +The Set standard library provides generation utilities for creating sets. + +**Example:** +```typescript +const createSet = East.function([], SetType(IntegerType), $ => { + // Generate set using function (index -> key) + const generated = East.Set.generate(5n, IntegerType, ($, i) => i.multiply(2n)); + // Result: Set {0, 2, 4, 6, 8} + + $.return(generated); +}); + +const compiled = East.compile(createSet, {}); +console.log(compiled()); // Set {0n, 2n, 4n, 6n, 8n} +``` + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| `East.Set.generate(size: IntegerExpr \| bigint, keyType: K, keyFn: FunctionType<[IntegerType], K>, onConflict?: FunctionType<[K], K>): SetExpr` | Generate set from function (errors on duplicates) | `East.Set.generate(10n, IntegerType, ($, i) => i)` | + + +--- + +### Dict + +The Dict standard library provides generation utilities for creating dictionaries. + +**Example:** +```typescript +const createDict = East.function([], DictType(IntegerType, IntegerType), $ => { + // Generate dict using functions (index -> key, index -> value) + const generated = East.Dict.generate( + 5n, + IntegerType, + IntegerType, + ($, i) => i, // key function + ($, i) => i.multiply(10n) // value function + ); + // Result: Map {0 => 0, 1 => 10, 2 => 20, 3 => 30, 4 => 40} + + $.return(generated); +}); + +const compiled = East.compile(createDict, {}); +console.log(compiled()); // Map {0n => 0n, 1n => 10n, 2n => 20n, 3n => 30n, 4n => 40n} +``` + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| `East.Dict.generate(size: IntegerExpr \| bigint, keyType: K, valueType: V, keyFn: FunctionType<[IntegerType], K>, valueFn: FunctionType<[IntegerType], V>, onConflict?: FunctionType<[V, V, K], V>): DictExpr` | Generate dict from functions (errors on duplicates) | `East.Dict.generate(10n, IntegerType, IntegerType, ($, i) => i, ($, i) => i.multiply(2n))` | + +--- + +### Struct + +Struct expressions currently have no standard library methods. + +--- + +### Variant + +Variant expressions currently have no standard library methods. \ No newline at end of file diff --git a/skills/east-development/USAGE.md b/skills/east-development/USAGE.md new file mode 100644 index 0000000..b015f5f --- /dev/null +++ b/skills/east-development/USAGE.md @@ -0,0 +1,937 @@ +# East Developer Guide + +Usage guide for the East programming language. + +This guide covers core expression types and their operations. For additional formatting, conversion, and generation utilities, see the **[Standard Library documentation](./STDLIB.md)**. + +--- + +## Table of Contents + +- [Quick Start](#quick-start) +- [Types](#types) +- [East Namespace](#east-namespace) +- [Functions](#functions) +- [Expressions](#expressions) + +--- + +## Quick Start + +East is a **statically typed, expression-based language** embedded in TypeScript. You write East programs using a fluent TypeScript API, then compile them to portable **IR (Intermediate Representation)** that can execute in different environments (javascript, julia, python, etc). East code runs in a controlled environment - you define **platform functions** that your East code can call, providing access to external capabilities like logging, database access, or any other effects you want to expose. + +**Workflow:** +1. **Define platform functions** - create an object with the external functions you want East code to access (e.g., logging, I/O, database queries) +2. **Define East functions** using `East.function()` with explicit types +3. **Build expressions** using methods on typed expression objects (`.add()`, `.map()`, etc.) +4. **Compile and run** using `East.compile(fn, platform)` to execute the code +5. **(Optional) Serialize to IR** using `.toIR()` for transmission/storage across environments + +### Basic Example + +```typescript +import { East, IntegerType, ArrayType, StructType, StringType, DictType, NullType } from "@elaraai/east"; + +// Platform function for logging +const log = East.platform("log", [StringType], NullType); + +const platform = [ + log.implement(console.log), +]; + +// Define sale data type +const SaleType = StructType({ + product: StringType, + quantity: IntegerType, + price: IntegerType +}); + +// Calculate revenue per product from sales data +const calculateRevenue = East.function( + [ArrayType(SaleType)], + DictType(StringType, IntegerType), + ($, sales) => { + // Group sales by product and sum revenue (quantity × price) + const revenueByProduct = sales.groupSum( + // Group by product name + ($, sale) => sale.product, + // Sum quantity × price + ($, sale) => sale.quantity.multiply(sale.price) + ); + + // Log revenue for each product + $(log(East.str`Total Revenue: ${revenueByProduct.sum()}`)); + + $.return(revenueByProduct); + } +); + +// Compile and execute +const compiled = East.compile(calculateRevenue, platform); + +const sales = [ + { product: "Widget", quantity: 10n, price: 50n }, + { product: "Gadget", quantity: 5n, price: 100n }, + { product: "Widget", quantity: 3n, price: 50n } +]; + +const result = compiled(sales); +// Result: Map { "Widget" => 650n, "Gadget" => 500n } +// Logs: "Gadget: $500" and "Widget: $650" +``` +--- + +## Types + +East is statically typed for speed and correctness, using **structural typing** for ease of use. All types (except functions) have a **total ordering**, enabling their use as Dict keys and Set elements, and allowing deep comparison operations. + +### Type System Concepts + +- **`EastType`** - A type descriptor (e.g., `IntegerType`, `StringType`, `ArrayType`) +- **`ValueTypeOf`** - The JavaScript runtime value for a type (e.g., `bigint` for `IntegerType`, `string` for `StringType`) +- **`Expr`** - A typed expression that can be composed and compiled (e.g., `IntegerExpr`, `StringExpr`) + +**Key insight:** Most East functions accept either `Expr` OR `ValueTypeOf`, allowing you to mix expressions and raw values. + +| Type | JavaScript Value | Mutability | Description | +|------|-----------------|------------|-------------| +| **Primitive Types** | | | | +| `NullType` | `null` | Immutable | Unit type (single value) | +| `BooleanType` | `boolean` | Immutable | True or false | +| `IntegerType` | `bigint` | Immutable | 64-bit signed integers | +| `FloatType` | `number` | Immutable | IEEE 754 double-precision (distinguishes `-0.0` from `0.0`) | +| `StringType` | `string` | Immutable | UTF-8 text | +| `DateTimeType` | `Date` | Immutable | UTC timestamp with millisecond precision | +| `BlobType` | `Uint8Array` | Immutable | Binary data | +| **Compound Types** | | | | +| `ArrayType` | `T[]` | **Mutable** | Ordered collection | +| `SetType` | `Set` | **Mutable** | Sorted set (keys ordered by total ordering) | +| `DictType` | `Map` | **Mutable** | Sorted dict (keys ordered by total ordering) | +| `StructType` | `{...}` | Immutable | Product type (field order matters) | +| `VariantType` | `variant` | Immutable | Sum type (cases sorted alphabetically) | +| **Function Type** | | | | +| `FunctionType` | Function | Immutable | First-class function (serializable as IR, not as data) | + +### Important Notes + +- **Total ordering**: All types (even `Float` with `NaN`, `-0.0`) have a defined total ordering +- **Immutable types**: Can be used as Dict keys and Set elements + - Includes: All primitives, Blob, Struct, Variant + - Excludes: Array, Set, Dict, Function +- **Data types**: Can be serialized (excludes Function) +- **Equality**: Deep structural equality for all types + - Mutable types also support reference equality via `East.is()` +- **Field/case order**: + - Struct field order is significant for structural typing + - Variant cases are automatically sorted alphabetically +- **Operations marked ❗**: Can throw runtime errors + +--- + +## East Namespace + +The `East` namespace is the main entry point for building East programs using a **fluent interface**. In East, you construct programs by building **expressions** - typed values that can be composed, transformed, and eventually compiled to executable code. + +Think of expressions as building blocks: `East.value(42n)` creates an integer expression, and you can call methods on it like `.add(1n)` to build larger expressions. The `East` namespace provides functions to create expressions from JavaScript values, perform comparisons, and access type-specific utilities. + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| **Expression Creation** | +| `value(val: ValueTypeOf): Expr` | Create expression from JavaScript value | `East.value(42n)` | +| `value(val: Expr \| ValueTypeOf, type: T): Expr` | Create expression with explicit type | `East.value(x, IntegerType)` | +| str\`...\`: StringExpr | String interpolation template | East.str\`Hello ${name}\` | +| `print(expr: Expr): StringExpr` | Convert any expression to string representation | `East.print(x)` | +| **Function Definition** | +| `function(inputs: I, output: O, body: ($, ...args) => Expr \| value): FunctionExpr` | Define a function (see [Function](#function)) | `East.function([IntegerType], IntegerType, ($, x) => x.add(1n))` | +| `compile(fn: FunctionExpr, platform: PlatformFunction[]): (...inputs) => ValueTypeOf` | Compile East function to executable JavaScript | `East.compile(myFunction, [log.implement(console.log)])` | +| `platform(name: string, inputs: I, output: O): (...args) => ExprType` | Create callable helper for platform function | `const log = East.platform("log", [StringType], NullType)` | +| **Comparisons** | +| `equal(a: Expr, b: Expr \| ValueTypeOf): BooleanExpr` | Deep equality comparison | `East.equal(x, 10n)` | +| `notEqual(a: Expr, b: Expr \| ValueTypeOf): BooleanExpr` | Deep inequality comparison | `East.notEqual(x, 0n)` | +| `less(a: Expr, b: Expr \| ValueTypeOf): BooleanExpr` | Less than comparison (total ordering) | `East.less(x, 100n)` | +| `lessEqual(a: Expr, b: Expr \| ValueTypeOf): BooleanExpr` | Less than or equal comparison | `East.lessEqual(x, y)` | +| `greater(a: Expr, b: Expr \| ValueTypeOf): BooleanExpr` | Greater than comparison | `East.greater(x, 0n)` | +| `greaterEqual(a: Expr, b: Expr \| ValueTypeOf): BooleanExpr` | Greater than or equal comparison | `East.greaterEqual(score, 50n)` | +| `is(a: Expr, b: Expr \| ValueTypeOf): BooleanExpr` | Reference equality (for mutable types) | `East.is(arr1, arr2)` | +| **Utilities** | +| `min(a: Expr, b: Expr \| ValueTypeOf): Expr` | Minimum of two values (uses total ordering) | `East.min(x, 100n)` | +| `max(a: Expr, b: Expr \| ValueTypeOf): Expr` | Maximum of two values (uses total ordering) | `East.max(x, 0n)` | +| `clamp(x: Expr, min: Expr \| ValueTypeOf, max: Expr \| ValueTypeOf): Expr` | Clamp value between min and max | `East.clamp(x, 0n, 100n)` | + +--- + +### Functions + +Functions in East are first-class values that can be defined, passed around, and called. They have concrete input and output types, and their bodies are written using a fluent interface. + +This section follows the workflow from [Quick Start](#quick-start): +1. Define platform functions +2. Define East functions +3. Compile and execute +4. (Optional) Serialize for transmission + +--- + +#### Defining Platform Functions + +East code runs in a sandboxed environment and can only interact with the outside world through **platform functions** that you explicitly provide. This ensures security and makes East code portable across different environments. + +**Creating platform functions:** + +Use `East.platform()` to create callable helpers that reference platform functions: + +```typescript +import { East, StringType, NullType, IntegerType } from "@elaraai/east"; + +// Define platform function helpers +const log = East.platform("log", [StringType], NullType); + +// define the run-time implementation +const platform = [ + log.implement(console.log), +]; +``` + +**Defining and compiling an East function:** + +```typescript +const greet = East.function([StringType], NullType, ($, name) => { + // Call platform function from East code + $(log(East.str`Hello, ${name}!`)); + $.return(null); +}); + +const compiled = East.compile(greet, platform); +compiled("Alice"); // Logs: "Hello, Alice!" +``` + +The compiled function has proper TypeScript types that match the East function signature. + +**Serializing an East function:** + +```typescript +// Serialize to JSON +const ir = greet.toIR(); +const jsonString = JSON.stringify(ir.toJSON()); + +// ... Send jsonString over network ... +``` + +Dynamically compile and execute the function on the remote environment + +```typescript +import { EastIR } from "@elaraai/east"; + +// define the remote environment run-time implementation +const remote_platform = { + log: (msg: string) => { + console.log(`Result: ${msg}`) + } +}; +// Deserialize and compile on remote environment +const receivedIR = EastIR.fromJSON(JSON.parse("... jsonString value ... ")); +// comile with a platform implementation on the remote environment! +const remote_compiled = receivedIR.compile(platform); + +compiled("Bob"); // Logs: "Result: Hello, Bob!" +``` + + +**Operations:** + +The first argument in an east function body (`$`) is a `BlockBuilder`, which is an entry point to scope specific operations. + +| Signature | Description | Example | +|-----------|-------------|---------| +| **Variables** | +| `const(value: ValueTypeOf): Expr` | Declare immutable variable (infers type) | `const x = $.const(42n)` | +| `const(value: Expr \| ValueTypeOf, type: T): Expr` | Declare immutable variable with explicit type | `const x = $.const(y, IntegerType)` | +| `let(value: ValueTypeOf): Expr` | Declare mutable variable (infers type) | `const x = $.let(0n)` | +| `let(value: Expr \| ValueTypeOf, type: T): Expr` | Declare mutable variable with explicit type | `const x = $.let(y, IntegerType)` | +| `assign(variable: Expr, value: Expr \| ValueTypeOf): NullExpr` | Reassign mutable variable (must be declared with `$.let`) | `$.assign(x, 10n)` | +| **Execution** | +| `$(expr: Expr): Expr` | Execute expression (often for side effects), returns the expression | `$(arr.pushLast(42n))` | +| `return(value: Expr \| ValueTypeOf): NeverExpr` | Early return from function | `$.return(x)` | +| `error(message: StringExpr \| string, location?: Location): NeverExpr` | Throw error with message | `$.error("Invalid input")` | +| **Control Flow** | +| `if(condition: BooleanExpr \| boolean, body: ($) => void \| Expr): IfBuilder` | If statement (chain with `.elseIf()`, `.else()`) | `$.if(East.greater(x, 0n), $ => $.return(x))` | +| `while(condition: BooleanExpr \| boolean, body: ($, label) => void \| Expr): NullExpr` | While loop | `$.while(East.greater(x, 0n), ($, label) => $.assign(x, x.subtract(1n)))` | +| `for(array: ArrayExpr, body: ($, value, index, label) => void): NullExpr` | For loop over array elements | `$.for(arr, ($, val, i, label) => $(total.add(val)))` | +| `for(set: SetExpr, body: ($, key, label) => void): NullExpr` | For loop over set keys | `$.for(s, ($, key, label) => $(arr.pushLast(key)))` | +| `for(dict: DictExpr, body: ($, value, key, label) => void): NullExpr` | For loop over dict entries | `$.for(d, ($, val, key, label) => $(total.add(val)))` | +| `break(label: Label): NeverExpr` | Break from loop (use label from loop body) | `$.break(label)` | +| `continue(label: Label): NeverExpr` | Continue to next iteration (use label from loop body) | `$.continue(label)` | +| `match(variant: VariantExpr, cases: { [K]: ($, data) => void \| Expr }): NullExpr` | Pattern match on variant (statement form) | `$.match(opt, { Some: ($, x) => $.return(x), None: $ => $.return(0n) })` | +| `try(body: ($) => void \| Expr): TryCatchBuilder` | Try block (chain with `.catch(($, message, stack) => ...)`) | `$.try($ => arr.get(i)).catch(($, msg, stack) => $.return(0n))` | + +--- + +## Expressions + +This section describes the operations available on each type's expressions. + +### Boolean + +Boolean expressions support logical operations and conditional branching using the ternary-like `ifElse` method. + +**Example:** +```typescript +import { East, IntegerType, BooleanType } from "@elaraai/east"; + +const validateOrder = East.function([IntegerType, IntegerType, BooleanType], BooleanType, ($, quantity, price, isPremium) => { + // Create mutable boolean - starts as true, can be updated + const isValid = $.let(true); + // Alternative ways to create boolean values: + // const isValid = $.let(true, BooleanType); + // const isValid = $.let(East.value(true)); + // const isValid = $.let(East.value(true, BooleanType)); + + // Check if order is too expensive without approval + $.if(East.greater(price, 10000n), $ => { + $.assign(isValid, false); + }); + + // Check for invalid quantity or price + $.if(East.less(quantity, 1n).or($ => East.less(price, 0n)), $ => { + $.assign(isValid, false); + }); + + // Combine checks: valid AND (premium OR large order) + const finalCheck = isValid.and($ => isPremium.or($ => East.greater(quantity, 100n))); + + $.return(finalCheck); +}); + +const compiled = East.compile(validateOrder, []); +console.log(compiled(150n, 500n, true)); // true +console.log(compiled(50n, 500n, false)); // false +console.log(compiled(0n, 50n, false)); // false (invalid quantity) +``` + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| **Short-Circuiting Operations** | +| `not(): BooleanExpr` | Logical NOT | `x.not()` | +| `and(y: ($) => BooleanExpr \| boolean): BooleanExpr` | Logical AND (short-circuit) | `x.and($ => y)` | +| `or(y: ($) => BooleanExpr \| boolean): BooleanExpr` | Logical OR (short-circuit) | `x.or($ => y)` | +| `ifElse(thenFn: ($) => any, elseFn: ($) => any): ExprType>` | Conditional expression (ternary) | `condition.ifElse($ => trueValue, $ => falseValue)` | +| **Non-Short-Circuiting Operations** | +| `bitAnd(y: BooleanExpr \| boolean): BooleanExpr` | Bitwise AND (always evaluates both) | `x.bitAnd(y)` | +| `bitOr(y: BooleanExpr \| boolean): BooleanExpr` | Bitwise OR (always evaluates both) | `x.bitOr(y)` | +| `bitXor(y: BooleanExpr \| boolean): BooleanExpr` | Bitwise XOR (always evaluates both) | `x.bitXor(y)` | + + +--- + +### Integer + +Integer expressions (`IntegerExpr`) represent 64-bit signed integers with standard arithmetic, mathematical functions, and rich formatting utilities in the standard library. + +**Example:** +```typescript +import { East, IntegerType, StringType } from "@elaraai/east"; + +const formatRevenue = East.function([IntegerType], StringType, ($, revenue) => { + // Create integer value with $.let() and East.value() (type inference) + const price = $.let(East.value(47n)); + const rounded = price.add(5n).divide(10n).multiply(10n); // Round to nearest 10 + + // Alternative: Create integer value with $.let() and East.value() with explicit type + const bonus = $.let(East.value(1000n, IntegerType)); + + const quarterly = revenue.add(bonus).divide(12n).multiply(3n); + + $.return(East.str`Revenue: ${revenue}, Price: ${rounded}, Quarterly: ${quarterly}`); +}); + +const compiled = East.compile(formatRevenue, []); +console.log(compiled(1234567n)); // "Revenue: 1234567, Price: 50, Quarterly: 308641" +``` + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| `negate(): IntegerExpr` | Unary negation | `x.negate()` | +| `add(y: IntegerExpr \| bigint): IntegerExpr` | Addition | `x.add(5n)` | +| `subtract(y: IntegerExpr \| bigint): IntegerExpr` | Subtraction | `x.subtract(3n)` | +| `multiply(y: IntegerExpr \| bigint): IntegerExpr` | Multiplication | `x.multiply(2n)` | +| `divide(y: IntegerExpr \| bigint): IntegerExpr` | Integer division (floored), `0 / 0 = 0` | `x.divide(10n)` | +| `remainder(y: IntegerExpr \| bigint): IntegerExpr` | Remainder (floored modulo) | `x.remainder(3n)` | +| `pow(y: IntegerExpr \| bigint): IntegerExpr` | Exponentiation | `x.pow(2n)` | +| `abs(): IntegerExpr` | Absolute value | `x.abs()` | +| `sign(): IntegerExpr` | Sign (-1, 0, or 1) | `x.sign()` | +| `log(base: IntegerExpr \| bigint): IntegerExpr` | Logarithm (floored, custom base) | `x.log(10n)` | +| `toFloat(): FloatExpr` | Convert to float (may be approximate) | `x.toFloat()` | + +**Standard Library:** See [STDLIB.md](./STDLIB.md#integer) for additional formatting and rounding functions. + +--- + +### Float + +Float expressions (`FloatExpr`) represent IEEE 754 double-precision floating-point numbers with standard arithmetic and mathematical functions. + +**Example:** +```typescript +const calculateCircle = East.function([FloatType], FloatType, ($, radius) => { + // Create float value with $.let() and East.value() (type inference) + const pi = $.let(East.value(3.14159)); + const area = pi.multiply(radius.pow(2.0)); + + // Alternative: Create float value with $.let() and East.value() with explicit type + const scaleFactor = $.let(East.value(1.5, FloatType)); + + const scaled = area.multiply(scaleFactor); + + $.return(scaled); +}); + +const compiled = East.compile(calculateCircle, []); +console.log(compiled(10.0)); // 471.2385 +``` + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| `negate(): FloatExpr` | Unary negation | `x.negate()` | +| `add(y: FloatExpr \| number): FloatExpr` | Addition | `x.add(2.5)` | +| `subtract(y: FloatExpr \| number): FloatExpr` | Subtraction | `x.subtract(1.5)` | +| `multiply(y: FloatExpr \| number): FloatExpr` | Multiplication | `x.multiply(2.0)` | +| `divide(y: FloatExpr \| number): FloatExpr` | Division, `0.0 / 0.0 = NaN` | `x.divide(2.0)` | +| `remainder(y: FloatExpr \| number): FloatExpr` | Remainder (floored modulo) | `x.remainder(3.0)` | +| `pow(y: FloatExpr \| number): FloatExpr` | Exponentiation | `x.pow(2.0)` | +| `abs(): FloatExpr` | Absolute value | `x.abs()` | +| `sign(): FloatExpr` | Sign (-1, 0, or 1) | `x.sign()` | +| `sqrt(): FloatExpr` | Square root | `x.sqrt()` | +| `exp(): FloatExpr` | Exponential (e^x) | `x.exp()` | +| `log(): FloatExpr` | Natural logarithm | `x.log()` | +| `sin(): FloatExpr` | Sine | `x.sin()` | +| `cos(): FloatExpr` | Cosine | `x.cos()` | +| `tan(): FloatExpr` | Tangent | `x.tan()` | +| `toInteger(): IntegerExpr` **❗** | Convert to integer (must be exact, errors otherwise) | `x.toInteger()` | + +--- + +### String + +String expressions (`StringExpr`) provide text manipulation, pattern matching, encoding, and parsing capabilities. + +**Example:** +```typescript +const processEmail = East.function([StringType], StringType, ($, email) => { + const atIndex = email.indexOf("@"); + const domain = email.substring(atIndex.add(1n), email.length()); + + // Create string value with $.let() and East.value() (type inference) + const greeting = $.let(East.value("Hello")); + + // Alternative: Create string value with $.let() and East.value() with explicit type + const separator = $.let(East.value(" ", StringType)); + + const message = greeting.concat(separator).concat(domain); + + $.return(message.upperCase()); +}); + +const compiled = East.compile(processEmail, []); +console.log(compiled("user@example.com")); // "HELLO EXAMPLE.COM" +``` + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| **Manipulation** | +| `concat(other: StringExpr \| string): StringExpr` | Concatenate strings | `str.concat(" world")` | +| `repeat(count: IntegerExpr \| bigint): StringExpr` | Repeat string n times | `str.repeat(3n)` | +| `substring(from: IntegerExpr \| bigint, to: IntegerExpr \| bigint): StringExpr` | Extract substring | `str.substring(0n, 5n)` | +| `upperCase(): StringExpr` | Convert to uppercase | `str.upperCase()` | +| `lowerCase(): StringExpr` | Convert to lowercase | `str.lowerCase()` | +| `trim(): StringExpr` | Remove whitespace from both ends | `str.trim()` | +| `trimStart(): StringExpr` | Remove whitespace from start | `str.trimStart()` | +| `trimEnd(): StringExpr` | Remove whitespace from end | `str.trimEnd()` | +| `split(separator: StringExpr \| string): ArrayExpr` | Split into array | `str.split(",")` | +| `replace(search: StringExpr \| string, replacement: StringExpr \| string): StringExpr` | Replace first occurrence | `str.replace("old", "new")` | +| **Query** | +| `length(): IntegerExpr` | Get string length (UTF-16 code units) | `str.length()` | +| `startsWith(prefix: StringExpr \| string): BooleanExpr` | Test if starts with prefix | `str.startsWith("Hello")` | +| `endsWith(suffix: StringExpr \| string): BooleanExpr` | Test if ends with suffix | `str.endsWith(".txt")` | +| `contains(substring: StringExpr \| string): BooleanExpr` | Test if contains substring | `str.contains("world")` | +| `contains(regex: RegExp): BooleanExpr` | Test if matches regex | `str.contains(/[0-9]+/)` | +| `indexOf(substring: StringExpr \| string): IntegerExpr` | Find index of substring (-1 if not found) | `str.indexOf("world")` | +| `indexOf(regex: RegExp): IntegerExpr` | Find index of regex match (-1 if not found) | `str.indexOf(/[0-9]+/)` | +| **Encoding** | +| `encodeUtf8(): BlobExpr` | Encode as UTF-8 bytes | `str.encodeUtf8()` | +| `encodeUtf16(): BlobExpr` | Encode as UTF-16 bytes (little-endian with BOM) | `str.encodeUtf16()` | +| **Parsing** | +| `parse(type: T): ExprType` **❗** | Parse string to given type (fallible) | `str.parse(IntegerType)` | +| `parseJson(type: T): ExprType` **❗** | Parse JSON to given type (fallible) | `str.parseJson(IntegerType)` | + +**Standard Library:** See [STDLIB.md](./STDLIB.md#string) for error formatting utilities. + +--- + +### DateTime + +DateTime expressions (`DateTimeExpr`) represent UTC timestamps with millisecond precision, supporting component access, arithmetic, and rounding operations. + +**Example:** +```typescript +const addBusinessDays = East.function([DateTimeType, IntegerType], DateTimeType, ($, startDate, days) => { + // Create datetime value with $.let() and East.value() (type inference) + const baseDate = $.let(East.value(new Date("2025-01-01T00:00:00Z"))); + + // Alternative: Create datetime value with $.let() and East.value() with explicit type + const epoch = $.let(East.value(new Date("1970-01-01T00:00:00Z"), DateTimeType)); + + const result = startDate.addDays(days); + const rounded = East.DateTime.roundDownDay(result, 1n); + $.return(rounded); +}); + +const compiled = East.compile(addBusinessDays, []); +const start = new Date("2025-10-10T14:30:00Z"); +const end = compiled(start, 7n); +console.log(end); // 2025-10-17 00:00:00 UTC +``` + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| **Component Access** | +| `getYear(): IntegerExpr` | Get year | `date.getYear()` | +| `getMonth(): IntegerExpr` | Get month (1-12) | `date.getMonth()` | +| `getDayOfMonth(): IntegerExpr` | Get day of month (1-31) | `date.getDayOfMonth()` | +| `getDayOfWeek(): IntegerExpr` | Get day of week (0-6, Sunday=0) | `date.getDayOfWeek()` | +| `getHour(): IntegerExpr` | Get hour (0-23) | `date.getHour()` | +| `getMinute(): IntegerExpr` | Get minute (0-59) | `date.getMinute()` | +| `getSecond(): IntegerExpr` | Get second (0-59) | `date.getSecond()` | +| `getMillisecond(): IntegerExpr` | Get millisecond (0-999) | `date.getMillisecond()` | +| **Arithmetic** | +| `addMilliseconds(ms: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Add milliseconds (int or float) | `date.addMilliseconds(1000n)` | +| `subtractMilliseconds(ms: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Subtract milliseconds | `date.subtractMilliseconds(500n)` | +| `addSeconds(s: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Add seconds | `date.addSeconds(60n)` | +| `subtractSeconds(s: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Subtract seconds | `date.subtractSeconds(30n)` | +| `addMinutes(m: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Add minutes | `date.addMinutes(10n)` | +| `subtractMinutes(m: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Subtract minutes | `date.subtractMinutes(5n)` | +| `addHours(h: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Add hours | `date.addHours(2n)` | +| `subtractHours(h: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Subtract hours | `date.subtractHours(1n)` | +| `addDays(d: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Add days | `date.addDays(7n)` | +| `subtractDays(d: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Subtract days | `date.subtractDays(1n)` | +| `addWeeks(w: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Add weeks | `date.addWeeks(2n)` | +| `subtractWeeks(w: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Subtract weeks | `date.subtractWeeks(1n)` | +| **Duration** | +| `durationMilliseconds(other: DateTimeExpr \| Date): IntegerExpr` | Duration in milliseconds (positive if other > this) | `date.durationMilliseconds(otherDate)` | +| `durationSeconds(other: DateTimeExpr \| Date): FloatExpr` | Duration in seconds | `date.durationSeconds(otherDate)` | +| `durationMinutes(other: DateTimeExpr \| Date): FloatExpr` | Duration in minutes | `date.durationMinutes(otherDate)` | +| `durationHours(other: DateTimeExpr \| Date): FloatExpr` | Duration in hours | `date.durationHours(otherDate)` | +| `durationDays(other: DateTimeExpr \| Date): FloatExpr` | Duration in days | `date.durationDays(otherDate)` | +| `durationWeeks(other: DateTimeExpr \| Date): FloatExpr` | Duration in weeks | `date.durationWeeks(otherDate)` | +| **Conversion** | +| `toEpochMilliseconds(): IntegerExpr` | Milliseconds since Unix epoch | `date.toEpochMilliseconds()` | + +**Standard Library:** See [STDLIB.md](./STDLIB.md#datetime) for construction from components and additional rounding functions. + +--- + +### Blob + +Blob expressions (`BlobExpr`) represent immutable binary data with byte access and encoding/decoding operations. + +**Example:** +```typescript +const encodeData = East.function([IntegerType], BlobType, ($, value) => { + const encoded = East.Blob.encodeBeast(value, 'v2'); + + // Create blob value with $.let() and East.value() (type inference) + const header = $.let(East.value(new Uint8Array([0x42, 0x45]))); + + // Alternative: Create blob value with $.let() and East.value() with explicit type + const footer = $.let(East.value(new Uint8Array([0xFF]), BlobType)); + + $.return(encoded); +}); + +const compiled = East.compile(encodeData, []); +const blob = compiled(42n); +console.log(blob); // Uint8Array containing BEAST-encoded 42n +``` + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| **Base Operations** | +| `size(): IntegerExpr` | Get size in bytes | `blob.size()` | +| `getUint8(offset: IntegerExpr \| bigint): IntegerExpr` **❗** | Get byte at offset (0-255, errors if out of bounds) | `blob.getUint8(0n)` | +| `decodeUtf8(): StringExpr` **❗** | Decode as UTF-8 (fallible) | `blob.decodeUtf8()` | +| `decodeUtf16(): StringExpr` **❗** | Decode as UTF-16 (fallible) | `blob.decodeUtf16()` | +| `decodeBeast(type: T, version: 'v1' \| 'v2' = 'v1'): ExprType` **❗** | Decode binary BEAST format (v1 or v2, fallible) | `blob.decodeBeast(IntegerType, 'v2')` | + +**Standard Library:** See [STDLIB.md](./STDLIB.md#blob) for BEAST encoding utilities. + +--- + +### Array + +Array expressions (`ArrayExpr`) represent mutable, ordered collections with rich functional operations, mutations, and conversions. + +**Example:** +```typescript +const processPrices = East.function([ArrayType(IntegerType)], IntegerType, ($, prices) => { + const doubled = prices.map(($, x, i) => x.multiply(2n)); + const filtered = doubled.filter(($, x, i) => East.greater(x, 100n)); + const sum = filtered.sum(); + + // Create array value with $.let() and East.value() (type inference) + const bonuses = $.let(East.value([10n, 20n, 30n])); + + // Alternative: Create array value with $.let() and East.value() with explicit type + const fees = $.let(East.value([5n, 10n], ArrayType(IntegerType))); + + $(prices.pushLast(999n)); // Mutate original array + + $.return(sum); +}); + +const compiled = East.compile(processPrices, []); +console.log(compiled([50n, 60n, 70n])); // 260n (60*2 + 70*2) +``` + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| **Read Operations** | +| `size(): IntegerExpr` | Get array length | `array.size()` | +| `has(index: IntegerExpr \| bigint): BooleanExpr` | Check if index is valid (0 ≤ index < size) | `array.has(5n)` | +| `get(index: IntegerExpr \| bigint): ExprType` **❗** | Get element (errors if out of bounds) | `array.get(0n)` | +| `get(index: IntegerExpr \| bigint, defaultFn: FunctionType<[IntegerType], V>): ExprType` | Get element or compute default | `array.get(10n, East.function([IntegerType], IntegerType, ($, i) => 0n))` | +| `tryGet(index: IntegerExpr \| bigint): OptionExpr` | Safe get returning Option | `array.tryGet(0n)` | +| **Mutation Operations** | +| `update(index: IntegerExpr \| bigint, value: ExprType \| ValueTypeOf): NullExpr` **❗** | Replace element (errors if out of bounds) | `array.update(0n, 42n)` | +| `merge(index: IntegerExpr \| bigint, value: Expr, updateFn: FunctionType<[V, T2, IntegerType], V>): ExprType` | Merge value with existing element using function | `array.merge(0n, 5n, ($, old, new, i) => old.add(new))` | +| `pushLast(value: ExprType \| ValueTypeOf): NullExpr` | Append to end | `array.pushLast(42n)` | +| `popLast(): ExprType` **❗** | Remove from end (errors if empty) | `array.popLast()` | +| `pushFirst(value: ExprType \| ValueTypeOf): NullExpr` | Prepend to start | `array.pushFirst(42n)` | +| `popFirst(): ExprType` **❗** | Remove from start (errors if empty) | `array.popFirst()` | +| `append(other: ArrayExpr): NullExpr` | Append all elements from other array (mutating) | `array.append(otherArray)` | +| `prepend(other: ArrayExpr): NullExpr` | Prepend all elements from other array (mutating) | `array.prepend(otherArray)` | +| `mergeAll(other: ArrayExpr, mergeFn: FunctionType<[V, T2, IntegerType], V>): NullExpr` | Merge all elements from another array using function | `array.mergeAll(other, ($, cur, new, i) => cur.add(new))` | +| `clear(): NullExpr` | Remove all elements | `array.clear()` | +| `sortInPlace(byFn?: FunctionType<[V], DataType>): NullExpr` | Sort in-place | `array.sortInPlace()` | +| `reverseInPlace(): NullExpr` | Reverse in-place | `array.reverseInPlace()` | +| **Functional Operations (Immutable)** | +| `copy(): ArrayExpr` | Shallow copy | `array.copy()` | +| `slice(start: IntegerExpr \| bigint, end: IntegerExpr \| bigint): ArrayExpr` | Extract subarray | `array.slice(0n, 10n)` | +| `concat(other: ArrayExpr): ArrayExpr` | Concatenate into new array | `array.concat(otherArray)` | +| `getKeys(keys: ArrayExpr, onMissing?: FunctionType<[IntegerType], V>): ArrayExpr` | Get values at given indices | `array.getKeys(indices, East.function([IntegerType], IntegerType, ($, i) => 0n))` | +| `sort(byFn?: FunctionType<[V], DataType>): ArrayExpr` | Return sorted copy | `array.sort()` | +| `reverse(): ArrayExpr` | Return reversed copy | `array.reverse()` | +| `isSorted(byFn?: FunctionType<[V], DataType>): BooleanExpr` | Check if sorted | `array.isSorted()` | +| `findSortedFirst(value: T2, byFn?: FunctionType<[V], TypeOf>): IntegerExpr` | Binary search for first element ≥ value | `array.findSortedFirst(42n)` | +| `findSortedLast(value: T2, byFn?: FunctionType<[V], TypeOf>): IntegerExpr` | Binary search for last element ≤ value | `array.findSortedLast(42n)` | +| `findSortedRange(value: T2, byFn?: FunctionType<[V], TypeOf>): StructExpr<{start, end}>` | Binary search for range of elements equal to value | `array.findSortedRange(42n)` | +| `map(fn: FunctionType<[V, IntegerType], U>): ArrayExpr` | Transform each element | `array.map(($, x, i) => x.multiply(2n))` | +| `filter(predicate: FunctionType<[V, IntegerType], BooleanType>): ArrayExpr` | Keep matching elements | `array.filter(($, x, i) => East.greater(x, 0n))` | +| `filterMap(fn: FunctionType<[V, IntegerType], OptionType>): ArrayExpr` | Filter and map using Option | `array.filterMap(($, x, i) => East.greater(x, 0n) ? East.some(x.multiply(2n)) : East.none())` | +| `firstMap(fn: FunctionType<[V, IntegerType], OptionType>): OptionExpr` | Map until first successful result (returns Option) | `array.firstMap(($, x, i) => East.greater(x, 0n) ? East.some(x) : East.none())` | +| `findFirst(value: V): OptionExpr` | Find index of first matching element | `array.findFirst(42n)` | +| `findFirst(value: T2, by: FunctionType<[V, IntegerType], T2>): OptionExpr` | Find first match with projection | `array.findFirst("active", ($, u, i) => u.status)` | +| `findAll(value: V): ArrayExpr` | Find all indices of matching elements | `array.findAll(42n)` | +| `findAll(value: T2, by: FunctionType<[V, IntegerType], T2>): ArrayExpr` | Find all matches with projection | `array.findAll("active", ($, u, i) => u.status)` | +| `forEach(fn: FunctionType<[V, IntegerType], any>): NullExpr` | Execute function for each element | `array.forEach(($, x, i) => $(total.add(x)))` | +| **Reduction Operations** | +| `reduce(combineFn: FunctionType<[T, V, IntegerType], T>, init: T): ExprType` | Fold/reduce with initial value | `array.reduce(($, acc, x, i) => acc.add(x), 0n)` | +| `reduce(combineFn: FunctionType<[V, V, IntegerType], V>): ExprType` | Fold/reduce without initial (uses first element) | `array.reduce(($, acc, x, i) => acc.add(x))` | +| `mapReduce(mapFn: FunctionType<[V, IntegerType], U>, combineFn: FunctionType<[T, U, IntegerType], T>, init: T): ExprType` | Map then reduce with initial value | `array.mapReduce(($, x, i) => x.multiply(2n), ($, acc, x, i) => acc.add(x), 0n)` | +| `mapReduce(mapFn: FunctionType<[V, IntegerType], U>, combineFn: FunctionType<[U, U, IntegerType], U>): ExprType` | Map then reduce without initial | `array.mapReduce(($, x, i) => x.multiply(2n), ($, acc, x, i) => acc.add(x))` | +| `every(predicate?: FunctionType<[V, IntegerType], BooleanType>): BooleanExpr` | True if all match predicate | `array.every()` | +| `some(predicate?: FunctionType<[V, IntegerType], BooleanType>): BooleanExpr` | True if any match predicate | `array.some()` | +| `sum(): IntegerExpr \| FloatExpr` | Sum of numeric array | `array.sum()` | +| `sum(fn: FunctionType<[V, IntegerType], IntegerType \| FloatType>): IntegerExpr \| FloatExpr` | Sum with projection | `array.sum(($, x, i) => x.multiply(2n))` | +| `mean(): FloatExpr` | Mean (NaN if empty) | `array.mean()` | +| `mean(fn: FunctionType<[V, IntegerType], IntegerType \| FloatType>): FloatExpr` | Mean with projection | `array.mean(($, x, i) => x.toFloat())` | +| `findMaximum(by?: FunctionType<[V, IntegerType], any>): OptionExpr` | Find index of maximum element | `array.findMaximum()` | +| `findMinimum(by?: FunctionType<[V, IntegerType], any>): OptionExpr` | Find index of minimum element | `array.findMinimum()` | +| `maximum(by?: FunctionType<[V, IntegerType], any>): ExprType` | Get maximum element value (errors if empty) | `array.maximum()` | +| `minimum(by?: FunctionType<[V, IntegerType], any>): ExprType` | Get minimum element value (errors if empty) | `array.minimum()` | +| **Conversion Operations** | +| `stringJoin(separator: StringExpr \| string): StringExpr` | Join string array (only for `ArrayType`) | `array.stringJoin(", ")` | +| `toSet(keyFn?: FunctionType<[V, IntegerType], K>): SetExpr` | Convert to set (ignoring duplicates) | `array.toSet()` | +| `toDict(keyFn?: FunctionType<[V, IntegerType], K>, valueFn?: FunctionType<[V, IntegerType], U>, onConflictFn?: FunctionType<[U, U, K], U>): DictExpr` | Convert to dict | `array.toDict(($, x, i) => i)` | +| `flatMap(fn?: FunctionType<[V, IntegerType], ArrayType>): ArrayExpr` | Flatten array of arrays | `array.flatMap()` | +| `flattenToSet(fn?: FunctionType<[V, IntegerType], SetType>): SetExpr` | Flatten to set | `array.flattenToSet()` | +| `flattenToDict(fn?: FunctionType<[V, IntegerType], DictType>, onConflictFn?: FunctionType<[U, U, K], U>): DictExpr` | Flatten to dict | `array.flattenToDict()` | +| **Grouping Operations** | +| `groupReduce(keyFn: FunctionType<[V, IntegerType], K>, valueFn: FunctionType<[V, IntegerType], U>, initFn: FunctionType<[K], T>, reduceFn: FunctionType<[T, U, K], T>): DictExpr` | Group by key and reduce groups | `array.groupReduce(($, x, i) => x.remainder(2n), ($, x, i) => x, ($, key) => 0n, ($, acc, val, key) => acc.add(val))` | +| `groupSize(keyFn?: FunctionType<[V, IntegerType], K>): DictExpr` | Count elements in each group | `array.groupSize(($, x, i) => x.remainder(2n))` | +| `groupEvery(keyFn: FunctionType<[V, IntegerType], K>, predFn: FunctionType<[V, IntegerType], BooleanType>): DictExpr` | Check if all elements in each group match predicate | `array.groupEvery(($, x, i) => x.remainder(2n), ($, x, i) => East.greater(x, 0n))` | +| `groupSome(keyFn: FunctionType<[V, IntegerType], K>, predFn: FunctionType<[V, IntegerType], BooleanType>): DictExpr` | Check if any element in each group matches predicate | `array.groupSome(($, x, i) => x.remainder(2n), ($, x, i) => East.greater(x, 10n))` | +| `groupFindFirst(keyFn: FunctionType<[V, IntegerType], K>, value: T2, projFn?: FunctionType<[V, IntegerType], T2>): DictExpr>` | Find first matching index in each group | `array.groupFindFirst(($, x, i) => x.remainder(2n), 42n)` | +| `groupFindAll(keyFn: FunctionType<[V, IntegerType], K>, value: T2, projFn?: FunctionType<[V, IntegerType], T2>): DictExpr>` | Find all matching indices in each group | `array.groupFindAll(($, x, i) => x.remainder(2n), 42n)` | +| `groupFindMinimum(keyFn: FunctionType<[V, IntegerType], K>, byFn?: FunctionType<[V, IntegerType], any>): DictExpr` | Find index of minimum in each group | `array.groupFindMinimum(($, x, i) => x.remainder(2n))` | +| `groupFindMaximum(keyFn: FunctionType<[V, IntegerType], K>, byFn?: FunctionType<[V, IntegerType], any>): DictExpr` | Find index of maximum in each group | `array.groupFindMaximum(($, x, i) => x.remainder(2n))` | +| `groupSum(keyFn: FunctionType<[V, IntegerType], K>, valueFn?: FunctionType<[V, IntegerType], IntegerType \| FloatType>): DictExpr` | Sum values in each group | `array.groupSum(($, x, i) => x.remainder(2n))` | +| `groupMean(keyFn: FunctionType<[V, IntegerType], K>, valueFn?: FunctionType<[V, IntegerType], IntegerType \| FloatType>): DictExpr` | Mean of values in each group | `array.groupMean(($, x, i) => x.remainder(2n))` | +| `groupMinimum(keyFn: FunctionType<[V, IntegerType], K>, byFn?: FunctionType<[V, IntegerType], any>): DictExpr` | Get minimum value in each group | `array.groupMinimum(($, x, i) => x.remainder(2n))` | +| `groupMaximum(keyFn: FunctionType<[V, IntegerType], K>, byFn?: FunctionType<[V, IntegerType], any>): DictExpr` | Get maximum value in each group | `array.groupMaximum(($, x, i) => x.remainder(2n))` | +| `groupToArrays(keyFn: FunctionType<[V, IntegerType], K>, valueFn?: FunctionType<[V, IntegerType], U>): DictExpr>` | Collect elements into arrays by group | `array.groupToArrays(($, x, i) => x.remainder(2n))` | +| `groupToSets(keyFn: FunctionType<[V, IntegerType], K>, valueFn?: FunctionType<[V, IntegerType], U>): DictExpr>` | Collect elements into sets by group | `array.groupToSets(($, x, i) => x.remainder(2n))` | +| `groupToDicts(keyFn: FunctionType<[V, IntegerType], K>, keyFn2: FunctionType<[V, IntegerType], K2>, valueFn?: FunctionType<[V, IntegerType], U>, combineFn?: FunctionType<[U, U, K2], U>): DictExpr>` | Collect elements into nested dicts | `array.groupToDicts(($, x, i) => x.remainder(2n), ($, x, i) => i, ($, x, i) => x)` | + +**Standard Library:** See [STDLIB.md](./STDLIB.md#array) for array generation functions (range, linspace, generate). + +--- + +### Set + +Set expressions (`SetExpr`) represent mutable, sorted collections of unique keys with set operations and functional transformations. + +**Example:** +```typescript +const processSet = East.function([SetType(IntegerType), SetType(IntegerType)], IntegerType, ($, setA, setB) => { + const unionSet = setA.union(setB); + const intersection = setA.intersection(setB); + + // Create set value with $.let() and East.value() (type inference) + const extras = $.let(East.value(new Set([100n, 200n]))); + + // Alternative: Create set value with $.let() and East.value() with explicit type + const defaults = $.let(East.value(new Set([1n, 2n]), SetType(IntegerType))); + + $(setA.insert(999n)); // Mutate original set + + const total = unionSet.sum(); + $.return(total); +}); + +const compiled = East.compile(processSet, []); +const a = new Set([1n, 2n, 3n]); +const b = new Set([3n, 4n, 5n]); +console.log(compiled(a, b)); // 15n (1+2+3+4+5) +``` + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| **Read Operations** | +| `size(): IntegerExpr` | Get set size | `set.size()` | +| `has(key: ExprType \| ValueTypeOf): BooleanExpr` | Check if key exists | `set.has(42n)` | +| **Mutation Operations** | +| `insert(key: ExprType \| ValueTypeOf): NullExpr` **❗** | Insert key (errors if exists) | `set.insert(42n)` | +| `tryInsert(key: ExprType \| ValueTypeOf): BooleanExpr` | Safe insert (returns success) | `set.tryInsert(42n)` | +| `delete(key: ExprType \| ValueTypeOf): NullExpr` **❗** | Delete key (errors if missing) | `set.delete(42n)` | +| `tryDelete(key: ExprType \| ValueTypeOf): BooleanExpr` | Safe delete (returns success) | `set.tryDelete(42n)` | +| `clear(): NullExpr` | Remove all elements | `set.clear()` | +| `unionInPlace(other: SetExpr): NullExpr` | Union in-place (mutating) | `set.unionInPlace(otherSet)` | +| **Set Operations** | +| `copy(): SetExpr` | Shallow copy | `set.copy()` | +| `union(other: SetExpr): SetExpr` | Return union | `set.union(otherSet)` | +| `intersection(other: SetExpr): SetExpr` | Return intersection | `set.intersection(otherSet)` | +| `difference(other: SetExpr): SetExpr` | Return difference (in this, not in other) | `set.difference(otherSet)` | +| `symmetricDifference(other: SetExpr): SetExpr` | Return symmetric difference | `set.symmetricDifference(otherSet)` | +| `isSubsetOf(other: SetExpr): BooleanExpr` | Check if subset | `set.isSubsetOf(otherSet)` | +| `isSupersetOf(other: SetExpr): BooleanExpr` | Check if superset | `set.isSupersetOf(otherSet)` | +| `isDisjointFrom(other: SetExpr): BooleanExpr` | Check if no common elements | `set.isDisjointFrom(otherSet)` | +| **Functional Operations (Immutable)** | +| `filter(predicate: FunctionType<[K], BooleanType>): SetExpr` | Keep matching elements | `set.filter(($, key) => East.greater(key, 0n))` | +| `filterMap(fn: FunctionType<[K], OptionType>): ArrayExpr` | Filter and map using Option | `set.filterMap(($, key) => East.greater(key, 0n) ? East.some(key) : East.none())` | +| `firstMap(fn: FunctionType<[K], OptionType>): OptionExpr` | Map until first successful result | `set.firstMap(($, key) => East.greater(key, 10n) ? East.some(key) : East.none())` | +| `forEach(fn: FunctionType<[K], any>): NullExpr` | Execute function for each element | `set.forEach(($, key) => $(arr.pushLast(key)))` | +| `map(fn: FunctionType<[K], V>): DictExpr` | Map to dict (keys unchanged, values from fn) | `set.map(($, key) => key.multiply(2n))` | +| `reduce(fn: FunctionType<[T, K], T>, init: T): ExprType` | Fold/reduce over set | `set.reduce(($, acc, key) => acc.add(key), 0n)` | +| `every(fn?: FunctionType<[K], BooleanType>): BooleanExpr` | True if all match | `set.every()` | +| `some(fn?: FunctionType<[K], BooleanType>): BooleanExpr` | True if any match | `set.some()` | +| `sum(): IntegerExpr \| FloatExpr` | Sum of numeric set | `set.sum()` | +| `sum(fn: FunctionType<[K], IntegerType \| FloatType>): IntegerExpr \| FloatExpr` | Sum with projection | `set.sum(($, key) => key.multiply(2n))` | +| `mean(): FloatExpr` | Mean (NaN if empty) | `set.mean()` | +| `mean(fn: FunctionType<[K], IntegerType \| FloatType>): FloatExpr` | Mean with projection | `set.mean(($, key) => key.toFloat())` | +| **Conversion Operations** | +| `toArray(fn?: FunctionType<[K], V>): ArrayExpr` | Convert to array | `set.toArray()` | +| `toSet(keyFn?: FunctionType<[K], U>): SetExpr` | Convert to new set (ignoring duplicates) | `set.toSet(($, key) => key.multiply(2n))` | +| `toDict(keyFn?: FunctionType<[K], K2>, valueFn?: FunctionType<[K], V>, onConflictFn?: FunctionType<[V, V, K2], V>): DictExpr` | Convert to dict | `set.toDict()` | +| `flattenToArray(fn: FunctionType<[K], ArrayType>): ArrayExpr` | Flatten to array | `set.flattenToArray(($, key) => East.Array.range(0n, key))` | +| `flattenToSet(fn: FunctionType<[K], SetType>): SetExpr` | Flatten to set | `set.flattenToSet(($, key) => otherSetDict.get(key))` | +| `flattenToDict(fn: FunctionType<[K], DictType>, onConflictFn?: FunctionType<[V, V, K2], V>): DictExpr` | Flatten to dict | `set.flattenToDict(($, key) => nestedDicts.get(key), ($, v1, v2, k) => v1.add(v2))` | +| **Grouping Operations** | +| `groupReduce(keyFn: FunctionType<[K], K2>, valueFn: FunctionType<[K], V>, initFn: FunctionType<[K2], T>, reduceFn: FunctionType<[T, V, K2], T>): DictExpr` | Group by key and reduce groups | `set.groupReduce(($, key) => key.remainder(2n), ($, key) => key, ($, grp) => 0n, ($, acc, val, grp) => acc.add(val))` | +| `groupSize(keyFn?: FunctionType<[K], K2>): DictExpr` | Count elements in each group | `set.groupSize(($, key) => key.remainder(2n))` | +| `groupEvery(keyFn: FunctionType<[K], K2>, predFn: FunctionType<[K], BooleanType>): DictExpr` | Check if all elements in each group match predicate | `set.groupEvery(($, key) => key.remainder(2n), ($, key) => East.greater(key, 0n))` | +| `groupSome(keyFn: FunctionType<[K], K2>, predFn: FunctionType<[K], BooleanType>): DictExpr` | Check if any element in each group matches predicate | `set.groupSome(($, key) => key.remainder(2n), ($, key) => East.greater(key, 10n))` | +| `groupSum(keyFn: FunctionType<[K], K2>, valueFn?: FunctionType<[K], IntegerType \| FloatType>): DictExpr` | Sum values in each group | `set.groupSum(($, key) => key.remainder(2n))` | +| `groupMean(keyFn: FunctionType<[K], K2>, valueFn?: FunctionType<[K], IntegerType \| FloatType>): DictExpr` | Mean of values in each group | `set.groupMean(($, key) => key.remainder(2n))` | +| `groupToArrays(keyFn: FunctionType<[K], K2>, valueFn?: FunctionType<[K], V>): DictExpr>` | Collect elements into arrays by group | `set.groupToArrays(($, key) => key.remainder(2n))` | +| `groupToSets(keyFn: FunctionType<[K], K2>, valueFn?: FunctionType<[K], U>): DictExpr>` | Collect elements into sets by group | `set.groupToSets(($, key) => key.remainder(2n))` | +| `groupToDicts(keyFn: FunctionType<[K], K2>, keyFn2: FunctionType<[K], K3>, valueFn?: FunctionType<[K], V>, combineFn?: FunctionType<[V, V, K3], V>): DictExpr>` | Collect elements into nested dicts | `set.groupToDicts(($, key) => key.remainder(2n), ($, key) => key, ($, key) => key)` | + +**Standard Library:** See [STDLIB.md](./STDLIB.md#set) for set generation functions. + +--- + +### Dict + +Dict expressions (`DictExpr`) represent mutable, sorted key-value mappings with functional operations and flexible merging strategies. + + +**Example:** +```typescript +import { East, DictType, StringType, IntegerType } from "@elaraai/east"; + +const inventoryLookup = East.function([DictType(StringType, IntegerType), StringType], IntegerType, ($, inventory, item) => { + const count = inventory.get(item, East.function([StringType], IntegerType, ($, key) => 0n)); + + // Create dict value with $.let() and East.value() (type inference) + const defaults = $.let(East.value(new Map([["apple", 0n], ["banana", 0n]]))); + + // Alternative: Create dict value with $.let() and East.value() with explicit type + const prices = $.let(East.value(new Map([["apple", 5n]]), DictType(StringType, IntegerType))); + + $(inventory.merge("widget", 5n, ($, old, newVal, key) => old.add(newVal))); + + const total = inventory.sum(); + $.return(count); +}); + +const compiled = East.compile(inventoryLookup, []); +const inventory = new Map([["apple", 10n], ["banana", 5n]]); +console.log(compiled(inventory, "apple")); // 10n +``` + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| **Read Operations** | +| `size(): IntegerExpr` | Get dict size | `dict.size()` | +| `has(key: ExprType \| ValueTypeOf): BooleanExpr` | Check if key exists | `dict.has("foo")` | +| `get(key: ExprType \| ValueTypeOf): ExprType` **❗** | Get value (errors if missing) | `dict.get("foo")` | +| `get(key: ExprType \| ValueTypeOf, defaultFn: FunctionType<[K], V>): ExprType` | Get value or compute default | `dict.get("foo", East.function([StringType], IntegerType, ($, key) => 0n))` | +| `tryGet(key: ExprType \| ValueTypeOf): OptionExpr` | Safe get returning Option | `dict.tryGet("foo")` | +| `keys(): SetExpr` | Get all keys as set | `dict.keys()` | +| `getKeys(keys: SetExpr, onMissing?: FunctionType<[K], V>): DictExpr` | Get values for given keys | `dict.getKeys(keySet, East.function([StringType], IntegerType, ($, key) => 0n))` | +| **Mutation Operations** | +| `insert(key: ExprType \| ValueTypeOf, value: ExprType \| ValueTypeOf): NullExpr` **❗** | Insert (errors if exists) | `dict.insert("foo", 42n)` | +| `insertOrUpdate(key: ExprType \| ValueTypeOf, value: ExprType \| ValueTypeOf): NullExpr` | Insert or update (idempotent) | `dict.insertOrUpdate("foo", 42n)` | +| `update(key: ExprType \| ValueTypeOf, value: ExprType \| ValueTypeOf): NullExpr` **❗** | Update existing (errors if missing) | `dict.update("foo", 100n)` | +| `merge(key: ExprType \| ValueTypeOf, value: T2, updateFn: FunctionType<[V, T2, K], V>, initialFn?: FunctionType<[K], V>): NullExpr` | Merge value with existing using function | `dict.merge("count", 1n, ($, old, new, key) => old.add(new), ($, key) => 0n)` | +| `getOrInsert(key: ExprType \| ValueTypeOf, defaultFn: FunctionType<[K], V>): ExprType` | Get or insert default if missing | `dict.getOrInsert("foo", East.function([StringType], IntegerType, ($, key) => 0n))` | +| `delete(key: ExprType \| ValueTypeOf): NullExpr` **❗** | Delete (errors if missing) | `dict.delete("foo")` | +| `tryDelete(key: ExprType \| ValueTypeOf): BooleanExpr` | Safe delete (returns success) | `dict.tryDelete("foo")` | +| `pop(key: ExprType \| ValueTypeOf): ExprType` **❗** | Remove and return value (errors if missing) | `dict.pop("foo")` | +| `swap(key: ExprType \| ValueTypeOf, value: ExprType \| ValueTypeOf): ExprType` **❗** | Replace and return old value (errors if missing) | `dict.swap("foo", 100n)` | +| `clear(): NullExpr` | Remove all entries | `dict.clear()` | +| `unionInPlace(other: DictExpr, mergeFn?: FunctionType<[V, V, K], V>): NullExpr` **❗** | Union in-place (errors on conflict without mergeFn) | `dict.unionInPlace(otherDict, ($, v1, v2, key) => v2)` | +| `mergeAll(other: DictExpr, mergeFn: FunctionType<[V, V2, K], V>, initialFn?: FunctionType<[K], V>): NullExpr` | Merge all entries from another dict | `dict.mergeAll(other, ($, cur, new, key) => cur.add(new))` | +| **Functional Operations** | +| `copy(): DictExpr` | Shallow copy | `dict.copy()` | +| `map(fn: FunctionType<[V, K], U>): DictExpr` | Transform values (keys unchanged) | `dict.map(($, val, key) => val.multiply(2n))` | +| `filter(predicate: FunctionType<[V, K], BooleanType>): DictExpr` | Keep matching entries | `dict.filter(($, val, key) => East.greater(val, 0n))` | +| `filterMap(fn: FunctionType<[V, K], OptionType>): DictExpr` | Filter and map using Option | `dict.filterMap(($, val, key) => East.greater(val, 0n) ? East.some(val.multiply(2n)) : East.none())` | +| `firstMap(fn: FunctionType<[V, K], OptionType>): OptionExpr` | Map until first successful result | `dict.firstMap(($, val, key) => East.greater(val, 10n) ? East.some(val) : East.none())` | +| `forEach(fn: FunctionType<[V, K], any>): NullExpr` | Execute function for each entry | `dict.forEach(($, val, key) => $(arr.pushLast(val)))` | +| `reduce(fn: FunctionType<[T, V, K], T>, init: T): ExprType` | Fold/reduce over dict | `dict.reduce(($, acc, val, key) => acc.add(val), 0n)` | +| `every(fn?: FunctionType<[V, K], BooleanType>): BooleanExpr` | True if all match | `dict.every()` | +| `some(fn?: FunctionType<[V, K], BooleanType>): BooleanExpr` | True if any match | `dict.some()` | +| `sum(): IntegerExpr \| FloatExpr` | Sum of numeric values | `dict.sum()` | +| `sum(fn: FunctionType<[V, K], IntegerType \| FloatType>): IntegerExpr \| FloatExpr` | Sum with projection | `dict.sum(($, val, key) => val.multiply(2n))` | +| `mean(): FloatExpr` | Mean (NaN if empty) | `dict.mean()` | +| `mean(fn: FunctionType<[V, K], IntegerType \| FloatType>): FloatExpr` | Mean with projection | `dict.mean(($, val, key) => val.toFloat())` | +| **Conversion Operations** | +| `toArray(fn?: FunctionType<[V, K], U>): ArrayExpr` | Convert to array | `dict.toArray()` | +| `toSet(keyFn?: FunctionType<[V, K], U>): SetExpr` | Convert to set (ignoring duplicates) | `dict.toSet(($, val, key) => key)` | +| `toDict(keyFn?: FunctionType<[V, K], K2>, valueFn?: FunctionType<[V, K], V2>, onConflictFn?: FunctionType<[V2, V2, K2], V2>): DictExpr` | Convert to new dict | `dict.toDict(($, val, key) => key)` | +| `flattenToArray(fn?: FunctionType<[V, K], ArrayType>): ArrayExpr` | Flatten to array | `dict.flattenToArray()` | +| `flattenToSet(fn?: FunctionType<[V, K], SetType>): SetExpr` | Flatten to set | `dict.flattenToSet()` | +| `flattenToDict(fn?: FunctionType<[V, K], DictType), onConflictFn?: FunctionType<[V2, V2, K2], V2>): DictExpr` | Flatten to dict | `dict.flattenToDict()` | +| **Grouping Operations** | +| `groupReduce(keyFn: FunctionType<[V, K], K2>, valueFn: FunctionType<[V, K], U>, initFn: FunctionType<[K2], T>, reduceFn: FunctionType<[T, U, K2], T>): DictExpr` | Group by key and reduce groups | `dict.groupReduce(($, val, key) => key.remainder(2n), ($, val, key) => val, ($, grp) => 0n, ($, acc, v, grp) => acc.add(v))` | +| `groupSize(keyFn?: FunctionType<[V, K], K2>): DictExpr` | Count elements in each group | `dict.groupSize(($, val, key) => key.remainder(2n))` | +| `groupEvery(keyFn: FunctionType<[V, K], K2>, predFn: FunctionType<[V, K], BooleanType>): DictExpr` | Check if all elements in each group match predicate | `dict.groupEvery(($, val, key) => key.remainder(2n), ($, val, key) => East.greater(val, 0n))` | +| `groupSome(keyFn: FunctionType<[V, K], K2>, predFn: FunctionType<[V, K], BooleanType>): DictExpr` | Check if any element in each group matches predicate | `dict.groupSome(($, val, key) => key.remainder(2n), ($, val, key) => East.greater(val, 10n))` | +| `groupSum(keyFn: FunctionType<[V, K], K2>, valueFn?: FunctionType<[V, K], IntegerType \| FloatType>): DictExpr` | Sum values in each group | `dict.groupSum(($, val, key) => key.remainder(2n))` | +| `groupMean(keyFn: FunctionType<[V, K], K2>, valueFn?: FunctionType<[V, K], IntegerType \| FloatType>): DictExpr` | Mean of values in each group | `dict.groupMean(($, val, key) => key.remainder(2n))` | +| `groupToArrays(keyFn: FunctionType<[V, K], K2>, valueFn?: FunctionType<[V, K], U>): DictExpr>` | Collect elements into arrays by group | `dict.groupToArrays(($, val, key) => key.remainder(2n))` | +| `groupToSets(keyFn: FunctionType<[V, K], K2>, valueFn?: FunctionType<[V, K], U>): DictExpr>` | Collect elements into sets by group | `dict.groupToSets(($, val, key) => key.remainder(2n))` | +| `groupToDicts(keyFn: FunctionType<[V, K], K2>, keyFn2: FunctionType<[V, K], K3>, valueFn?: FunctionType<[V, K], U>, combineFn?: FunctionType<[U, U, K3], U>): DictExpr>` | Collect elements into nested dicts | `dict.groupToDicts(($, val, key) => key.remainder(2n), ($, val, key) => key, ($, val, key) => val)` | + +**Standard Library:** See [STDLIB.md](./STDLIB.md#dict) for dict generation functions. + +--- + +### Struct + +Struct fields are accessed directly as properties. + + +**Example:** +```typescript +const PersonType = StructType({ name: StringType, age: IntegerType }); + +const updateAge = East.function([PersonType, IntegerType], PersonType, ($, person, yearsToAdd) => { + const newAge = person.age.add(yearsToAdd); + + // Create struct value with $.let() and East.value() (type inference) + const updated = $.let(East.value({ ...person, age: newAge })); + + // Alternative: Create struct value with $.let() and East.value() with explicit type + const defaultPerson = $.let(East.value({ name: "Unknown", age: 0n }, PersonType)); + + $.return(updated); +}); + +const compiled = East.compile(updateAge, []); +const result = compiled({ name: "Alice", age: 30n }, 5n); +console.log(result); // { name: "Alice", age: 35n } +``` + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| **Read Operations** | +| `field: ExprType` | Access struct field | `person.name` | + +**Notes:** +- Structs are immutable (use spread to create modified copies) + +--- + +### Variant + +Variants represent tagged unions (sum types). + +**Example:** + +```typescript +import { East, variant, VariantType, IntegerType, NullType } from "@elaraai/east"; + +const OptionType = VariantType({ Some: IntegerType, None: NullType }); + +const processOption = East.function([OptionType], IntegerType, ($, value) => { + // Create variant value with $.let() and East.value() (type inference) + const defaultValue = $.let(East.value(variant("None", null))); + + // Alternative: Create variant value with $.let() and East.value() with explicit type + const someValue = $.let(East.value(variant("Some", 10n), OptionType)); + + // Pattern match using statement form + const result = $.let(0n); + $.match(value, { + Some: ($, x) => $.assign(result, x.add(1n)), + None: $ => $.assign(result, 0n), + }); + $.return(result); +}); + +const compiled = East.compile(processOption, []); +console.log(compiled(variant("Some", 41n))); // 42n +console.log(compiled(variant("None", null))); // 0n +``` + +**Operations:** +| Signature | Description | Example | +|-----------|-------------|---------| +| **Base Operations** | +| `match(cases: { [K]: ($, data) => Expr }): ExprType` | Pattern match on all cases | `opt.match({ Some: ($, x) => x, None: $ => 0n })` | +| `unwrap(tag?: string): ExprType` **❗** | Extract value (errors if wrong tag) | `opt.unwrap("Some")` | +| `unwrap(tag: string, defaultFn: ($) => Expr): ExprType` | Extract value or compute default | `opt.unwrap("Some", $ => 0n)` | +| `getTag(): StringExpr` | Get tag as string | `opt.getTag()` | +| `hasTag(tag: string): BooleanExpr` | Check if has specific tag | `opt.hasTag("Some")` | \ No newline at end of file diff --git a/skills/east-development/examples/01-basic-functions.md b/skills/east-development/examples/01-basic-functions.md new file mode 100644 index 0000000..3be434a --- /dev/null +++ b/skills/east-development/examples/01-basic-functions.md @@ -0,0 +1,69 @@ +# Basic East Functions - Examples + +This file contains simple examples to get started with East functions. + +## Example 1: Increment Function + +Adds 1 to an integer: + +```typescript +return East.function([East.IntegerType], East.IntegerType, ($, x) => { + return $.return(x.add(1n)); +}); +``` + +## Example 2: Add Two Integers + +Adds two integers together: + +```typescript +return East.function( + [East.IntegerType, East.IntegerType], + East.IntegerType, + ($, x, y) => { + return $.return(x.add(y)); + } +); +``` + +## Example 3: String Concatenation + +Appends "!" to a string: + +```typescript +return East.function([East.StringType], East.StringType, ($, s) => { + return $.return(s.concat("!")); +}); +``` + +## Example 4: Boolean Comparison + +Checks if an integer is greater than zero: + +```typescript +return East.function([East.IntegerType], East.BooleanType, ($, x) => { + return $.return(East.greater(x, East.value(0n))); +}); +``` + +## Example 5: Float Multiplication + +Doubles a float value: + +```typescript +return East.function([East.FloatType], East.FloatType, ($, x) => { + return $.return(x.multiply(2.0)); +}); +``` + +## Testing These Examples + +Each example can be validated using the `east_compile` tool: + +```json +{ + "typescript_code": "" +} +``` + +If compilation succeeds, you'll receive the serialized IR. If it fails, you'll get a clear error message explaining what needs to be fixed.