go-ruby-prism is a Go library that leverages the Ruby Prism parser compiled to WebAssembly, enabling Ruby code parsing and analysis without the need for CGO bindings. This solution provides native and efficient integration for Ruby code analysis in Go applications.
- Features
- Installation
- Quick Start
- Detailed Examples
- API Reference
- Advanced Configuration
- Project Structure
- Build and Development
- Performance
- Contributing
- License
- π CGO-Free: Uses the Ruby Prism parser compiled to WebAssembly, completely eliminating the need for CGO bindings
- β‘ High Performance: Leverages WebAssembly efficiency for fast and efficient Ruby code parsing
- π Simplified Integration: Seamless integration into Go applications with minimal configuration
- π Cross-Platform: Works on any platform supported by Go, ensuring universal compatibility
- π― Flexible API: Offers multiple ways to work with the generated AST (Abstract Syntax Tree)
- π Complete Ruby Support: Compatible with Ruby versions 3.3 and 3.4
- π οΈ Advanced Configuration: Support for encoding, scopes, frozen string literals, and other Ruby options
- Go 1.23 or higher
go get github.com/danielgatis/go-ruby-prism
git clone https://github.com/danielgatis/go-ruby-prism.git
cd go-ruby-prism
make all
package main
import (
"context"
"fmt"
"log"
parser "github.com/danielgatis/go-ruby-prism/parser"
)
func main() {
ctx := context.Background()
// Create a new parser instance
p, err := parser.NewParser(ctx)
if err != nil {
log.Fatal(err)
}
defer p.Close(ctx)
// Ruby code to analyze
source := "puts 'Hello, World!'"
// Parse the code
result, err := p.Parse(ctx, []byte(source))
if err != nil {
log.Fatal(err)
}
fmt.Printf("AST Root: %T\n", result.Value)
fmt.Printf("Errors: %d\n", len(result.Errors))
fmt.Printf("Warnings: %d\n", len(result.Warnings))
}
package main
import (
"context"
"encoding/json"
"fmt"
"log"
parser "github.com/danielgatis/go-ruby-prism/parser"
)
func main() {
ctx := context.Background()
p, err := parser.NewParser(ctx)
if err != nil {
log.Fatal(err)
}
defer p.Close(ctx)
source := `
class User
attr_reader :name, :email
def initialize(name, email)
@name = name
@email = email
end
def greet
puts "Hello, #{@name}!"
end
end
`
result, err := p.Parse(ctx, []byte(source))
if err != nil {
log.Fatal(err)
}
// Convert to JSON
jsonResult, err := json.MarshalIndent(result, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(jsonResult))
}
package main
import (
"context"
"fmt"
"log"
"github.com/danielgatis/go-ruby-prism/parser"
)
type CodeAnalyzer struct {
parser.DefaultVisitor
methodCount int
classCount int
}
func (v *CodeAnalyzer) Visit(node parser.Node) {
switch node.(type) {
case *parser.DefNode:
v.methodCount++
fmt.Printf("π Found method at line %d\n", node.StartLine())
case *parser.ClassNode:
v.classCount++
fmt.Printf("π Found class at line %d\n", node.StartLine())
}
v.DefaultVisitor.Visit(node)
}
func (v *CodeAnalyzer) Analyze(node parser.Node) {
for _, child := range node.ChildNodes() {
v.Visit(child)
if len(child.ChildNodes()) > 0 {
v.Analyze(child)
}
}
}
func main() {
ctx := context.Background()
p, err := parser.NewParser(ctx)
if err != nil {
log.Fatal(err)
}
defer p.Close(ctx)
source := `
class Calculator
def add(a, b)
a + b
end
def multiply(a, b)
a * b
end
end
class MathUtils
def self.pi
3.14159
end
end
`
result, err := p.Parse(ctx, []byte(source))
if err != nil {
log.Fatal(err)
}
analyzer := &CodeAnalyzer{}
analyzer.Analyze(result.Value)
fmt.Printf("\nπ Analysis complete:\n")
fmt.Printf(" Classes found: %d\n", analyzer.classCount)
fmt.Printf(" Methods found: %d\n", analyzer.methodCount)
}
The project includes a complete example that downloads and analyzes the entire Rails codebase:
go run example/parse_rails/main.go
This example demonstrates:
- Automatic Rails source code download
- Parsing thousands of Ruby files
Creates a new parser instance with the specified options.
Parses the provided Ruby code and returns the resulting AST.
Releases WebAssembly runtime resources. Should always be called when the parser is no longer needed.
// Configure source file
parser.WithFilePath("app/models/user.rb")
// Configure starting line
parser.WithLine(10)
// Configure encoding
parser.WithEncoding("UTF-8")
// Enable frozen string literals
parser.WithFrozenStringLiteral(true)
// Configure Ruby version
parser.WithVersion(parser.SyntaxVersionV3_4)
// Configure as main script
parser.WithMainScript(true)
// Configure local variable scopes
parser.WithScopes([][][]byte{{[]byte("local_var")}})
// Configure custom logger
parser.WithLogger(customLogger)
The ParseResult
structure contains:
type ParseResult struct {
Value Node // Root AST node
Errors []Error // Parsing errors
Warnings []Warning // Parser warnings
Source []byte // Original source code
}
const (
SyntaxVersionLatest SyntaxVersion = iota // Latest version
SyntaxVersionV3_3 // Ruby 3.3
SyntaxVersionV3_4 // Ruby 3.4
)
type CustomLogger struct{}
func (l *CustomLogger) Debug(format string, args ...interface{}) {
log.Printf("[DEBUG] "+format, args...)
}
// Use custom logger
p, err := parser.NewParser(ctx, parser.WithLogger(&CustomLogger{}))
p, err := parser.NewParser(ctx,
parser.WithMainScript(true),
parser.WithVersion(parser.SyntaxVersionV3_4),
parser.WithFrozenStringLiteral(true),
parser.WithEncoding("UTF-8"),
)
p, err := parser.NewParser(ctx,
parser.WithFilePath("app/controllers/users_controller.rb"),
parser.WithLine(1),
parser.WithMainScript(true),
)
go-ruby-prism/
βββ example/ # Usage examples
β βββ json/ # JSON conversion
β βββ parse_rails/ # Rails application analysis
β βββ visitor/ # Visitor pattern
βββ parser/ # Main parser API
β βββ parser.go # Main interface
β βββ gen_nodes.go # Generated AST nodes
β βββ gen_visitor.go # Generated visitor pattern
β βββ parsing_options.go # Configuration options
βββ prism/ # Ruby Prism submodule
βββ wasm/ # WebAssembly runtime
βββ templates/ # Code generation templates
# Complete build (recommended for first time)
make all
# Generate Go code only
make generate
# Build WASM only
make wasm_build
# Format code
make format
# Clean temporary files
make clean
- Ruby with Bundler (for Prism build)
- WASI SDK (downloaded automatically)
- Go 1.23+
- Initialization:
git submodule update --init --recursive
- Prism Compilation: Compiles Ruby parser to WebAssembly
- Code Generation: Generates Go structs from Prism schema
- Formatting: Applies
go fmt
to all files
- Parser pools for concurrent usage
- Instance reuse when possible
- Automatic WebAssembly memory management
type ParserPool struct {
parsers chan *parser.Parser
ctx context.Context
}
func NewParserPool(ctx context.Context, size int) *ParserPool {
pool := &ParserPool{
parsers: make(chan *parser.Parser, size),
ctx: ctx,
}
for i := 0; i < size; i++ {
p, _ := parser.NewParser(ctx)
pool.parsers <- p
}
return pool
}
func (p *ParserPool) Parse(source []byte) (*parser.ParseResult, error) {
parser := <-p.parsers
defer func() { p.parsers <- parser }()
return parser.Parse(p.ctx, source)
}
All examples are available in the /example
directory:
- JSON Export (
example/json/
): Converts AST to JSON format - Visitor Pattern (
example/visitor/
): Demonstrates custom AST traversal - Rails Analysis (
example/parse_rails/
): Complete Rails application analysis
To run any example:
go run example/[example-name]/main.go
# Set up development environment
git clone https://github.com/danielgatis/go-ruby-prism.git
cd go-ruby-prism
make all
# Run tests
go test ./...
# Check linting
golangci-lint run
Copyright (c) 2024-present Daniel Gatis
Licensed under MIT License