Skip to content

Commit d9cc954

Browse files
committed
updates to comments, docs, example code
1 parent 8ea67c4 commit d9cc954

File tree

6 files changed

+112
-95
lines changed

6 files changed

+112
-95
lines changed

Diff for: README.md

+67-61
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ A Go package providing a unified interface for loading and running various scrip
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 is achieved through a low-overhead abstraction of "machines," "executables," and the final "result". The input/output and runtime features provided by the scripting engines are standardized, which simplifies combining or swapping scripting engines in your application.
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.
1313

14-
Currently supported scripting engines ("machines"):
14+
Currently supported scripting engines "machines":
1515

1616
- **Risor**: A simple scripting language specifically designed for embedding in Go applications
1717
- **Starlark**: Google's configuration language (a Python dialect) used in Bazel and many other tools
@@ -21,11 +21,10 @@ Currently supported scripting engines ("machines"):
2121

2222
- **Unified API**: Common interfaces for all supported scripting languages
2323
- **Flexible Engine Selection**: Easily switch between different script engines
24-
- **Powerful Data Passing**: Multiple ways to provide input data to scripts
25-
- **Comprehensive Logging**: Structured logging with `slog` support
26-
- **Error Handling**: Robust error handling and reporting from script execution
24+
- **Thread-safe Data Management**: Multiple ways to provide input data to scripts
2725
- **Compilation and Evaluation Separation**: Compile once, run multiple times with different inputs
2826
- **Data Preparation and Evaluation Separation**: Prepare data in one step/system, evaluate in another
27+
- **Slog Logging**: Customizable structured logging with `slog`
2928

3029
## Installation
3130

@@ -35,7 +34,7 @@ go get github.com/robbyt/go-polyscript@latest
3534

3635
## Quick Start
3736

38-
Here's a simple example of using go-polyscript with the Risor scripting engine:
37+
Using go-polyscript with the Risor scripting engine:
3938

4039
```go
4140
package main
@@ -47,10 +46,6 @@ import (
4746
"os"
4847

4948
"github.com/robbyt/go-polyscript"
50-
"github.com/robbyt/go-polyscript/execution/constants"
51-
"github.com/robbyt/go-polyscript/execution/data"
52-
"github.com/robbyt/go-polyscript/machines/risor"
53-
"github.com/robbyt/go-polyscript/engine/options"
5449
)
5550

5651
func main() {
@@ -73,15 +68,12 @@ func main() {
7368

7469
// Input data
7570
inputData := map[string]any{"name": "World"}
76-
dataProvider := data.NewStaticProvider(inputData)
7771

78-
// Create evaluator with functional options
79-
evaluator, err := polyscript.FromRisorString(
72+
// Create evaluator from string with static data
73+
evaluator, err := polyscript.FromRisorStringWithData(
8074
scriptContent,
81-
options.WithDefaults(),
82-
options.WithLogHandler(handler),
83-
options.WithDataProvider(dataProvider),
84-
risor.WithGlobals([]string{"ctx"}),
75+
inputData,
76+
handler,
8577
)
8678
if err != nil {
8779
logger.Error("Failed to create evaluator", "error", err)
@@ -110,12 +102,11 @@ go-polyscript uses data providers to supply information to scripts during evalua
110102
The `StaticProvider` supplies fixed data for all evaluations. This is ideal for scenarios where the input data remains constant across evaluations:
111103

112104
```go
113-
// Create a static provider with predefined data
105+
// Create static data for configuration
114106
configData := map[string]any{"name": "World", "timeout": 30}
115-
provider := data.NewStaticProvider(configData)
116107

117-
// Create evaluator with the provider
118-
evaluator, err := polyscript.FromRisorString(script, options.WithDataProvider(provider))
108+
// Create evaluator with static data
109+
evaluator, err := polyscript.FromRisorStringWithData(script, configData, logHandler)
119110

120111
// In scripts, static data is accessed directly:
121112
// name := ctx["name"] // "World"
@@ -128,45 +119,57 @@ However, when using `StaticProvider`, each evaluation will always use the same i
128119
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:
129120

