A remote REPL protocol implementation for Zylisp, supporting multiple transport mechanisms (in-process, Unix domain sockets, and TCP) with a unified API.
- Multiple Transports: In-process, Unix domain sockets, and TCP
- Auto-Detection: Client automatically detects transport from address format
- JSON Codec: Human-readable newline-delimited JSON protocol
- Errors as Data: Follows Zylisp's philosophy - evaluation errors are data, not exceptions
- Simple Session Model: Connection = session (no explicit session management)
- Interface-First Design: Extensible architecture for future enhancements
package main
import (
"context"
"github.com/zylisp/repl"
)
func main() {
// Create a TCP server
server, _ := repl.NewServer(repl.ServerConfig{
Transport: "tcp",
Addr: ":5555",
Codec: "json",
Evaluator: myZylispEval,
})
// Start the server
server.Start(context.Background())
}
func myZylispEval(code string) (interface{}, string, error) {
// Your Zylisp evaluator implementation
return nil, "", nil
}package main
import (
"context"
"fmt"
"github.com/zylisp/repl"
)
func main() {
// Create client (transport auto-detected)
client := repl.NewClient()
// Connect to server
client.Connect(context.Background(), "localhost:5555")
defer client.Close()
// Evaluate code
result, err := client.Eval(context.Background(), "(+ 1 2)")
if err != nil {
panic(err)
}
fmt.Printf("Result: %v\n", result.Value)
}- Message Format (
protocol/message.go): Core message structure - Codec (
protocol/codec.go): Message encoding/decoding (JSON, MessagePack) - Operations (
operations/operations.go): Operation handlers (eval, load-file, describe) - Transports (
transport/*/): Connection mechanisms - Unified API (
repl.go): High-level Server/Client interfaces
- Zero-overhead communication using Go channels
- Perfect for testing and embedded use cases
- Address:
"in-process"or""
server, _ := repl.NewServer(repl.ServerConfig{
Transport: "in-process",
Evaluator: myEval,
})- High-performance local IPC
- Ideal for development tools
- Address:
/path/to/socketorunix:///path/to/socket
server, _ := repl.NewServer(repl.ServerConfig{
Transport: "unix",
Addr: "/tmp/zylisp.sock",
Codec: "json",
Evaluator: myEval,
})- Remote REPL access across network
- Address:
host:portortcp://host:port
server, _ := repl.NewServer(repl.ServerConfig{
Transport: "tcp",
Addr: ":5555",
Codec: "json",
Evaluator: myEval,
})Messages are JSON objects with the following fields:
{
"op": "eval",
"id": "1",
"code": "(+ 1 2)",
"status": ["done"],
"value": 3,
"output": "",
"protocol_error": "",
"data": {}
}Fields:
op: Operation name (e.g., "eval", "load-file", "describe")id: Unique message identifier for request/response correlationcode: Code to evaluate (for eval operations)status: Status flags (["done"],["error"],["interrupted"])value: Evaluation result (including Zylisp error-as-data)output: Captured stdout/stderrprotocol_error: Protocol-level errors only (not Zylisp errors)data: Additional operation-specific data
Evaluate Zylisp code.
Request:
{"op": "eval", "id": "1", "code": "(+ 1 2)"}Response:
{"id": "1", "value": 3, "status": ["done"]}Load and evaluate a file.
Request:
{
"op": "load-file",
"id": "2",
"data": {"file": "/path/to/file.zylisp"}
}Response:
{"id": "2", "value": "...", "status": ["done"]}Get server capabilities.
Request:
{"op": "describe", "id": "3"}Response:
{
"id": "3",
"status": ["done"],
"data": {
"versions": {"zylisp": "0.1.0", "protocol": "0.1.0"},
"ops": ["eval", "load-file", "describe", "interrupt"],
"transports": ["in-process", "unix", "tcp"]
}
}Interrupt running evaluation (stub for now).
Request:
{"op": "interrupt", "id": "4", "interrupt-id": "1"}Response:
{"id": "4", "status": ["error"], "protocol_error": "not yet implemented"}The protocol distinguishes between two types of errors:
Connection failures, malformed messages, unknown operations. These are returned as Go errors and set the protocol_error field.
result, err := client.Eval(ctx, code)
if err != nil {
// This is a protocol/transport error
log.Fatal(err)
}Type errors, runtime errors, etc. These are not Go errors - they're successful evaluations that produced error values (errors-as-data).
result, err := client.Eval(ctx, "(/ 1 0)")
if err != nil {
// This would be a transport error
log.Fatal(err)
}
// err is nil - the protocol worked fine
// Check result.Value for Zylisp error-as-data
if errorValue, ok := result.Value.(map[string]interface{}); ok {
if _, isError := errorValue["error"]; isError {
fmt.Printf("Zylisp error: %v\n", errorValue)
}
}| Format | Transport | Example |
|---|---|---|
"" or "in-process" |
In-process | "in-process" |
Path starting with / or . |
Unix | "/tmp/zylisp.sock" |
unix://path |
Unix | "unix:///tmp/zylisp.sock" |
tcp://host:port |
TCP | "tcp://localhost:5555" |
host:port |
TCP | "localhost:5555" |
// server.go
package main
import (
"context"
"github.com/zylisp/repl"
)
func evalZylisp(code string) (interface{}, string, error) {
// Simple mock evaluator
if code == "(+ 1 2)" {
return 3, "", nil
}
return nil, "", nil
}
func main() {
server, _ := repl.NewServer(repl.ServerConfig{
Transport: "tcp",
Addr: ":5555",
Codec: "json",
Evaluator: evalZylisp,
})
server.Start(context.Background())
}// client.go
package main
import (
"context"
"fmt"
"github.com/zylisp/repl"
)
func main() {
client := repl.NewClient()
client.Connect(context.Background(), "localhost:5555")
defer client.Close()
result, err := client.Eval(context.Background(), "(+ 1 2)")
if err != nil {
panic(err)
}
fmt.Printf("Result: %v\n", result.Value) // Output: Result: 3
}server, _ := repl.NewServer(repl.ServerConfig{
Transport: "unix",
Addr: "/tmp/zylisp.sock",
Codec: "json",
Evaluator: myEval,
})
go server.Start(context.Background())
client := repl.NewClient()
client.Connect(context.Background(), "/tmp/zylisp.sock")
result, _ := client.Eval(context.Background(), "(+ 1 2)")Since the protocol uses newline-delimited JSON, you can test it with netcat:
# Start a TCP server on port 5555
# Then connect with netcat:
$ nc localhost 5555
# Send a request (paste this JSON and press Enter):
{"op":"eval","id":"1","code":"(+ 1 2)"}
# You'll receive:
{"id":"1","value":3,"status":["done"]}Run all tests:
go test ./...Run specific transport tests:
go test ./transport/inprocess/
go test ./transport/unix/
go test ./transport/tcp/These features are planned but not yet implemented:
- Explicit Session Management: Multiple sessions per connection
- Streaming Responses: Multiple response messages per request
- MessagePack Codec: Binary protocol for performance
- Advanced Operations: Code completion, symbol documentation, jump-to-definition
- Security: TLS support, authentication/authorization
- Middleware Architecture: Pluggable cross-cutting concerns
- ✅ Protocol message format
- ✅ JSON codec (fully implemented)
- ⏳ MessagePack codec (placeholder only)
- ✅ In-process transport
- ✅ Unix domain socket transport
- ✅ TCP transport
- ✅ Core operations (eval, load-file, describe)
- ⏳ Interrupt operation (stub only)
- ✅ Universal client with transport auto-detection
- ✅ Comprehensive test coverage
See LICENSE file for details.