| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
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.
go get github.com/danielgatis/go-ruby-prismgit clone https://github.com/danielgatis/go-ruby-prism.git
cd go-ruby-prism
make allprism.wasm and the generated parser sources are committed, so using the library needs nothing beyond Go. Rebuilding them is only necessary when the prism submodule moves:
make wasm_build # compiles prism.wasm in Docker; needs Docker, not Ruby
make config # copies config.yml out of the submodule
make generate # regenerates the parser sources with the Go generatorCode generation reads prism's config.yml directly, so neither Ruby nor the WASI SDK has to be installed. go generate ./... runs the same generator.
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.goThis example demonstrates:
Creates a new parser instance with the specified options.
Parses the provided Ruby code and returns the resulting AST.
A single parser can be reused for any number of Parse calls, including across different files, and this is the recommended way to process a batch of sources — creating a parser instantiates the WebAssembly module, which costs far more than a parse.
Parse is safe to call from multiple goroutines. A parser holds a pool of WebAssembly instances and each call runs on its own instance, so parses proceed in parallel rather than queueing behind one another. The pool is filled on demand, so a program that only ever parses one file at a time pays for a single instance.
Sets how many instances the pool may hold, which caps how many parses can run at once. It defaults to GOMAXPROCS. Each instance carries its own linear memory, so a larger pool trades memory for concurrency; a size of 1 serializes every parse onto one instance.
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 how many parses can run at once (defaults to GOMAXPROCS)
parser.WithPoolSize(16)
// 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 cleantype 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:
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 runCopyright (c) 2024-present Daniel Gatis
Licensed under MIT License
| Back | FazBrowse Home | New Git URL |