130121
```go
131-
// Create a context provider for dynamic data
132-
provider := data.NewContextProvider(constants.EvalData)
122+
// Create evaluator with dynamic data capability
123+
evaluator, err := polyscript.FromRisorString(script, logHandler)
133124

134-
// Prepare context with runtime data
125+
// Prepare context with runtime data for each request
135126
ctx := context.Background()
136-
userData := map[string]any{"userId": 123, "preferences": {"theme": "dark"}}
137-
enrichedCtx, _ := provider.AddDataToContext(ctx, userData)
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+
}
138132

139-
// Create evaluator with the provider
140-
evaluator, err := polyscript.FromRisorString(script, options.WithDataProvider(provider))
133+
// Execute with the enriched context
134+
result, err := evaluator.Eval(enrichedCtx)
141135

142136
// In scripts, dynamic data is accessed via input_data:
143137
// userId := ctx["input_data"]["userId"] // 123
144138
```
145139

146-
### CompositeProvider
140+
### Combining Static and Dynamic Data
147141

148-
To combine static data with dynamic runtime data, you can use the `CompositeProvider`. This allows you to stack a `StaticProvider` with a `ContextProvider`, enabling both fixed and variable data to be available during evaluation:
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:
149143

150144
```go
151-
// Create providers for static and dynamic data
152-
staticProvider := data.NewStaticProvider(map[string]any{
145+
// Create static configuration data
146+
staticData := map[string]any{
153147
"appName": "MyApp",
154148
"version": "1.0",
155-
})
156-
ctxProvider := data.NewContextProvider(constants.EvalData)
149+
}
157150

158-
// Combine them using CompositeProvider
159-
provider := data.NewCompositeProvider(staticProvider, ctxProvider)
151+
// Create evaluator with the static data
152+
evaluator, err := polyscript.FromRisorStringWithData(script, staticData, logHandler)
153+
if err != nil {
154+
// handle error
155+
}
156+
157+
// 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+
}
160163

161-
// Create evaluator with the composite provider
162-
evaluator, err := polyscript.FromRisorString(script, options.WithDataProvider(provider))
164+
// Execute with both static and dynamic data available
165+
result, err := evaluator.Eval(enrichedCtx)
163166

164167
// In scripts, data can be accessed from both locations:
165168
// appName := ctx["appName"] // Static data: "MyApp"
166169
// userId := ctx["input_data"]["userId"] // Dynamic data: 123
167170
```
168171

169-
By using the `CompositeProvider`, you can ensure that your scripts have access to both constant configuration values and per-evaluation runtime data, making your evaluations more flexible and powerful.
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.
170173

171174
## Architecture
172175

@@ -198,7 +201,7 @@ go-polyscript provides the `EvalDataPreparer` interface to separate data prepara
198201

