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
42 changes: 42 additions & 0 deletions docs/Reference/Modules/ExpressionService/AddCustomBinder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
title: AddCustomBinder
parent: ExpressionService Module
permalink: /tB/Modules/ExpressionService/AddCustomBinder
---
# AddCustomBinder
{: .no_toc }

Registers a user-supplied binder that resolves symbols dynamically at compile time.

Syntax: *service*.**AddCustomBinder** *customBinder*

*service*
: *required* An object expression that evaluates to a **TbExpressionService** object.

*customBinder*
: *required* An object that implements [**ITbCustomBinder**](./#itbcustombinder-interface).

Use **AddCustomBinder** when [**AddCustomBinderObject**](AddCustomBinderObject) doesn't fit — for example, when the names available to the expression are not statically known, when a name should resolve to something other than a member access on a fixed object, or when the implementer needs to inspect the argument count at the call site as part of resolution.

The engine calls [**Bind**](Bind) on each registered custom binder during compilation, once per unresolved symbol. The first binder that returns a non-**Nothing** result wins. Multiple custom binders can be registered with one service, and they are consulted in registration order.

### Example

This example registers a class instance as both a property source (via [**AddCustomBinderObject**](AddCustomBinderObject) with [**IsAppObject**](./#IsAppObject)) and a custom binder. Bare symbols in the expression are first looked up as members of `Me` via the property source; whatever isn't matched there falls through to `Me.Bind`, which can resolve it dynamically — for example, against a live recordset.

```tb
' Inside a class that does: Implements ITbCustomBinder
Dim Service As TbExpressionService = New TbExpressionService
Service.AddStdLibraryBinder()
Service.AddCustomBinderObject "Report", Me, IsAppObject
Service.AddCustomBinder Me

Dim Expr As ITbExpression = Service.Compile("UCase(FieldName) & "" — "" & Title")
```

### See Also

- [Bind](Bind) method
- [Compile](Compile) method
- [AddStdLibraryBinder](AddStdLibraryBinder) method
- [AddCustomBinderObject](AddCustomBinderObject) method
46 changes: 46 additions & 0 deletions docs/Reference/Modules/ExpressionService/AddCustomBinderObject.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
title: AddCustomBinderObject
parent: ExpressionService Module
permalink: /tB/Modules/ExpressionService/AddCustomBinderObject
---
# AddCustomBinderObject
{: .no_toc }

Exposes the public members of an object so that compiled expressions can reach them.

Syntax: *service*.**AddCustomBinderObject** *name*, *object* [ **,** *flags* ]

*service*
: *required* An object expression that evaluates to a **TbExpressionService** object.

*name*
: *required* A **String** giving the qualifier under which *object*'s members are visible to expressions compiled by *service*.

*object*
: *required* The object whose public members are exposed.

*flags*
: *optional* A combination of [**ExpressionEngineBinderFlags**](./#expressionenginebinderflags) values. The default is `0`, in which case the object's members are reachable only when qualified by *name* (e.g. `Report.Title`). Pass [**IsAppObject**](./#IsAppObject) to additionally make the members reachable without qualification, the way an Office host's **Application** members are.

Member resolution is performed by name through the standard COM/IDispatch protocol — any property or method that is callable from outside the object is callable from the expression. The object must remain alive for as long as expressions might be evaluated against it.

Multiple objects can be bound to the same service, each under its own *name*. They are consulted in the order they were added.

### Example

This example exposes the host's report object so that an expression can refer to its properties either by qualified name or by bare name.

```tb
Dim Service As TbExpressionService = New TbExpressionService
Service.AddStdLibraryBinder()
Service.AddCustomBinderObject "Report", Me, IsAppObject

Debug.Print Service.Compile("Report.Title").Evaluate() ' "Sales Q4"
Debug.Print Service.Compile("Title").Evaluate() ' "Sales Q4" — IsAppObject in effect
```

### See Also

- [Compile](Compile) method
- [AddStdLibraryBinder](AddStdLibraryBinder) method
- [AddCustomBinder](AddCustomBinder) method
34 changes: 34 additions & 0 deletions docs/Reference/Modules/ExpressionService/AddStdLibraryBinder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
title: AddStdLibraryBinder
parent: ExpressionService Module
permalink: /tB/Modules/ExpressionService/AddStdLibraryBinder
---
# AddStdLibraryBinder
{: .no_toc }

Registers the standard-library binder so compiled expressions can call the common runtime functions.

Syntax: *service*.**AddStdLibraryBinder**

*service*
: *required* An object expression that evaluates to a **TbExpressionService** object.

After **AddStdLibraryBinder** has been called, expressions compiled by *service* can reference any procedure or property in the standard runtime library — math functions like [**Sqr**](../Math/Sqr), [**Sin**](../Math/Sin), and [**Round**](../Math/Round); string functions like [**Len**](../Strings/Len), [**Mid**](../Strings/Mid), and [**Format**](../Strings/Format); conversion functions like [**CStr**](../Conversion/CStr) and [**CInt**](../Conversion/CInt); and so on.

A new **TbExpressionService** has no binders registered. Without at least one binder, compiled expressions can do little more than evaluate literal arithmetic — any reference to a named symbol fails compilation with a run-time error.

### Example

```tb
Dim Service As TbExpressionService = New TbExpressionService
Service.AddStdLibraryBinder()

Debug.Print Service.Compile("Sqr(2) + Sqr(3)").Evaluate() ' 3.14...
Debug.Print Service.Compile("UCase(""hello"")").Evaluate() ' HELLO
```

### See Also

- [Compile](Compile) method
- [AddCustomBinderObject](AddCustomBinderObject) method
- [AddCustomBinder](AddCustomBinder) method
56 changes: 56 additions & 0 deletions docs/Reference/Modules/ExpressionService/Bind.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
title: Bind
parent: ExpressionService Module
permalink: /tB/Modules/ExpressionService/Bind
---
# Bind
{: .no_toc }

Resolves a symbol referenced in an expression to an [**ITbExpression**](./#itbexpression-interface) that produces its value.

Syntax: *binder*.**Bind(** *symbol*, *argCount* **)**

*binder*
: *required* An object expression that evaluates to an [**ITbCustomBinder**](./#itbcustombinder-interface) object.

*symbol*
: *required* A **String** containing the name being looked up — the identifier as it appears in the source of the expression being compiled.

*argCount*
: *required* A **Long** giving the number of arguments at the call site, or `0` if *symbol* is referenced as a bare value (a property-style access).

The return value is an [**ITbExpression**](./#itbexpression-interface) whose [**Evaluate**](Evaluate) method produces the value of *symbol* when invoked, or **Nothing** to indicate that this binder cannot resolve *symbol* and the engine should fall through to the next binder.

**Bind** is called by the engine during compilation — once per unresolved symbol encountered in the expression source — not at evaluation time. The implementer is expected either to construct an **ITbExpression** that, when later evaluated, produces the value, or to return **Nothing** so that another binder gets a chance.

The *argCount* parameter lets the implementer distinguish a property-style reference (`MyName`, where *argCount* is `0`) from a function-style call (`MyName(1, 2, 3)`, where *argCount* is `3`), and bind them to different things.

A class registers itself as a binder by including `Implements ITbCustomBinder` and then passing itself to [**AddCustomBinder**](AddCustomBinder).

### Example

This **ITbCustomBinder** implementation looks up zero-argument symbols against the current row of an external recordset, deferring to the next binder for everything else.

```tb
Implements ITbCustomBinder

Public Recordset As Object

Protected Function Bind(ByVal Symbol As String, ByVal ArgCount As Long) As ITbExpression _
Implements ITbCustomBinder.Bind

If ArgCount = 0 AndAlso Recordset IsNot Nothing Then
Dim Field As Object = Recordset.GetFieldBinder(Symbol)
If TypeOf Field Is ITbExpression Then
Return CType(Of ITbExpression)(Field)
End If
End If
' Returning Nothing lets the next binder try.
End Function
```

### See Also

- [AddCustomBinder](AddCustomBinder) method
- [Evaluate](Evaluate) method
- [Compile](Compile) method
43 changes: 43 additions & 0 deletions docs/Reference/Modules/ExpressionService/Compile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
title: Compile
parent: ExpressionService Module
permalink: /tB/Modules/ExpressionService/Compile
---
# Compile
{: .no_toc }

Parses an expression string and returns it as a compiled **ITbExpression**.

Syntax: *service*.**Compile(** *expression* **)**

*service*
: *required* An object expression that evaluates to a **TbExpressionService** object.

*expression*
: *required* A **String** containing a twinBASIC-syntax expression — for example, `"Sqr(2) + 1"` or `"UCase(FirstName) & "" "" & UCase(LastName)"`.

The return value is an [**ITbExpression**](./#itbexpression-interface). Calling [**Evaluate**](Evaluate) on it runs the expression and produces the current value; the same instance can be evaluated as many times as needed.

Symbols referenced in *expression* — function names, object members, properties — are resolved against the binders registered with *service* at the time of the call. At least one binder must be registered before **Compile** is called; the most common starting point is [**AddStdLibraryBinder**](AddStdLibraryBinder), which exposes the standard runtime library.

Compilation is the relatively expensive step; evaluation reuses the compiled form. When a piece of source text is going to drive repeated evaluation — a formula column refreshed every row, a watch expression sampled in a debugger — compile it once and keep the **ITbExpression** around.

If *expression* is malformed, or references a symbol that no registered binder can resolve, **Compile** raises a run-time error.

### Example

```tb
Dim Service As TbExpressionService = New TbExpressionService
Service.AddStdLibraryBinder()

Dim Square As ITbExpression = Service.Compile("Sqr(2)")
Debug.Print Square.Evaluate() ' 1.4142135623731
Debug.Print Square.Evaluate() ' Same compiled instance, evaluated again.
```

### See Also

- [Evaluate](Evaluate) method
- [AddStdLibraryBinder](AddStdLibraryBinder) method
- [AddCustomBinderObject](AddCustomBinderObject) method
- [AddCustomBinder](AddCustomBinder) method
40 changes: 40 additions & 0 deletions docs/Reference/Modules/ExpressionService/Evaluate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
title: Evaluate
parent: ExpressionService Module
permalink: /tB/Modules/ExpressionService/Evaluate
---
# Evaluate
{: .no_toc }

Runs a compiled expression and returns its current value.

Syntax: *expression*.**Evaluate()**

*expression*
: *required* An object expression that evaluates to an [**ITbExpression**](./#itbexpression-interface), typically the value returned by [**Compile**](Compile).

The return value is a **Variant** holding the result of the expression. Its subtype reflects the natural type of the value — for example, **Double** for a numeric expression, **String** for a text-producing one, **Boolean** for a comparison.

Each call re-runs the expression against the current state of its bindings. If the bound objects expose properties whose values can change between calls — host application state, the current row of a recordset, configurable parameters — evaluating the same compiled expression twice may legitimately return different values.

A run-time error raised inside the expression — division by zero, type mismatch, an invalid call into a bound object — propagates out of **Evaluate** like any other run-time error.

### Example

This example compiles an expression that references a property on the host object via [**AddCustomBinderObject**](AddCustomBinderObject), then evaluates it twice with the property having different values.

```tb
Dim Service As TbExpressionService = New TbExpressionService
Service.AddStdLibraryBinder()
Service.AddCustomBinderObject "State", Me, IsAppObject

Dim Expr As ITbExpression = Service.Compile("Counter * 2")

Me.Counter = 1 : Debug.Print Expr.Evaluate() ' 2
Me.Counter = 5 : Debug.Print Expr.Evaluate() ' 10
```

### See Also

- [Compile](Compile) method
- [Bind](Bind) method
84 changes: 84 additions & 0 deletions docs/Reference/Modules/ExpressionService/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
title: ExpressionService Module
parent: Modules
permalink: /tB/Modules/ExpressionService/
has_toc: false
---

# ExpressionService module

The **ExpressionService** module provides a runtime expression engine: a way to take twinBASIC-syntax expressions supplied as ordinary strings and compile and evaluate them on the fly, without going through a separate build step. It powers calculators, formula columns in reports, scriptable property bindings, and any other feature that needs to turn user-supplied text into a value.

The module exposes one class and two interfaces:

- [**TbExpressionService**](#tbexpressionservice-class) — the engine; instantiate one with **New**, register its binders, then [**Compile**](Compile) expressions against it.
- [**ITbExpression**](#itbexpression-interface) — a compiled expression handle returned by [**Compile**](Compile), evaluated with [**Evaluate**](Evaluate).
- [**ITbCustomBinder**](#itbcustombinder-interface) — implement this to provide fully custom symbol resolution.

## Compiling and evaluating an expression

Create a **TbExpressionService**, register at least one binder, then call [**Compile**](Compile) to get back an [**ITbExpression**](#itbexpression-interface). The same compiled expression can be evaluated as many times as needed, so reuse it whenever the source text doesn't change.

```tb
Sub Demo()
Dim Service As TbExpressionService = New TbExpressionService
Service.AddStdLibraryBinder() ' enable Sin, Sqr, Len, ...

Dim Expr As ITbExpression = Service.Compile("2 * (Sqr(2) + 1)")
Debug.Print Expr.Evaluate() ' 4.82842712474619
End Sub
```

## Binding to your own objects

Anything beyond the standard library — application objects, configuration values, helper functions, recordset fields — has to be made visible to the engine through a *binder*.

The simplest form is [**AddCustomBinderObject**](AddCustomBinderObject), which takes a name and an object and exposes the object's public members under that name. Pass the **IsAppObject** flag to make the object behave like an Office host's **Application**: its members become reachable both qualified (`Report.Title`) and unqualified (`Title`).

```tb
Sub UseCustomObject()
Dim Service As TbExpressionService = New TbExpressionService
Service.AddStdLibraryBinder()
Service.AddCustomBinderObject "Report", Me, IsAppObject

Debug.Print Service.Compile("Report.Title").Evaluate() ' qualified
Debug.Print Service.Compile("Title").Evaluate() ' unqualified — IsAppObject in effect
End Sub
```

For full control over symbol resolution — for example, to look up names dynamically against a recordset, virtualize a name into something other than a member access, or fall through to a custom default — implement [**ITbCustomBinder**](#itbcustombinder-interface) and register it with [**AddCustomBinder**](AddCustomBinder). Multiple binders can coexist; the engine consults them in registration order until one returns a non-**Nothing** result.

## TbExpressionService class

`New TbExpressionService` returns the default interface, **ITbExpressionService**. Multiple services can coexist; each carries its own list of binders and is independent of the others.

### Members

- [Compile](Compile) -- parses an expression string and returns it as an executable **ITbExpression**
- [AddStdLibraryBinder](AddStdLibraryBinder) -- registers the built-in binder for the standard runtime library (**Sin**, **Sqr**, **Len**, **CStr**, ...)
- [AddCustomBinderObject](AddCustomBinderObject) -- exposes a live object's members under a chosen name, optionally as an unqualified application object
- [AddCustomBinder](AddCustomBinder) -- registers a user-supplied [**ITbCustomBinder**](#itbcustombinder-interface) implementation

### ExpressionEngineBinderFlags

Flags accepted by [**AddCustomBinderObject**](AddCustomBinderObject):

| Constant | Value | Description |
|----------|-------|-------------|
| **IsAppObject**{: #IsAppObject } | 1 | Members of the bound object are reachable without the qualifying name, the way an Office host's **Application** members are. |

## ITbExpression interface

A handle to a compiled expression. Returned by [**Compile**](Compile) and by an [**ITbCustomBinder.Bind**](Bind) implementation. Calling [**Evaluate**](Evaluate) runs the expression against the current state of its bindings and returns the result; the same instance can be evaluated as many times as needed.

### Members

- [Evaluate](Evaluate) -- runs the compiled expression and returns its result

## ITbCustomBinder interface

Implement this interface to register a fully custom resolver with [**AddCustomBinder**](AddCustomBinder). The engine calls [**Bind**](Bind) during compilation for each unresolved symbol it encounters in the expression source, supplying the symbol name and the number of arguments at the call site, and expects an **ITbExpression** that produces the value when **Evaluate** is called — or **Nothing** to defer to the next binder.

### Members

- [Bind](Bind) -- resolves a symbol reference to an **ITbExpression**, or returns **Nothing** to defer to the next binder
1 change: 0 additions & 1 deletion docs/Reference/Modules/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
title: General TODO List for /tB/Modules/
nav_exclude: true
redirect_from:
- /tB/Modules/ExpressionService
- /tB/Modules/Information
- /tB/Core/Shell
- /tB/Core/SendKeys
Expand Down
Loading