Risor Documentation
repository·main·Indexed 21 days ago
https://github.com/deepnoodle-ai/risorRisor is a fast, embeddable scripting language written in pure Go, designed for safely evaluating user-provided expressions, rules, and small scripts at runtime. It compiles expressions to bytecode for a lightweight VM, offering features such as sandboxed execution, resource limits (max steps, stack depth, and timeouts), and a standard library of built-ins. The ecosystem includes a CLI for executing .risor files, a VS Code extension for syntax highlighting, and an LSP server providing autocompletion, formatting, and definition providers.
What's inside Risor
- The Risor extension for Visual Studio Code currently only supports syntax highlighting. Additional language features are planned for future updates.
Use the regexp module for pattern matching
mainTheregexpmodule provides regular expression matching using RE2 syntax. You can use standalone functions for quick one-off matches or compile patterns intoregexpobjects for better performance during repeated use.Choose the appropriate StepMode for your use case
mainThe
StepModetype determines how frequently theOnStepcallback is triggered. Choosing the correct mode is critical for performance.Mode Description Best Use Case StepAllCalls OnStepfor every single instruction.Detailed tracing, instruction-level debugging. StepNoneNever calls OnStep.Profilers that only need OnCall/OnReturnevents.StepSampledCalls OnStepeveryNinstructions (defined bySampleInterval).Statistical CPU profiling. StepOnLineCalls OnSteponly when the source location (file or line) changes.Coverage tools, line-level debugging, breakpoint-based debuggers. type StepMode uint8 const ( StepAll StepMode = iota StepNone StepSampled StepOnLine )Thread safety constraints for Risor objects
mainRisor objects are not thread-safe. Do not share Risor objects across different goroutines. This design follows the same constraints as Python and JavaScript to maintain simplicity and performance.Equality rules in Risor
mainEquality in Risor is symmetric (
a.Equals(b)impliesb.Equals(a)).Type-specific behavior:
- Numeric types: Compared by value after converting to a common representation (allows cross-type equality between
int,float, andbyte). - Strings and bytes:
bytescan equalstringif the content matches. - Containers: Uses deep equality.
- Lists: Must have the same length and element-wise equality.
- Maps: Must have the same keys and equal values (key order does not matter).
- Other types:
bool(value),null(only to null),time(value),error(message),function/builtin(identity only).
[1, 2, 3] == [1, 2, 3] // true - element-wise comparison {a: 1, b: 2} == {b: 2, a: 1} // true - key order doesn't matter "hello" == bytes("hello") // true- Numeric types: Compared by value after converting to a common representation (allows cross-type equality between
How exception handling works in Risor
mainRisor uses a Python-like exception model with
try,catch,finally, andthrow. A key distinction is thattryis an expression (Kotlin-style semantics), meaning it evaluates to a value.Expression Evaluation Rules
- Success: The expression evaluates to the value of the
tryblock. - Caught Exception: The expression evaluates to the value of the
catchblock. - Finally Block: The
finallyblock always runs but does not affect the expression value of thetryorcatchblocks.
// Returns try value on success let x = try { 42 } catch (e) { -1 } // x == 42 // Returns catch value on exception let x = try { throw "err"; 42 } catch (e) { -1 } // x == -1 // Finally runs but doesn't affect result let x = try { 42 } finally { 999 } // x == 42- Success: The expression evaluates to the value of the
How AST validation and transformation work together
mainRisor uses a two-phase approach for managing script syntax: Validation followed by Transformation.
- Validation Phase: Validators check the AST (Abstract Syntax Tree) against specific rules (e.g., restricting certain language features). Validators are non-destructive and can be aggregated to report multiple errors.
- Transformation Phase: Transformers modify the AST (e.g., constant folding or dead code removal). Transformers are sequential; each transformer receives the AST produced by the previous one.
Ordering Principle: Validation always runs before transformation. This ensures the user's original intent is validated against the syntax rules before any automated modifications occur. If you need to validate the result of a transformation, you must manually invoke a validator within your transformer function.
// Example: Manual post-transform validation postValidator := risor.ValidatorFunc(...) risor.WithTransform(risor.TransformerFunc(func(p *ast.Program) (*ast.Program, error) { transformed := doTransform(p) if errs := postValidator.Validate(transformed); len(errs) > 0 { return nil, syntax.NewValidationErrors(errs) } return transformed, nil }))Use `when` and `match` expressions for pattern-based control flow
mainRisor provides two primary expressions for declarative control flow:
whenfor condition-based logic andmatchfor pattern-based logic. These are designed for policy evaluation, configuration logic, and data transformation.whenExpression: Used for standard conditional logic based on boolean expressions.matchExpression: Used for structural pattern matching and variable binding. It allows you to inspect the shape of data and bind parts of that data to local variables.
To achieve complex validation, you can combine these with:
- Guards: Arbitrary conditions attached to pattern arms.
- Schema Validation: Using the
matchesoperator andschemabuiltin to check types and constraints within a match arm.
Note: For fallible destructuring (e.g., when a structure might lack expected keys), use
matchinstead ofletbindings to handle the failure case safely./* Conceptual usage pattern: match input { UserSchema (if guard_condition) => handle_user(user_vars), OrderSchema => handle_order(order_vars), _ => handle_default() } */Restrict language features using SyntaxConfig
mainYou can restrict the available Risor language features (e.g., disabling variable declarations, control flow, or function definitions) by providing a
SyntaxConfigto the evaluation process. This is useful for creating 'Expression-only' modes or 'Basic Scripting' environments.Commonly used presets include:
risor.ExpressionOnly: Restricts syntax to literals, operators, variable access, indexing, attribute access, and function calls. Disallows variable declarations, assignments, returns, function definitions, try/catch, if/else, switch, destructuring, spread, and pipes.risor.BasicScripting: Allows variable declarations, assignment, and if/else, but disallows function definitions, error handling, and advanced syntax like destructuring or pipes.risor.FullLanguage: The default behavior (zero value) which allows all features.
// Example of using a syntax preset to restrict the language result, err := risor.Eval(ctx, source, risor.WithSyntax(risor.ExpressionOnly), )Ensure exhaustiveness in `when` and `match` expressions
mainTo prevent undefined behavior, both
whenandmatchexpressions require a default case to handle all possible inputs. Failing to provide one will result in a compilation error.whenrequires anelsearm.matchrequires a_(wildcard) arm.
// when requires else let x = when { a => 1 // Error: missing else arm } // match requires _ (wildcard) let y = match value { 1 => "one" // Error: missing default arm }What Risor is and isn't
mainWhat Risor is
A fast, embeddable scripting language for Go applications. It compiles expressions to bytecode and runs on a lightweight VM. It is intended for evaluating user-provided expressions, rules, or small scripts safely at runtime.
What Risor isn't
- It is not a general-purpose programming language meant to replace Python or TypeScript.
- It has no package manager, no module imports, and no third-party ecosystem by design.
- Extension is handled via Go code (adding builtins and passing data) rather than a script-side ecosystem.
Understand error wrapping and equality in Risor v2
mainIn Risor v2, error handling currently relies on Go-specific implementation details that are not part of the stable scripting API:
- Error Wrapping: The
error()builtin usesfmt.Errorfinternally. This allows scripts to use the%wverb to create wrapped error chains. - Error Equality: The
*Error.Equalsmethod (and the==operator) useserrors.Islogic (available since v2.2.0). This means a wrapped sentinel error will match its descendants when compared using==.
Warning: Because these behaviors are implementation details and not documented in the language reference, they are subject to change in future versions (v3) and should not be relied upon for stable script logic.
- Error Wrapping: The