Skip to content

Commit cd74f62

Browse files
committed
Add Blue integration for Bex with type matching, node conversion, and tests
1 parent 490b8b7 commit cd74f62

16 files changed

Lines changed: 1665 additions & 107 deletions

.cz.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
name = "cz_conventional_commits"
33
tag_format = "v$version"
44
version_scheme = "semver"
5-
version = "0.1.0"
5+
version = "0.2.0"
66
update_changelog_on_bump = true

README.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,102 @@ BexProgramSource source = BexProgramSource.withDefinition(
103103
);
104104
```
105105

106+
## Function Arguments And Blue Patterns
107+
108+
BEX function arguments may declare Blue type or shape patterns. BEX does not
109+
have a separate type enum or type system; declared argument patterns are Blue
110+
nodes and runtime values are checked with Blue's node/type matcher.
111+
112+
```yaml
113+
functions:
114+
capture:
115+
args:
116+
amount:
117+
type: Integer
118+
hotelOrder:
119+
blueId: HotelOrderBlueId
120+
request:
121+
customerName:
122+
type: Text
123+
schema:
124+
required: true
125+
nights:
126+
type: Integer
127+
schema:
128+
required: true
129+
expr:
130+
amount:
131+
$var: amount
132+
order:
133+
$var: hotelOrder
134+
```
135+
136+
All declared function arguments are required by the function call ABI for now.
137+
Unknown functions, extra argument names, and missing declared arguments fail at
138+
compile time. Typed arguments are checked after their call expressions are
139+
evaluated; a runtime mismatch throws `BexException`.
140+
141+
Text `"400"` does not match the Blue `Integer` pattern. Use an explicit
142+
conversion when conversion is intended:
143+
144+
```yaml
145+
$call:
146+
function: capture
147+
args:
148+
amount:
149+
$integer:
150+
$event: /message/request/amount
151+
```
152+
153+
Untyped required arguments remain supported by declaring an empty pattern:
154+
155+
```yaml
156+
args:
157+
input: {}
158+
```
159+
160+
When BEX converts computed values back to Blue nodes for `$is`, function
161+
argument checks, or output conversion, Blue language keys keep their Blue
162+
meaning. For example, this computed value is a node with a `type` field and a
163+
`status` property, not an object with an ordinary property named `type`:
164+
165+
```yaml
166+
type:
167+
blueId: HotelOrderType
168+
status: confirmed
169+
```
170+
171+
A bare `blueId` object is a Blue reference pattern:
172+
173+
```yaml
174+
blueId: HotelOrderType
175+
```
176+
177+
Do not combine `blueId` with sibling fields to describe a typed instance. Use
178+
`type: { blueId: ... }` for typed values.
179+
180+
BEX programs are Blue documents, so BEX syntax must use valid Blue authoring
181+
forms. For user-defined name containers such as `functions`, `constants`,
182+
`args`, and `$call.args`, do not use Blue language keys as names. This includes
183+
`value`, `items`, `blueId`, `type`, `schema`, `name`, `description`,
184+
`itemType`, `keyType`, `valueType`, `mergePolicy`, `constraints`, `contracts`,
185+
`properties`, `$previous`, and `$pos`.
186+
187+
For operator bodies, payload/reference/control keys such as `value`, `items`,
188+
`blueId`, `properties`, `$previous`, and `$pos` cannot be used as ordinary
189+
multi-field operands. Use BEX operand names such as `node`, `list`, `input`,
190+
`pattern`, `object`, `key`, `path`, `val`, `cond`, `then`, and `else`.
191+
192+
Metadata keys such as `name`, `description`, `type`, `schema`, `itemType`,
193+
`keyType`, and `valueType` are legal Blue language fields, but they are not
194+
ordinary object properties. An operator may use one of them only when the BEX
195+
compiler explicitly supports that field.
196+
197+
Function argument patterns and `$is.pattern` are static Blue patterns. BEX does
198+
not evaluate expressions inside those patterns, and it does not emulate Blue
199+
authoring sugar such as inline `type: Integer` preprocessing for computed type
200+
fields.
201+
106202
## Document Views
107203

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

295+
`$is.pattern` is static Blue pattern data, not a BEX expression:
296+
297+
```yaml
298+
$is:
299+
node:
300+
$event: /message/request/amount
301+
pattern:
302+
type: Integer
303+
```
304+
198305
### Strings
199306

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

315+
```yaml
316+
$join:
317+
list:
318+
- a
319+
- b
320+
separator: ":"
321+
```
322+
208323
### Logic And Comparison
209324

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

372+
`$literal` prevents normal expression compilation, but BEX still rejects
373+
BEX-looking operators inside Blue type-definition fields such as `type`,
374+
`itemType`, `keyType`, `valueType`, `blue`, and `schema`.
375+
257376
## Statement Operators
258377

259378
| Statement | Purpose |

src/main/java/blue/bex/api/BexEngine.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import blue.bex.result.BexExecutionResult;
1111
import blue.bex.result.BexMetrics;
1212
import blue.bex.runtime.BexRuntime;
13+
import blue.language.Blue;
1314

1415
/**
1516
* Public entry point for compiling and executing selected BEX programs.
@@ -20,12 +21,14 @@
2021
* emit events, or perform host actions.</p>
2122
*/
2223
public final class BexEngine {
24+
private final Blue blue;
2325
private final BexGasSchedule gasSchedule;
2426
private final BexCompiledProgramCache cache;
2527
private final BexMetricsSink metricsSink;
2628
private final BexPointerCache pointerCache = new BexPointerCache();
2729

2830
private BexEngine(Builder builder) {
31+
this.blue = builder.blue;
2932
this.gasSchedule = builder.gasSchedule;
3033
this.cache = builder.cache;
3134
this.metricsSink = builder.metricsSink;
@@ -69,7 +72,7 @@ public BexExecutionResult execute(BexCompiledProgram program, BexExecutionContex
6972

7073
private BexExecutionResult execute(BexCompiledProgram program, BexExecutionContext context, BexMetrics metrics) {
7174
long start = System.nanoTime();
72-
BexRuntime runtime = new BexRuntime(program, context, gasSchedule, metrics, pointerCache);
75+
BexRuntime runtime = new BexRuntime(program, context, blue, gasSchedule, metrics, pointerCache);
7376
BexExecutionResult result = runtime.execute();
7477
metrics.addExecuteNanos(System.nanoTime() - start);
7578
return new BexExecutionResult(result.value(),
@@ -92,10 +95,16 @@ private BexCompiledProgramKey key(BexProgramSource source) {
9295
}
9396

9497
public static final class Builder {
98+
private Blue blue = new Blue();
9599
private BexGasSchedule gasSchedule = BexGasSchedule.defaults();
96100
private BexCompiledProgramCache cache = new LruBexCompiledProgramCache();
97101
private BexMetricsSink metricsSink = BexMetricsSink.NOOP;
98102

103+
public Builder blue(Blue blue) {
104+
this.blue = blue != null ? blue : new Blue();
105+
return this;
106+
}
107+
99108
public Builder gasSchedule(BexGasSchedule gasSchedule) {
100109
this.gasSchedule = gasSchedule;
101110
return this;

src/main/java/blue/bex/compile/BexCompiledProgram.java

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package blue.bex.compile;
22

3+
import blue.bex.BexException;
34
import blue.bex.runtime.BexRuntime;
45
import blue.bex.runtime.CompiledFrame;
56
import blue.bex.runtime.CompiledStatement;
67
import blue.bex.runtime.Control;
78
import blue.bex.runtime.CompiledExpression;
89
import blue.bex.value.BexValue;
910
import blue.bex.value.BexValues;
11+
import blue.language.snapshot.FrozenNode;
1012

13+
import java.util.Collection;
1114
import java.util.Collections;
1215
import java.util.LinkedHashMap;
1316
import java.util.List;
@@ -55,35 +58,42 @@ public BexValue constant(String name) {
5558
*/
5659
public static final class CompiledFunction {
5760
private final String name;
58-
private final List<String> args;
59-
private final Map<String, Integer> argSlotByName;
61+
private final List<ArgSpec> args;
62+
private final Map<String, ArgSpec> argByName;
63+
private final ArgSpec[] argBySlot;
6064
private final List<CompiledStatement> statements;
6165
private final CompiledExpression expression;
6266
private final int frameSize;
6367

6468
public CompiledFunction(String name,
65-
List<String> args,
69+
List<ArgSpec> args,
6670
List<CompiledStatement> statements,
6771
CompiledExpression expression,
6872
int frameSize) {
6973
this.name = name;
70-
this.args = args;
71-
LinkedHashMap<String, Integer> argSlots = new LinkedHashMap<>();
72-
for (int i = 0; i < args.size(); i++) {
73-
argSlots.put(args.get(i), i);
74+
this.args = Collections.unmodifiableList(args);
75+
LinkedHashMap<String, ArgSpec> argSpecs = new LinkedHashMap<>();
76+
this.argBySlot = new ArgSpec[frameSize];
77+
for (ArgSpec arg : args) {
78+
argSpecs.put(arg.name(), arg);
79+
if (arg.slot() >= 0 && arg.slot() < argBySlot.length) {
80+
argBySlot[arg.slot()] = arg;
81+
}
7482
}
75-
this.argSlotByName = Collections.unmodifiableMap(argSlots);
83+
this.argByName = Collections.unmodifiableMap(argSpecs);
7684
this.statements = statements;
7785
this.expression = expression;
7886
this.frameSize = frameSize;
7987
}
8088

8189
public String name() { return name; }
82-
public List<String> args() { return args; }
90+
public Collection<ArgSpec> args() { return args; }
8391
public int frameSize() { return frameSize; }
92+
public boolean hasArg(String name) { return argByName.containsKey(name); }
93+
public ArgSpec arg(String name) { return argByName.get(name); }
8494
public int argSlot(String name) {
85-
Integer slot = argSlotByName.get(name);
86-
return slot != null ? slot : -1;
95+
ArgSpec arg = argByName.get(name);
96+
return arg != null ? arg.slot() : -1;
8797
}
8898

8999
public BexValue invokeRoot(BexRuntime runtime) {
@@ -97,6 +107,14 @@ public BexValue invokePrepared(BexRuntime runtime, CompiledFrame parent, int[] s
97107
for (int i = 0; i < slots.length; i++) {
98108
if (slots[i] >= 0) {
99109
BexValue value = values[i];
110+
ArgSpec arg = slots[i] < argBySlot.length ? argBySlot[slots[i]] : null;
111+
if (arg != null && arg.typed()
112+
&& !runtime.typeMatcher().matches(value, arg.pattern())) {
113+
throw new BexException("Function " + name
114+
+ " argument " + arg.name()
115+
+ " does not match declared Blue pattern at "
116+
+ arg.sourcePointer());
117+
}
100118
frame.set(slots[i], value != null ? value : BexValues.undefined());
101119
}
102120
}
@@ -111,4 +129,26 @@ public BexValue invokePrepared(BexRuntime runtime, CompiledFrame parent, int[] s
111129
return runtime.defaultResultValue();
112130
}
113131
}
132+
133+
public static final class ArgSpec {
134+
private final String name;
135+
private final int slot;
136+
private final FrozenNode pattern;
137+
private final boolean typed;
138+
private final String sourcePointer;
139+
140+
public ArgSpec(String name, int slot, FrozenNode pattern, String sourcePointer) {
141+
this.name = name;
142+
this.slot = slot;
143+
this.pattern = pattern;
144+
this.typed = pattern != null && !pattern.isEmptyNode();
145+
this.sourcePointer = sourcePointer;
146+
}
147+
148+
public String name() { return name; }
149+
public int slot() { return slot; }
150+
public FrozenNode pattern() { return pattern; }
151+
public boolean typed() { return typed; }
152+
public String sourcePointer() { return sourcePointer; }
153+
}
114154
}

0 commit comments

Comments
 (0)