Skip to content

Commit c733e29

Browse files
authored
major reorg (#26)
* major reorg * rename the EvalWithPrep to just Evaluator * update docs and other references after big reorg * relocate again, replace 'abstract' package with 'platform' and move evaluator to the root of platform * add a workaround script to fix the benchmark, need to review the input_data prefix later * add new benchmark result * fix and relocate benchmarks
1 parent 4104333 commit c733e29

File tree

176 files changed

+1102
-963
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

176 files changed

+1102
-963
lines changed

.github/workflows/go-coverage.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ jobs:
2929
cache: true
3030
cache-dependency-path: |
3131
go.sum
32-
machines/extism/testdata/go.sum
32+
engines/extism/testdata/go.sum
3333
3434
- name: Display Go version
3535
run: go version
3636

3737
- name: Go test
3838
run: |
3939
go test -coverprofile=unit.coverage.out -cover ./...
40-
cd machines/extism/testdata && \
40+
cd engines/extism/testdata && \
4141
go test -coverprofile=../../../extism.coverage.out -cover .
4242
4343
- name: SonarQube Scan

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ test: go-generate
2121
## bench: Run performance benchmarks and create reports
2222
.PHONY: bench
2323
bench: go-generate
24-
benchmarks/run.sh
24+
engines/benchmarks/run.sh
2525

2626
## bench-quick: Run benchmarks without creating reports
2727
.PHONY: bench-quick
@@ -42,4 +42,4 @@ lint-fix: go-generate
4242
## go-generate: Run code generation for type wrappers
4343
.PHONY: go-generate
4444
go-generate:
45-
cd machines/types && go generate
45+
cd engines/types && go generate

README.md

Lines changed: 57 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,25 @@
55
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=robbyt_go-polyscript&metric=coverage)](https://sonarcloud.io/summary/new_code?id=robbyt_go-polyscript)
66
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
77

8-
A Go package providing a unified interface for loading and running various scripting languages and WebAssembly modules in your Go applications.
8+
A Go package providing a unified interface for loading and running various scripting languages and WASM in your app.
99

1010
## Overview
1111

12-
go-polyscript provides a consistent API across different scripting engines, allowing for easy interchangeability and minimizing lock-in to a specific scripting language. This package provides low-overhead abstractions of "machines," "executables," and the final "result". The API for the input/output and runtime are all standardized, which simplifies combining or swapping scripting engines in your application.
13-
14-
Currently supported scripting engines "machines":
15-
16-
- **Risor**: A simple scripting language specifically designed for embedding in Go applications
17-
- **Starlark**: Google's configuration language (a Python dialect) used in Bazel and many other tools
18-
- **Extism**: WebAssembly runtime and plugin system for executing WASM modules
12+
go-polyscript democratizes different scripting engines by abstracting the loading, data handling, runtime, and results handling, allowing for interchangeability of scripting languages. This package provides interfaces and implementations for "engines", "executables", "evaluators" and the final "result". There are several tiers of public APIs, each with increasing complexity and configurability. `polyscript.go` in the root exposes the most common use cases, but is also the most opiniated.
1913

2014
## Features
2115

22-
- **Unified API**: Common interfaces for all supported scripting languages
16+
- **Unified API**: Common interfaces and implementations for several scripting languages
2317
- **Flexible Engine Selection**: Easily switch between different script engines
2418
- **Thread-safe Data Management**: Multiple ways to provide input data to scripts
2519
- **Compilation and Evaluation Separation**: Compile once, run multiple times with different inputs
2620
- **Data Preparation and Evaluation Separation**: Prepare data in one step/system, evaluate in another
27-
- **Slog Logging**: Customizable structured logging with `slog`
21+
22+
## Engines Implemented
23+
24+
- **Risor**: A simple scripting language specifically designed for embedding in Go applications
25+
- **Starlark**: Google's configuration language (a Python dialect) used in Bazel and many other tools
26+
- **Extism**: Pure Go runtime and plugin system for executing WASM
2827

2928
## Installation
3029

@@ -49,140 +48,110 @@ import (
4948
)
5049

5150
func main() {
52-
// Create a logger
53-
handler := slog.NewTextHandler(os.Stdout, nil)
54-
logger := slog.New(handler)
51+
logHandler := slog.NewTextHandler(os.Stdout, nil)
5552

56-
// Script content
57-
scriptContent := `
53+
script := `
5854
// Script has access to ctx variable passed from Go
59-
name := ctx["name"]
60-
message := "Hello, " + name + "!"
55+
name := ctx.get("name", "Roberto")
56+
57+
if ctx.get("excited") {
58+
p := "!"
59+
} else {
60+
p := "."
61+
}
62+
63+
message := "Hello, " + name
64+
if excited {
65+
message = message + "!"
66+
}
6167
6268
// Return a map with our result
6369
{
6470
"greeting": message,
6571
"length": len(message)
6672
}
6773
`
68-
69-
// Input data
74+
7075
inputData := map[string]any{"name": "World"}
7176

72-
// Create evaluator from string with static data
73-
evaluator, err := polyscript.FromRisorStringWithData(
74-
scriptContent,
77+
evaluator, _ := polyscript.FromRisorStringWithData(
78+
script,
7579
inputData,
76-
handler,
80+
logHandler,
7781
)
78-
if err != nil {
79-
logger.Error("Failed to create evaluator", "error", err)
80-
return
81-
}
8282

83-
// Execute the script with a context
8483
ctx := context.Background()
85-
result, err := evaluator.Eval(ctx)
86-
if err != nil {
87-
logger.Error("Script evaluation failed", "error", err)
88-
return
89-
}
90-
91-
// Use the result
84+
result, _ := evaluator.Eval(ctx)
9285
fmt.Printf("Result: %v\n", result.Interface())
9386
}
9487
```
9588

9689
## Working with Data Providers
9790

98-
go-polyscript uses data providers to supply information to scripts during evaluation. Depending on your use case, you can choose from several built-in providers or combine them for more flexibility.
91+
go-polyscript enables you to send input data using a system called "data providers". There are several built-in providers, and you can implement your own or stack multiple with the `CompositeProvider`.
9992

10093
### StaticProvider
10194

102-
The `StaticProvider` supplies fixed data for all evaluations. This is ideal for scenarios where the input data remains constant across evaluations:
95+
The `FromRisorStringWithData` function uses a `StaticProvider` to send the static data map.
10396

10497
```go
105-
// Create static data for configuration
106-
configData := map[string]any{"name": "World", "timeout": 30}
107-
108-
// Create evaluator with static data
109-
evaluator, err := polyscript.FromRisorStringWithData(script, configData, logHandler)
110-
111-
// In scripts, static data is accessed directly:
112-
// name := ctx["name"] // "World"
98+
inputData := map[string]any{"name": "cats", "excited": true}
99+
evaluator, _ := polyscript.FromRisorStringWithData(script, inputData, logHandler)
113100
```
114101

115102
However, when using `StaticProvider`, each evaluation will always use the same input data. If you need to provide dynamic runtime data that varies per evaluation, you can use the `ContextProvider`.
116103

117104
### ContextProvider
118105

119-
The `ContextProvider` retrieves dynamic data from the context and makes it available to scripts. This is useful for scenarios where input data changes at runtime:
106+
The `ContextProvider` retrieves dynamic data from the context object sent to Eval. This is useful when input data changes at runtime:
120107

121108
```go
122-
// Create evaluator with dynamic data capability
123-
evaluator, err := polyscript.FromRisorString(script, logHandler)
109+
evaluator, _ := polyscript.FromRisorString(script, logHandler)
124110

125-
// Prepare context with runtime data for each request
126111
ctx := context.Background()
127-
userData := map[string]any{"userId": 123, "preferences": map[string]string{"theme": "dark"}}
128-
enrichedCtx, err := evaluator.PrepareContext(ctx, userData)
129-
if err != nil {
130-
// handle error
131-
}
132-
133-
// Execute with the enriched context
134-
result, err := evaluator.Eval(enrichedCtx)
112+
runtimeData := map[string]any{"name": "Billie Jean", "relationship": false}
113+
enrichedCtx, _ := evaluator.PrepareContext(ctx, runtimeData)
135114

136-
// In scripts, dynamic data is accessed via input_data:
137-
// userId := ctx["input_data"]["userId"] // 123
115+
// Execute with the "enriched" context containing the link to the input data
116+
result, _ := evaluator.Eval(enrichedCtx)
138117
```
139118

140-
### Combining Static and Dynamic Data
119+
### Combining Static and Dynamic Runtime Data
141120

142-
go-polyscript makes it easy to combine static configuration data with dynamic request data. This is a common pattern where you want both fixed configuration values and per-request variable data to be available during evaluation:
121+
This is a common pattern where you want both fixed configuration values and threadsafe per-request data to be available during evaluation:
143122

144123
```go
145-
// Create static configuration data
146124
staticData := map[string]any{
147125
"appName": "MyApp",
148126
"version": "1.0",
149127
}
150128

151-
// Create evaluator with the static data
152-
evaluator, err := polyscript.FromRisorStringWithData(script, staticData, logHandler)
153-
if err != nil {
154-
// handle error
155-
}
129+
// Create the evaluator with the static data
130+
evaluator, _ := polyscript.FromRisorStringWithData(script, staticData, logHandler)
156131

157132
// For each request, prepare dynamic data
158-
requestData := map[string]any{"userId": 123, "preferences": map[string]string{"theme": "dark"}}
159-
enrichedCtx, err := evaluator.PrepareContext(context.Background(), requestData)
160-
if err != nil {
161-
// handle error
162-
}
133+
requestData := map[string]any{"userId": 123}
134+
enrichedCtx, _ := evaluator.PrepareContext(context.Background(), requestData)
163135

164136
// Execute with both static and dynamic data available
165-
result, err := evaluator.Eval(enrichedCtx)
137+
result, _ := evaluator.Eval(enrichedCtx)
166138

167139
// In scripts, data can be accessed from both locations:
168140
// appName := ctx["appName"] // Static data: "MyApp"
169141
// userId := ctx["input_data"]["userId"] // Dynamic data: 123
170142
```
171143

172-
This pattern ensures that your scripts have access to both constant configuration values and per-evaluation runtime data, making your evaluations more flexible and powerful.
173-
174144
## Architecture
175145

176146
go-polyscript is structured around a few key concepts:
177147

178-
1. **Loader**: Loads script content from various sources (files, strings, http, etc.)
148+
1. **Loader**: Loads script content from various sources (disk, `io.Reader`, strings, http, etc.)
179149
2. **Compiler**: Validates and compiles scripts into internal "bytecode"
180-
3. **ExecutableUnit**: Represents a compiled script ready for execution
181-
4. **Evaluator**: Executes compiled scripts with provided input data
182-
5. **EvalDataPreparer**: Prepares data for evaluation (can be separated from evaluation)
183-
6. **Provider**: Supplies data to scripts during evaluation
184-
7. **Machine**: A specific implementation of a scripting engine (Risor, Starlark, Extism)
185-
8. **EvaluatorResponse**: The response object returned from all **Machine**s
150+
3. **ExecutableUnit**: Compiled script bundle, ready for execution
151+
4. **Engine**: A specific implementation of a scripting engine (Risor, Starlark, Extism)
152+
5. **Evaluator**: Executes compiled scripts with provided input data
153+
6. **DataProvider**: Sends data to the VM prior to evaluation
154+
7. **EvaluatorResponse**: The response object returned from all **Engine**s
186155

187156
### Note on Data Access Patterns
188157

@@ -194,39 +163,12 @@ go-polyscript uses a unified `Provider` interface to supply data to scripts. The
194163

195164
See the [Data Providers](#working-with-data-providers) section for more details.
196165

197-
## Preparing Data Separately from Evaluation
166+
## Other Engines
198167

199-
go-polyscript provides the `EvalDataPreparer` interface to separate data preparation from script evaluation, which is useful for distributed architectures and multi-step data processing:
168+
### Starlark
169+
Starlark syntax is a deterministic "python like" language designed for complex configuration, not so much for dynamic scripting. It's high performance, but the capabilities of the language are very limited. Read more about it here: [Starlark-Go](https://github.com/google/starlark-go)
200170

201171
```go
202-
// Create an evaluator (implements EvaluatorWithPrep interface)
203-
evaluator, err := polyscript.FromRisorString(script, logHandler)
204-
if err != nil {
205-
// handle error
206-
}
207-
208-
// Prepare context with data (could happen on a web server)
209-
requestData := map[string]any{"name": "World"}
210-
enrichedCtx, err := evaluator.PrepareContext(ctx, requestData)
211-
if err != nil {
212-
// handle error
213-
}
214-
215-
// Later, or on a different system, evaluate with the prepared context
216-
result, err := evaluator.Eval(enrichedCtx)
217-
if err != nil {
218-
// handle error
219-
}
220-
```
221-
222-
For more detailed examples of this pattern, see the [data-prep examples](examples/data-prep/).
223-
224-
## Advanced Usage
225-
226-
### Using Starlark
227-
228-
```go
229-
// Create a Starlark evaluator with static data
230172
scriptContent := `
231173
# Starlark has access to ctx variable
232174
name = ctx["name"]
@@ -250,7 +192,9 @@ evaluator, err := polyscript.FromStarlarkStringWithData(
250192
result, err := evaluator.Eval(context.Background())
251193
```
252194

253-
### Using WebAssembly with Extism
195+
### WASM with Extism
196+
197+
Extism uses the Wazero WASM runtime for providing WASI abstractions, and an easy input/output memory sharing data system. Read more about writing WASM plugins for the Extism/Wazero runtime using the Extism PDK here: [extism.org](https://extism.org/docs/concepts/pdk)
254198

255199
```go
256200
// Create an Extism evaluator with static data

0 commit comments

Comments
 (0)