Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
392 changes: 392 additions & 0 deletions src/mantle/evaluator/simple/expression/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,392 @@
# Expression Evaluator

A powerful expression evaluator for evaluating dynamic expressions with feature properties, supporting various operators, functions, and property access methods.

## Table of Contents

- [Basic Usage](#basic-usage)
- [Property Access](#property-access)
- [Operators](#operators)
- [Functions](#functions)
- [Data Types](#data-types)
- [Advanced Features](#advanced-features)

## Basic Usage

```typescript
import { Expression } from "./expression";

const feature = {
properties: {
height: 100,
name: "Building A",
},
};

const expr = new Expression("${height} > 50", feature);
const result = expr.evaluate(); // true
```

## Property Access

### 1. Standard Property Names (No Spaces)

For properties without spaces or special characters, use the simple syntax:

```typescript
${propertyName}
${height}
${temperature}
```

**Example:**
```typescript
const feature = {
properties: {
height: 100,
width: 50,
},
};

const expr = new Expression("${height} * ${width}", feature);
expr.evaluate(); // 5000
```

### 2. Quoted Property Names (With Spaces)

**✨ NEW:** For properties with spaces or special characters, wrap the property name in quotes:

```typescript
${"property name"} // Double quotes (recommended)
${'property name'} // Single quotes
```

**Example:**
```typescript
const feature = {
properties: {
"user name": "Alice",
"user score": 95,
"email@address": "alice@example.com",
"user-info:age": 25,
},
};

// Access properties with spaces
const expr1 = new Expression('${"user name"}', feature);
expr1.evaluate(); // "Alice"

// Use in conditionals
const expr2 = new Expression('${"user score"} > 90 ? "Excellent" : "Good"', feature);
expr2.evaluate(); // "Excellent"

// Arithmetic operations
const expr3 = new Expression('${"user-info:age"} + 5', feature);
expr3.evaluate(); // 30

// Properties with special characters
const expr4 = new Expression('${"email@address"} !== ""', feature);
expr4.evaluate(); // true
```

**Supported Characters in Quoted Names:**
- Spaces: `${"user name"}`
- Hyphens: `${"user-info"}`
- Colons: `${"category:name"}`
- At signs: `${"email@domain"}`
- Any other special characters

### 3. Special Variables

```typescript
${id} // Access feature.id
${rootProperties} // Access entire feature.properties object
```

**Example:**
```typescript
const feature = {
id: "feature-123",
properties: {
height: 100,
},
};

const expr = new Expression('${id}', feature);
expr.evaluate(); // "feature-123"
```

## Operators

### Comparison Operators

```typescript
${height} > 50 // Greater than
${height} >= 50 // Greater than or equal
${height} < 100 // Less than
${height} <= 100 // Less than or equal
${height} === 50 // Strict equality
${height} !== 50 // Strict inequality
${height} == 50 // Loose equality (with type coercion)
${height} != 50 // Loose inequality
```

### Arithmetic Operators

```typescript
${a} + ${b} // Addition
${a} - ${b} // Subtraction
${a} * ${b} // Multiplication
${a} / ${b} // Division
${a} % ${b} // Modulo
```

### Logical Operators

```typescript
${a} && ${b} // Logical AND
${a} || ${b} // Logical OR
!${a} // Logical NOT
```

### Conditional (Ternary) Operator

```typescript
${condition} ? ${trueValue} : ${falseValue}
```

**Example:**
```typescript
const expr = new Expression('${height} > 100 ? "Tall" : "Short"', feature);
```

## Functions

### Type Conversion Functions

```typescript
Boolean(${value}) // Convert to boolean
Number(${value}) // Convert to number
String(${value}) // Convert to string
```

### Math Functions

```typescript
abs(${value}) // Absolute value
sqrt(${value}) // Square root
ceil(${value}) // Round up
floor(${value}) // Round down
round(${value}) // Round to nearest integer
sin(${value}) // Sine
cos(${value}) // Cosine
tan(${value}) // Tangent
```

### Utility Functions

```typescript
isNaN(${value}) // Check if Not a Number
isFinite(${value}) // Check if finite number
```

### Color Functions

```typescript
color("red") // Named color
color("#ff0000") // Hex color
color("#ff0000", 0.5) // Hex color with alpha
rgb(255, 0, 0) // RGB color
rgba(255, 0, 0, 0.5) // RGBA color
hsl(0, 100, 50) // HSL color
hsla(0, 100, 50, 0.5) // HSLA color
```

**Example:**
```typescript
const expr = new Expression('${height} > 100 ? color("red") : color("blue")', feature);
```

## Data Types

### Supported Types

- **Numbers**: `42`, `3.14`, `Infinity`, `NaN`
- **Strings**: `"hello"`, `'world'`
- **Booleans**: `true`, `false`
- **Null**: `null`
- **Undefined**: `undefined`
- **Arrays**: `[1, 2, 3]`
- **Colors**: Result of color functions

### Constants

```typescript
Math.PI // 3.141592653589793
Math.E // 2.718281828459045
Number.POSITIVE_INFINITY
NaN
Infinity
undefined
```

## Advanced Features

### String Interpolation

Within string literals, you can interpolate property values:

```typescript
"Hello, ${name}!"
"Height: ${height}m"
```

**Note:** String interpolation only supports simple property names (without spaces). For properties with spaces or special characters, use concatenation instead (see below).

**Example:**
```typescript
const feature = {
properties: {
name: "Building A",
height: 100,
},
};

const expr = new Expression('"Building: ${name}, Height: ${height}m"', feature);
expr.evaluate(); // "Building: Building A, Height: 100m"
```

**For properties with spaces, use concatenation:**
```typescript
const feature = {
properties: {
"building name": "Tower A",
floors: 30,
},
};

// Use concatenation with + operator
const expr = new Expression('${"building name"} + " has " + ${floors} + " floors"', feature);
expr.evaluate(); // "Tower A has 30 floors"
```

### Array Comparisons

The equality operators support checking if a value is in an array:

```typescript
${value} == [1, 2, 3] // Check if value is in array
${value} != [1, 2, 3] // Check if value is not in array
```

**Example:**
```typescript
const feature = {
properties: {
status: "active",
},
};

const expr = new Expression('${status} == ["active", "pending"]', feature);
expr.evaluate(); // true
```

### Defines (Variable Substitution)

You can define placeholder values that get substituted before evaluation:

```typescript
const defines = {
MAX_HEIGHT: "100",
MIN_HEIGHT: "10",
};

const expr = new Expression("${height} > ${MAX_HEIGHT}", feature, defines);
// Becomes: "${height} > 100"
```

### Expression Caching

Expressions are cached automatically for better performance. To clear caches:

```typescript
import { clearExpressionCaches } from "./expression";

clearExpressionCaches(expressionString, feature, defines);
```

## Property Name Comparison

| Syntax | Use Case | Example |
|--------|----------|---------|
| `${name}` | Simple properties (no spaces) | `${height}`, `${temperature}` |
| `${"property name"}` | Properties with spaces/special chars | `${"user name"}`, `${"email@domain"}` |
| `${rootProperties}` | Access entire properties object | `${rootProperties["dynamic-key"]}` |

## Complete Example

```typescript
import { Expression } from "./expression";

const feature = {
id: "building-001",
properties: {
"building name": "Tower A",
"building height": 150,
"building color": "blue",
floors: 30,
status: "active",
"contact info": {
"email address": "info@tower-a.com",
},
},
};

// Simple comparison
const expr1 = new Expression('${"building height"} > 100', feature);
console.log(expr1.evaluate()); // true

// Conditional with color
const expr2 = new Expression(
'${"building height"} > 100 ? color("red") : color("green")',
feature
);
console.log(expr2.evaluate()); // #ff0000 (red)

// String concatenation (for properties with spaces)
const expr3 = new Expression(
'${"building name"} + " has " + ${floors} + " floors"',
feature
);
console.log(expr3.evaluate()); // "Tower A has 30 floors"

// Array membership check
const expr4 = new Expression(
'${status} == ["active", "pending"]',
feature
);
console.log(expr4.evaluate()); // true
```

## Migration Guide

### From Standard Syntax to Quoted Syntax

If you have properties with spaces, you need to update your expressions:

**Before (doesn't work):**
```typescript
${user name} // ❌ Fails: interpreted as two separate identifiers
```

**After (works):**
```typescript
${"user name"} // ✅ Works: quoted property name
```

### Best Practices

1. **Use simple syntax** for properties without spaces: `${height}`
2. **Use quoted syntax** for properties with spaces: `${"user name"}`
3. **Prefer double quotes** for consistency: `${"property"}` instead of `${'property'}`
4. **Avoid special characters in property names** when possible to keep expressions simple
Loading
Loading