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
2 changes: 1 addition & 1 deletion .cz.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
name = "cz_conventional_commits"
tag_format = "v$version"
version_scheme = "semver"
version = "0.1.0"
version = "0.2.0"
update_changelog_on_bump = true
119 changes: 119 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,102 @@ BexProgramSource source = BexProgramSource.withDefinition(
);
```

## Function Arguments And Blue Patterns

BEX function arguments may declare Blue type or shape patterns. BEX does not
have a separate type enum or type system; declared argument patterns are Blue
nodes and runtime values are checked with Blue's node/type matcher.

```yaml
functions:
capture:
args:
amount:
type: Integer
hotelOrder:
blueId: HotelOrderBlueId
request:
customerName:
type: Text
schema:
required: true
nights:
type: Integer
schema:
required: true
expr:
amount:
$var: amount
order:
$var: hotelOrder
```

All declared function arguments are required by the function call ABI for now.
Unknown functions, extra argument names, and missing declared arguments fail at
compile time. Typed arguments are checked after their call expressions are
evaluated; a runtime mismatch throws `BexException`.

Text `"400"` does not match the Blue `Integer` pattern. Use an explicit
conversion when conversion is intended:

```yaml
$call:
function: capture
args:
amount:
$integer:
$event: /message/request/amount
```

Untyped required arguments remain supported by declaring an empty pattern:

```yaml
args:
input: {}
```

When BEX converts computed values back to Blue nodes for `$is`, function
argument checks, or output conversion, Blue language keys keep their Blue
meaning. For example, this computed value is a node with a `type` field and a
`status` property, not an object with an ordinary property named `type`:

```yaml
type:
blueId: HotelOrderType
status: confirmed
```

A bare `blueId` object is a Blue reference pattern:

```yaml
blueId: HotelOrderType
```

Do not combine `blueId` with sibling fields to describe a typed instance. Use
`type: { blueId: ... }` for typed values.

BEX programs are Blue documents, so BEX syntax must use valid Blue authoring
forms. For user-defined name containers such as `functions`, `constants`,
`args`, and `$call.args`, do not use Blue language keys as names. This includes
`value`, `items`, `blueId`, `type`, `schema`, `name`, `description`,
`itemType`, `keyType`, `valueType`, `mergePolicy`, `constraints`, `contracts`,
`properties`, `$previous`, and `$pos`.

For operator bodies, payload/reference/control keys such as `value`, `items`,
`blueId`, `properties`, `$previous`, and `$pos` cannot be used as ordinary
multi-field operands. Use BEX operand names such as `node`, `list`, `input`,
`pattern`, `object`, `key`, `path`, `val`, `cond`, `then`, and `else`.

Metadata keys such as `name`, `description`, `type`, `schema`, `itemType`,
`keyType`, and `valueType` are legal Blue language fields, but they are not
ordinary object properties. An operator may use one of them only when the BEX
compiler explicitly supports that field.

Function argument patterns and `$is.pattern` are static Blue patterns. BEX does
not evaluate expressions inside those patterns, and it does not emulate Blue
authoring sugar such as inline `type: Integer` preprocessing for computed type
fields.

## Document Views

`BexDocumentView` owns document pointer resolution, canonical reads, resolved
Expand Down Expand Up @@ -188,13 +284,24 @@ BEX operators are Blue objects whose single key starts with `$`.
| Operator | Purpose |
|---|---|
| `$unwrap` | Unwrap Blue scalar wrapper values. |
| `$is` | Return whether a value matches a Blue pattern. |
| `$text` | Convert to text. |
| `$integer` | Convert to exact integer. |
| `$number` | Convert to exact decimal/number. |
| `$boolean` | Convert to boolean. |
| `$object` | Require an object, or default undefined to an empty object. |
| `$list` | Require a list, or default undefined to an empty list. |

`$is.pattern` is static Blue pattern data, not a BEX expression:

```yaml
$is:
node:
$event: /message/request/amount
pattern:
type: Integer
```

### Strings

| Operator | Purpose |
Expand All @@ -205,6 +312,14 @@ BEX operators are Blue objects whose single key starts with `$`.
| `$startsWith` | Check a prefix. |
| `$sliceAfter` | Return text after a prefix. |

```yaml
$join:
list:
- a
- b
separator: ":"
```

### Logic And Comparison

| Operator | Purpose |
Expand Down Expand Up @@ -254,6 +369,10 @@ BEX operators are Blue objects whose single key starts with `$`.
| `$call` | Call a local function. |
| `$literal` | Return payload without compiling nested operators. |

`$literal` prevents normal expression compilation, but BEX still rejects
BEX-looking operators inside Blue type-definition fields such as `type`,
`itemType`, `keyType`, `valueType`, `blue`, and `schema`.

## Statement Operators

| Statement | Purpose |
Expand Down
25 changes: 23 additions & 2 deletions src/main/java/blue/bex/api/BexEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import blue.bex.result.BexExecutionResult;
import blue.bex.result.BexMetrics;
import blue.bex.runtime.BexRuntime;
import blue.language.Blue;

/**
* Public entry point for compiling and executing selected BEX programs.
Expand All @@ -20,12 +21,14 @@
* emit events, or perform host actions.</p>
*/
public final class BexEngine {
private final Blue blue;
private final BexGasSchedule gasSchedule;
private final BexCompiledProgramCache cache;
private final BexMetricsSink metricsSink;
private final BexPointerCache pointerCache = new BexPointerCache();

private BexEngine(Builder builder) {
this.blue = builder.blue;
this.gasSchedule = builder.gasSchedule;
this.cache = builder.cache;
this.metricsSink = builder.metricsSink;
Expand All @@ -43,6 +46,8 @@ public BexCompiledProgram compile(BexProgramSource source) {
}

private BexCompiledProgram compile(BexProgramSource source, BexMetrics metrics) {
long start = System.nanoTime();
try {
BexCompiledProgramKey key = key(source);
BexCompiledProgram cached = cache.get(key);
if (cached != null) {
Expand All @@ -53,6 +58,9 @@ private BexCompiledProgram compile(BexProgramSource source, BexMetrics metrics)
BexCompiledProgram program = new BexCompiler(metrics).compile(source);
cache.put(key, program);
return program;
} finally {
metrics.addCompileNanos(System.nanoTime() - start);
}
}

public BexExecutionResult execute(BexCompiledProgram program, BexExecutionContext context) {
Expand All @@ -63,8 +71,15 @@ public BexExecutionResult execute(BexCompiledProgram program, BexExecutionContex
}

private BexExecutionResult execute(BexCompiledProgram program, BexExecutionContext context, BexMetrics metrics) {
BexRuntime runtime = new BexRuntime(program, context, gasSchedule, metrics, pointerCache);
return runtime.execute();
long start = System.nanoTime();
BexRuntime runtime = new BexRuntime(program, context, blue, gasSchedule, metrics, pointerCache);
BexExecutionResult result = runtime.execute();
metrics.addExecuteNanos(System.nanoTime() - start);
return new BexExecutionResult(result.value(),
result.changeset(),
result.events(),
result.gasUsed(),
metrics);
}

public BexExecutionResult compileAndExecute(BexProgramSource source, BexExecutionContext context) {
Expand All @@ -80,10 +95,16 @@ private BexCompiledProgramKey key(BexProgramSource source) {
}

public static final class Builder {
private Blue blue = new Blue();
private BexGasSchedule gasSchedule = BexGasSchedule.defaults();
private BexCompiledProgramCache cache = new LruBexCompiledProgramCache();
private BexMetricsSink metricsSink = BexMetricsSink.NOOP;

public Builder blue(Blue blue) {
this.blue = blue != null ? blue : new Blue();
return this;
}

public Builder gasSchedule(BexGasSchedule gasSchedule) {
this.gasSchedule = gasSchedule;
return this;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/blue/bex/api/FrozenBexDocumentView.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public String currentScopePath() {

private BexValue read(FrozenNode root, String pointer) {
List<String> segments = JsonPointer.split(pointer);
FrozenNode selected = root.at(segments);
FrozenNode selected = root.at(JsonPointer.toPointer(segments));
if (selected != null) {
return BexValues.frozen(selected);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import blue.bex.value.BexValue;
import blue.bex.value.BexValues;
import blue.language.model.Node;
import blue.language.processor.ProcessorExecutionContext;
import blue.language.snapshot.FrozenNode;
import blue.language.utils.JsonPointer;

import java.util.Objects;
Expand All @@ -25,26 +25,22 @@ public String resolvePointer(String authoredPointer) {

@Override
public BexValue canonicalAt(String absolutePointer) {
FrozenNode selected = context.canonicalFrozenAt(absolutePointer);
if (selected != null) {
return BexValues.frozen(selected);
}
FrozenNode root = context.canonicalFrozenAt("/");
return root != null ? BexValues.frozen(root).at(JsonPointer.split(absolutePointer)) : BexValues.undefined();
return documentAt(absolutePointer);
}

@Override
public BexValue resolvedAt(String absolutePointer) {
FrozenNode selected = context.resolvedFrozenAt(absolutePointer);
if (selected != null) {
return BexValues.frozen(selected);
}
FrozenNode root = context.resolvedFrozenAt("/");
return root != null ? BexValues.frozen(root).at(JsonPointer.split(absolutePointer)) : BexValues.undefined();
return documentAt(absolutePointer);
}

@Override
public String currentScopePath() {
return context.scopePath();
String pointer = context.resolvePointer("");
return pointer != null ? JsonPointer.canonicalize(pointer) : "/";
}

private BexValue documentAt(String absolutePointer) {
Node selected = context.documentAt(absolutePointer);
return selected != null ? BexValues.nodeSnapshot(selected) : BexValues.undefined();
}
}
Loading
Loading