199202
```go
200203
// Create an evaluator (implements EvaluatorWithPrep interface)
201-
evaluator, err := polyscript.FromRisorString(script, options...)
204+
evaluator, err := polyscript.FromRisorString(script, logHandler)
202205
if err != nil {
203206
// handle error
204207
}
@@ -224,22 +227,24 @@ For more detailed examples of this pattern, see the [data-prep examples](example
224227
### Using Starlark
225228

226229
```go
227-
// Create a Starlark evaluator with options
228-
evaluator, err := polyscript.FromStarlarkString(
229-
`
230-
# Starlark has access to ctx variable
231-
name = ctx["name"]
232-
message = "Hello, " + name + "!"
233-
234-
# Create the result dictionary
235-
result = {"greeting": message, "length": len(message)}
236-
237-
# Assign to _ to return the value
238-
_ = result
239-
`,
240-
options.WithDefaults(),
241-
options.WithDataProvider(data.NewStaticProvider(map[string]any{"name": "World"})),
242-
starlark.WithGlobals([]string{constants.Ctx}),
230+
// Create a Starlark evaluator with static data
231+
scriptContent := `
232+
# Starlark has access to ctx variable
233+
name = ctx["name"]
234+
message = "Hello, " + name + "!"
235+
236+
# Create the result dictionary
237+
result = {"greeting": message, "length": len(message)}
238+
239+
# Assign to _ to return the value
240+
_ = result
241+
`
242+
243+
staticData := map[string]any{"name": "World"}
244+
evaluator, err := polyscript.FromStarlarkStringWithData(
245+
scriptContent,
246+
staticData,
247+
logHandler,
243248
)
244249

245250
// Execute with a context
@@ -249,12 +254,13 @@ result, err := evaluator.Eval(context.Background())
249254
### Using WebAssembly with Extism
250255

251256
```go
252-
// Create an Extism evaluator
253-
evaluator, err := polyscript.FromExtismFile(
257+
// Create an Extism evaluator with static data
258+
staticData := map[string]any{"input": "World"}
259+
evaluator, err := polyscript.FromExtismFileWithData(
254260
"/path/to/module.wasm",
255-
options.WithDefaults(),
256-
options.WithDataProvider(data.NewStaticProvider(map[string]any{"input": "World"})),
257-
extism.WithEntryPoint("greet"),
261+
staticData,
262+
logHandler,
263+
"greet", // entryPoint
258264
)
259265

260266
// Execute with a context

Diff for: benchmarks/README.md

+37-17
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
## Benchmarking VMs, and general optimization
1+
# Benchmark Documentation
22

3-
This directory contains benchmarking tools and historical benchmark records to track performance
4-
characteristics of go-polyscript. These benchmarks serve as both documentation of optimization
5-
effectiveness and protection against performance regressions. The data collected here demonstrates
6-
the impact of various performance optimizations implemented throughout the package.
3+
This directory contains benchmarking tools and historical benchmark records for go-polyscript performance analysis. Benchmarks serve as documentation of optimization effectiveness and protection against performance regressions.
4+
5+
## Performance Optimization Patterns
76

87
### 1. Compile Once, Run Many Times
98

@@ -31,23 +30,23 @@ enrichedCtx, _ := evaluator.PrepareContext(ctx, inputData)
3130
result, _ := evaluator.Eval(enrichedCtx)
3231
```
3332

34-
### 3. Provider Selection
33+
### 3. Provider Performance Comparison
3534

36-
The benchmarks also show performance differences between `data.Provider` implementations:
35+
The benchmarks show performance differences between `data.Provider` implementations:
3736

38-
- **StaticProvider**: Fastest overall - use when runtime data is not needed, and input data is static
39-
- **ContextProvider**: Needed for request-specific data that varies per execution, data stored in local memory
40-
- **CompositeProvider**: Meta-provider, enabling multiple `data.Provider` objects in a series
37+
- **StaticProvider**: Fastest overall (~5-10% faster than other providers) - use when input data is static
38+
- **ContextProvider**: Needed for request-specific data that varies per execution
39+
- **CompositeProvider**: Small overhead but enables both static configuration and dynamic request data
4140

42-
### 4. VM Selection Trade-offs
41+
### 4. Script Engine Performance Characteristics
4342

44-
Performance characteristics vary by VM implementation:
43+
Performance characteristics vary significantly by implementation:
4544

46-
- **Risor**: Fast general-purpose scripting with broad Go compatibility
47-
- **Starlark**: Excellent for configuration processing with Python-like syntax
48-
- **Extism/WASM**: Best security isolation with pre-compiled modules
45+
- **Risor**: Generally fastest for general-purpose scripting with good Go interoperability
46+
- **Starlark**: Optimized for configuration processing with Python-like syntax
47+
- **Extism/WASM**: Best for security isolation with pre-compiled modules
4948

50-
### Running Benchmarks
49+
## Running Benchmarks
5150

5251
To benchmark go-polyscript performance in your environment:
5352

@@ -56,11 +55,32 @@ To benchmark go-polyscript performance in your environment:
5655
make bench
5756

5857
# Run specific benchmark pattern
59-
./benchmark.sh BenchmarkEvaluationPatterns
58+
./benchmarks/run.sh BenchmarkEvaluationPatterns
6059

6160
# Quick benchmark without reports
6261
make bench-quick
6362

6463
# Benchmark with specific iterations
6564
go test -bench=BenchmarkVMComparison -benchmem -benchtime=10x ./engine
6665
```
66+
67+
## Historical Results
68+
69+
Benchmark results are stored in the `results/` directory with timestamps. This allows tracking performance changes over time and identifying performance regressions:
70+
71+
- `benchmark_YYYY-MM-DD_HH-MM-SS.json` - Raw benchmark data
72+
- `benchmark_YYYY-MM-DD_HH-MM-SS.txt` - Human-readable summary
73+
- `comparison.txt` - Comparison of current results with previous runs
74+
- `latest.txt` - Most recent benchmark summary
75+
76+
## Benchmarking Infrastructure
77+
78+
The benchmarking infrastructure in go-polyscript automatically:
79+
80+
1. Runs comprehensive benchmarks across all script engines
81+
2. Compares execution patterns (one-time vs. compile-once-run-many)
82+
3. Measures data provider performance differences
83+
4. Records results for historical comparison
84+
5. Generates human-readable reports
85+
86+
For more detailed examples of optimized script usage patterns, see the [examples directory](../examples/).

Diff for: examples/data-prep/starlark/testdata/script.star

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def process_data():
4040

4141
return result
4242

43-
# Call the function and store the result
4443
result = process_data()
45-
# The underscore variable is what gets returned to the VM
44+
45+
# The underscore variable is returned to Go
4646
_ = result

Diff for: machines/extism/evaluator/evaluator.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,6 @@ func (be *Evaluator) exec(
137137
if err != nil {
138138
return nil, fmt.Errorf("extism execution error: %w", err)
139139
}
140-
141-
logger.InfoContext(ctx, "execution complete", "result", result)
142140
return newEvalResult(be.logHandler, result, execTime, ""), nil
143141
}
144142

@@ -201,12 +199,12 @@ func (be *Evaluator) Eval(ctx context.Context) (engine.EvaluatorResponse, error)
201199
runtimeData,
202200
)
203201
if err != nil {
204-
return nil, fmt.Errorf("execution error: %w", err)
202+
return nil, fmt.Errorf("exec error: %w", err)
205203
}
204+
logger.DebugContext(ctx, "exec completed", "result", result)
206205

207206
// 5. Collect results
208207
result.scriptExeID = exeID
209-
logger.DebugContext(ctx, "execution complete")
210208
return result, nil
211209
}
212210

Diff for: machines/risor/evaluator/evaluator.go

+2-6
Original file line numberDiff line numberDiff line change
@@ -78,16 +78,13 @@ func (be *Evaluator) exec(
7878
bytecode *risorCompiler.Code,
7979
options ...risorLib.Option,
8080
) (*execResult, error) {
81-
logger := be.logger.WithGroup("exec")
8281
startTime := time.Now()
8382
result, err := risorLib.EvalCode(ctx, bytecode, options...)
8483
execTime := time.Since(startTime)
8584

8685
if err != nil {
8786
return nil, fmt.Errorf("risor execution error: %w", err)
8887
}
89-
90-
logger.InfoContext(ctx, "execution complete", "result", result)
9188
return newEvalResult(be.logHandler, result, execTime, ""), nil
9289
}
9390

@@ -136,9 +133,9 @@ func (be *Evaluator) Eval(ctx context.Context) (engine.EvaluatorResponse, error)
136133
// 4. Execute the program
137134
result, err := be.exec(ctx, risorByteCode, runtimeData...)
138135
if err != nil {
139-
return nil, fmt.Errorf("error returned from script: %w", err)
136+
return nil, fmt.Errorf("exec error: %w", err)
140137
}
141-
logger.Debug("script execution complete", "result", result)
138+
logger.DebugContext(ctx, "exec complete", "result", result)
142139

143140
// 5. Collect results
144141
result.scriptExeID = exeID
@@ -155,7 +152,6 @@ func (be *Evaluator) Eval(ctx context.Context) (engine.EvaluatorResponse, error)
155152
return result, fmt.Errorf("function object returned from script: %s", result.Inspect())
156153
}
157154

158-
logger.DebugContext(ctx, "execution complete")
159155
return result, nil
160156
}
161157

0 commit comments

Comments
 (0)