@elaraai/east-claude-plugin 1.0.90 → 1.0.92
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.build/daemon/lsp.js +122 -13
- package/.build/daemon/server.js +122 -13
- package/.claude-plugin/plugin.json +1 -1
- package/index.json +844 -799
- package/package.json +1 -1
- package/skills/east-py/SKILL.md +58 -53
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elaraai/east-claude-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.92",
|
|
4
4
|
"description": "East programming language ecosystem - a statically typed, expression-based language for business logic, data processing, UI definitions, and durable execution",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
package/skills/east-py/SKILL.md
CHANGED
|
@@ -98,10 +98,12 @@ platform = East.platform_functions(__name__) # pass to East.compile(fn, platfo
|
|
|
98
98
|
A body's parameters are expressions already. A python scalar or `datetime` inside
|
|
99
99
|
a body lifts to a literal; `East.value(v, T)` / `b.const(v)` lift anything else
|
|
100
100
|
(TS `East.value` / `$.const`); an East COLLECTION an `East.function` body closes
|
|
101
|
-
over is snapshot into the IR
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
over is snapshot into the IR as a literal of its contents, rebuilt wherever the
|
|
102
|
+
body reads it — `t = b.const(table)` builds it once per call, and `.bind(table)`
|
|
103
|
+
as a trailing parameter keeps it live. The other way, an artifact is a plain
|
|
104
|
+
callable on values, every eager method accepts one (a VALUE takes no block),
|
|
105
|
+
and one called inside another body is a call of its inline Function node — see
|
|
106
|
+
[Python values vs East expressions](#python-values-vs-east-expressions).
|
|
105
107
|
|
|
106
108
|
## Decision Tree: What Do You Need?
|
|
107
109
|
|
|
@@ -136,7 +138,7 @@ Task → What do you need?
|
|
|
136
138
|
│ ├─ Call python from a body → the @East.platform_function you hold IS the Platform node (no declaration) · East.platform /
|
|
137
139
|
│ │ asyncPlatform / genericPlatform only for one implemented elsewhere · East.compile(fn, platform=East.platform_functions(__name__))
|
|
138
140
|
│ │ → "Declare, implement, build, compile", "Platform functions"
|
|
139
|
-
│ ├─ Make it fast → data stays in East · chain ·
|
|
141
|
+
│ ├─ Make it fast → data stays in East · chain · bind shared work with b.const · b.const a captured table, .bind big ones
|
|
140
142
|
│ │ → "Performance — the levers, ranked"
|
|
141
143
|
│ ├─ IR ↔ python → to_python_source · east-py transpile · compile_from_beast2 / json / east / value · east-c ir … → "IR ↔ python"
|
|
142
144
|
│ ├─ Refusals at edit time → east-py lint · flake8 --select EAS · east-py lsp; TYPE errors → east-py check
|
|
@@ -167,9 +169,10 @@ Task → What do you need?
|
|
|
167
169
|
│ └─ Let East call python → @East.platform_function · @East.generic_platform_function · @memoize → "Platform functions"
|
|
168
170
|
│
|
|
169
171
|
├─ C. Cross between them → a body's parameters are expressions · python scalars and datetimes lift · East.value(v, T) /
|
|
170
|
-
│ b.const(v) · a captured East collection is a build-time SNAPSHOT
|
|
171
|
-
│ live · an East function is a callable on
|
|
172
|
-
│
|
|
172
|
+
│ b.const(v) · a captured East collection is a build-time SNAPSHOT, a literal rebuilt at each read (an eager callback
|
|
173
|
+
│ refuses it) — b.const(table) builds it once per call, .bind(table) keeps it live · an East function is a callable on
|
|
174
|
+
│ values, and a VALUE takes no block · east.runtime.compiler.eager_stats() shows how a hot call ran
|
|
175
|
+
│ → "Python values vs East expressions", "Performance"
|
|
173
176
|
│
|
|
174
177
|
└─ D. Run a program from the shell → east-py run (IR + input files → a result) · exec (a unit: the runner protocol e3
|
|
175
178
|
speaks) · convert (a beast2 value → East text) · version · where its time goes: --profile / EAST_PROFILE=1
|
|
@@ -309,12 +312,12 @@ artifact used as an `East.if_else` arm evaluates only when that arm is taken,
|
|
|
309
312
|
an error inside it names its own lines, and a profile lists it as an entry of
|
|
310
313
|
its own, placed where it was written and where it was called. (One called with
|
|
311
314
|
arguments narrower than its parameters — a column projection's narrowed rows —
|
|
312
|
-
re-runs its source in its own frame and splices in instead
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
315
|
+
re-runs its source in its own frame and splices in instead.) An artifact that
|
|
316
|
+
mutates or raises, called and DISCARDED, is the build's "evaluated and thrown
|
|
317
|
+
away" error — spell it `b.do(push(xs))` / `East.block(push(xs), result)`, as
|
|
318
|
+
with any mutation. An artifact that calls platform functions stays first-class
|
|
319
|
+
(composable, serializable) but raises until `East.compile` pairs it with
|
|
320
|
+
implementations.
|
|
318
321
|
`East.function(...)` spelled INSIDE a body is the inline `Function` node, a
|
|
319
322
|
Function-typed expression: bind it with `b.const`, hand it to a callback slot,
|
|
320
323
|
or call it (a `Call`; in an `East.asyncFunction`, calling an async one is a
|
|
@@ -512,7 +515,7 @@ East values. A worked example is in
|
|
|
512
515
|
| `East.while_(state, cond, body, label=…)` | `While` | `cond(b, s) -> Boolean`, `body(b, s) -> next state` |
|
|
513
516
|
| `East.for_(coll, state, body, label=…)` | `ForArray`/`ForSet`/`ForDict` | Array `body(b, s, el[, i])`, Set `body(b, s, el)`, Dict `body(b, s, k, v)` |
|
|
514
517
|
| `East.block(a, b, …)` | `Block` | evaluates in order, yields the last — the sequencing point for mutators (`East.block(lambda b: …)` is the statement form) |
|
|
515
|
-
| `East.let(value, fn)` | `Let` | `fn(b, bound)` — bind once, use many times (
|
|
518
|
+
| `East.let(value, fn)` | `Let` | `fn(b, bound)` — bind once, use many times, inside an expression body (`b.const` is the statement form) |
|
|
516
519
|
| `East.ref(v)` | `NewRef` | a cell — `.get()` / `.update(v)` / `.merge(v, fn(b, current, patch))` |
|
|
517
520
|
| `East.label(name=None)` | — | names a loop, for `break_`/`continue_` from a NESTED one |
|
|
518
521
|
| `East.break_(state=…, label=…)` / `East.continue_(state=…, label=…)` | `Break` / `Continue` | leave / next iteration, optionally committing a last state |
|
|
@@ -535,10 +538,10 @@ counts = East.for_(rows, {"counts": East.new_dict(StringType, IntegerType)},
|
|
|
535
538
|
```
|
|
536
539
|
|
|
537
540
|
⚠️ Seed accumulators with `East.new_array/new_set/new_dict`. A captured
|
|
538
|
-
`EastArray(T)` works too — a
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
541
|
+
`EastArray(T)` works too — a captured collection is a snapshot of its
|
|
542
|
+
contents, rebuilt wherever the body reads it, so a seed is fresh per call —
|
|
543
|
+
but mutating one anywhere ELSE changes a copy nothing else sees, and the
|
|
544
|
+
build raises rather than compile a mutation that persists nowhere.
|
|
542
545
|
|
|
543
546
|
### Queries — `East.jq`
|
|
544
547
|
|
|
@@ -621,34 +624,39 @@ element**.
|
|
|
621
624
|
crossing per element — convert at most once, at the very end.
|
|
622
625
|
2. **Let the callbacks build, and chain on the results** — a chain of
|
|
623
626
|
`map`/`filter`/`reduce` stays native between steps. Return a dict literal
|
|
624
|
-
(`lambda b, r: {"a": …, "b": …}`) to derive every column in ONE pass. **
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
627
|
+
(`lambda b, r: {"a": …, "b": …}`) to derive every column in ONE pass. **Bind
|
|
628
|
+
shared work with `b.const`** — the builder emits exactly what the body
|
|
629
|
+
spells, as TypeScript's does, so a python variable holding an expression is
|
|
630
|
+
re-emitted (and re-evaluated) at every read: `fields = r.data.split("|")`
|
|
631
|
+
read for 30 columns runs the split 30 times per row, and `table =
|
|
632
|
+
derive(rec)` read inside a `.map` runs `derive` once per element. `fields =
|
|
633
|
+
b.const(r.data.split("|"))` and `table = b.const(derive(rec))` run each
|
|
634
|
+
once, as `$.const` does — the `no-reinlined-east-binding` rule (`east-py
|
|
635
|
+
lint`, the LSP) points at every such read. There is no optimisation pass
|
|
636
|
+
(#1312 removed the trace-time CSE: it moved reads across mutations it could
|
|
637
|
+
not see), so what you write is what runs — a callback that pushes onto
|
|
638
|
+
`order` and returns `order.size() - 1` reads the size after its push.
|
|
639
|
+
3. **Side tables: `b.const` a captured one, `bind` big ones** — opposite contracts:
|
|
635
640
|
- **Capture (snapshot).** A collection captured by an `East.function` body
|
|
636
|
-
is snapshot into its IR
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
641
|
+
is snapshot into its IR as a literal of its contents — exactly as a
|
|
642
|
+
TypeScript literal is — and rebuilt wherever the body reads it: per call
|
|
643
|
+
at the function's top level, per element inside a callback. **Bind it
|
|
644
|
+
once per call with `t = b.const(TABLE)`** and read `t`. Right for small
|
|
645
|
+
tables (up to a few thousand entries): the literal's size rides the IR,
|
|
646
|
+
every call rebuilds it, and later mutations are **not** seen. Only an
|
|
640
647
|
EXPLICIT build snapshots: an eager callback refuses a mutable capture.
|
|
641
648
|
- **`East.function(...).bind(table)` (by reference, live).** Declare the
|
|
642
|
-
table as a trailing parameter and pre-bind it: zero copy, O(1) at any
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
`TypeError`.
|
|
649
|
+
table as a trailing parameter and pre-bind it: zero copy, O(1) at any
|
|
650
|
+
size, nothing rebuilt per call, and the function **sees later
|
|
651
|
+
mutations**. Rebinding gives independent callables, the unbound function
|
|
652
|
+
stays usable, and a wrong-typed value raises `TypeError`.
|
|
647
653
|
|
|
648
654
|
```python
|
|
649
655
|
fx = EastDict(StringType, FloatType, rates) # small table → capture
|
|
650
|
-
|
|
651
|
-
|
|
656
|
+
@East.function([Row], FloatType) # (the EXPLICIT build
|
|
657
|
+
def to_usd(b, r): # opts into the snapshot)
|
|
658
|
+
t = b.const(fx) # the literal, once per call
|
|
659
|
+
return r.amount * t.get_or_default(r.ccy, 1.0)
|
|
652
660
|
|
|
653
661
|
TableT = DictType(StringType, FloatType) # huge table → bind
|
|
654
662
|
conv = East.function([Row, TableT], FloatType,
|
|
@@ -680,7 +688,7 @@ REBUILDS it through the surface above:
|
|
|
680
688
|
|
|
681
689
|
```python
|
|
682
690
|
from east.codegen import to_python_source
|
|
683
|
-
print(to_python_source(classify)) # `@East.function(
|
|
691
|
+
print(to_python_source(classify)) # `@East.function(...)` / `def main(b, …)`
|
|
684
692
|
```
|
|
685
693
|
|
|
686
694
|
```bash
|
|
@@ -789,6 +797,7 @@ The rules — `east-py lint --list-rules` prints them:
|
|
|
789
797
|
| `no-python-string-building` (EAS023, warning) | an f-string assembling an East string CONSTANT — a regex, a template, a key | spell the constant out |
|
|
790
798
|
| `no-derived-struct-fields` (EAS024, warning) | `Derived = StructType([… for f in Other.value])` | a type declaration is a wire format — spell the fields |
|
|
791
799
|
| `no-python-data-work` (EAS025, warning) | a python helper doing parse / strip / null-check / coerce work on an EXPRESSION a body hands it | express it in East, where it runs on every row |
|
|
800
|
+
| `no-reinlined-east-binding` (EAS026, warning) | a python local holding an EXPRESSION (`n = xs.size()`, not a `b.let`/`b.const` binding) read more than once in one body, or read inside a per-element callback, a loop body or a nested function below the body that bound it | each read re-emits — and re-evaluates — the expression: bind it once, `n = b.const(xs.size())`, where it is needed (the TypeScript rule of the same name) |
|
|
792
801
|
|
|
793
802
|
Configure it once in the project's `pyproject.toml`; every surface reads it, and
|
|
794
803
|
`east-py lint`'s own flags add to it:
|
|
@@ -811,10 +820,7 @@ A file that does not import `east` is never diagnosed; a line ending in `# noqa`
|
|
|
811
820
|
`node_modules`, `dist`, `build`, `.git`, `__pycache__`, `tests` and `test` are not
|
|
812
821
|
walked. The rules are syntactic, so a clean `lint` is necessary, not sufficient —
|
|
813
822
|
the type errors live behind `east-py check`. The rules are disjoint (each
|
|
814
|
-
mistake is one rule's to report).
|
|
815
|
-
purpose: `no-reinlined-east-binding` warns that a JS `const` holding an `Expr`
|
|
816
|
-
re-inlines it at each use, but the python build's CSE binds a reused python local
|
|
817
|
-
to ONE `Let`, so the hazard does not exist here.
|
|
823
|
+
mistake is one rule's to report).
|
|
818
824
|
|
|
819
825
|
### Cross-language functions: `east-py export-functions` and `East.import_function`
|
|
820
826
|
|
|
@@ -833,12 +839,11 @@ from east.types.types import FloatType, StructType, IntegerType
|
|
|
833
839
|
Row = StructType([("qty", IntegerType), ("price", FloatType)])
|
|
834
840
|
score = East.function([Row], FloatType, lambda b, r: r.qty.to_float() * r.price)
|
|
835
841
|
east_functions = {"score": score} # name -> East.function artifact. CLOSED values: the
|
|
836
|
-
# exported IR must BIND every name it reads. A
|
|
837
|
-
#
|
|
838
|
-
#
|
|
839
|
-
#
|
|
840
|
-
#
|
|
841
|
-
# variable is refused BY NAME
|
|
842
|
+
# exported IR must BIND every name it reads. A captured
|
|
843
|
+
# lookup table or a stdlib format string is a literal in
|
|
844
|
+
# the body and closes over nothing; a .bind result (no IR
|
|
845
|
+
# of its own) and an unresolved import are refused, and a
|
|
846
|
+
# genuinely free variable is refused BY NAME
|
|
842
847
|
```
|
|
843
848
|
|
|
844
849
|
```typescript
|
|
@@ -1635,7 +1640,7 @@ CUTOFF = 100
|
|
|
1635
1640
|
@East.function([Row], FloatType)
|
|
1636
1641
|
def usd(b, r):
|
|
1637
1642
|
return East.if_else(r.amount > CUTOFF, # a scalar constant lifts (TS East.value)
|
|
1638
|
-
r.amount * RATES.get_or_default(r.ccy, 1.0), # an EXPLICIT build snapshots the dict
|
|
1643
|
+
r.amount * RATES.get_or_default(r.ccy, 1.0), # an EXPLICIT build snapshots the dict: a literal, rebuilt per call
|
|
1639
1644
|
0.0)
|
|
1640
1645
|
|
|
1641
1646
|
rows.map(lambda b, r: r.amount * RATES.get_or_default(r.ccy, 1.0)) # ❌ an eager callback REFUSES
|