Expert in developing Rugo, a Ruby-inspired language that compiles to native binaries via Go. Load when working on the rugo compiler, parser, modules, or writing .rg scripts.
preference.md and rats.md in the repo root for current design decisions and pending work before making changes.rugo-quickstart skill when writing .rugo scripts or helping users with Rugo language syntax and features.rugo-native-module-writer when you are writing or debugging a Rugo module written in Rugo lang.idiomatic-rugo skill when writing Rugo code.Rugo is a tiny Ruby-inspired language that compiles to native binaries via Go. The compiler pipeline is:
.rugo source β strip comments β preprocess β parse (EBNF grammar) β AST walk β resolve imports/requires β Go codegen β go build
Repository: github.com/rubiojr/rugo
| Path | Purpose |
|---|---|
main.go |
CLI entry point (urfave/cli): run, build, emit, rats, bench, doc, mod, tool, dev subcommands |
parser/ |
Generated LL(1) parser from rugo.ebnf (do NOT hand-edit parser.go) |
parser/rugo.ebnf |
Authoritative grammar β edit this to change syntax |
ast/ |
AST package: preprocessor, walker, and typed node definitions |
ast/preprocess.go |
Multi-pass preprocessor (compound assignment, bare append, backticks, try sugar, pipes, paren-free calls, shell fallback) |
ast/walker.go |
AST walker β transforms parse tree into typed AST nodes |
ast/nodes.go |
Typed AST node definitions (Statement and Expr interfaces) |
ast/cache.go |
Build cache management |
compiler/ |
Compiler pipeline: codegen + orchestration |
compiler/codegen.go |
Go code generation from AST nodes |
compiler/compiler.go |
Orchestrates: file loading, require resolution, compilation |
remote/ |
Remote module resolution, lockfile management |
cmd/ |
CLI command implementations |
cmd/dev/ |
Developer tools: modgen module scaffolding |
doc/ |
Documentation extractor: parses .rugo doc comments, formats module/bridge docs |
modules/ |
Stdlib module registry and built-in modules |
modules/module.go |
Module type, registry API, CleanRuntime helper |
modules/{ast,bench,cli,color,conv,fmt,http,json,os,queue,re,sqlite,str,test,web}/ |
Built-in modules (each has registration + runtime.go) |
rats/ |
RATS regression tests organized into subdirectories |
rats/core/ |
Core language tests (variables, control flow, functions, lambdas, structs, pipes, etc.) |
rats/stdlib/ |
Stdlib module tests (cli, color, http, json, web, queue, sqlite, etc.) |
rats/tools/ |
Tool tests (linter, tool command, runmd) |
rats/fixtures/ |
Test fixture files |
bench/ |
Performance benchmarks (_bench.rugo files) |
examples/ |
Example .rugo scripts |
tools/ |
Rugo CLI extensions (linter, fuzz, runmd) |
docs/ |
Full documentation (language.md, modules.md, etc.) |
script/test |
Rugo test runner script |
def/end, if/elsif/else/end, while/end, for/in/endputs "hello" (preprocessor rewrites to puts("hello"))"Hello, #{name}!"'no #{interpolation}\n' (single-quoted, no escape processing)PI = 3.14) are immutable bindings (compile-time error on reassignment)fn(params) body end (first-class functions with closures)struct Name fields end with methods via def Name.method(self, ...)echo "hello" | str.upper | puts (chains shell + Rugo functions).map(), .filter(), .reduce(), .each(), .sort_by(), .join(), etc. on arrays and hashesuse "http" β http.get(url)import "strings" β strings.to_upper("hello") (direct Go calls, 16 packages)require "lib" β lib.func()require "github.com/user/repo@v1.0.0" with lock filesrequire "lib" with client, helpersputs, print, len, append, raise, type_of, exitappend arr, val (preprocessor desugars to arr = append(arr, val))for..in loops β for x in arr, for k, v in hash (iterates arrays and hashes)break / next β loop control (compiles to Go break/continue)arr[0] = x, hash["key"] = yarr[-1] returns last element (Ruby behavior)arr[start, length], str[start, length] (clamped, Ruby behavior)x += 1, x -= 1, *=, /=, %= (preprocessor sugar)try expr, try expr or default, try expr or err ... endspawn (goroutine + task handle), parallel (fan-out N, wait all)task.value (block+get result), task.done (non-blocking check), task.wait(n) (timeout)# blocks before def/struct shown by rugo docrugo tool install/list/remove for compiled Rugo tools| Block | Own scope? | Sees outer vars? | Vars leak out? |
|---|---|---|---|
| Top-level | Yes (root) | β | β |
def function |
Yes | Yes (read-only) | No |
fn lambda |
Yes | Yes (captures outer) | No |
if/elsif/else |
No (transparent) | Yes | Yes |
while loop |
Yes | Yes (read + modify) | No |
for..in loop |
Yes | Yes (read + modify) | No |
spawn block |
Yes | Yes (shared) | No |
rats block |
Yes | No (isolated) | No |
ast/preprocess.go)Transforms raw .rugo source before parsing. Operates in multiple passes:
Pass 1: Compound assignment β x += y β x = x + y (also index targets: arr[0] += 1)
Pass 1b: Bare append β append arr, val β arr = append(arr, val)
Pass 2: Backtick expansion β `hostname` β __capture__("hostname")
Pass 3: Try sugar β try EXPR or DEFAULT β multi-line block form
Pass 4: Line-by-line processing:
echo "hi" | str.upper | puts β nested calls (all-shell pipes left native)puts "hi" β puts("hi")ls -la β __shell__("ls -la")Keywords (not treated as shell commands): if, elsif, else, end, while, for, in, def, return, require, break, next, true, false, nil, use, import, test, try, or, spawn, parallel, bench, struct, fn
Important: Shell fallback resolution is positional at top level (function names are only recognized after their def line) but forward-referencing inside function bodies. See preference.md for details.
parser/)parser/rugo.ebnf using the egg toolparser.go β regenerate from the EBNFegg -o parser.go -package parser -start Program -type Parser -constprefix Rugo rugo.ebnfast/walker.go)Walks the parse tree and produces typed AST nodes defined in ast/nodes.go. The AST uses Go interfaces with marker methods:
Node (interface)
Statement: Program, UseStmt, ImportStmt, RequireStmt, FuncDef, TestDef,
IfStmt, WhileStmt, ForStmt, BreakStmt, NextStmt, ReturnStmt,
ExprStmt, AssignStmt, IndexAssignStmt
Expr: BinaryExpr, UnaryExpr, CallExpr, IndexExpr, SliceExpr, DotExpr,
IdentExpr, IntLiteral, FloatLiteral, StringLiteral, BoolLiteral,
NilLiteral, ArrayLiteral, HashLiteral, TryExpr, SpawnExpr,
ParallelExpr, FnExpr
Every statement embeds BaseStmt with SourceLine for .rugo source mapping.
compiler/codegen.go)Converts AST nodes to Go source code. Emits:
main() function with top-level code (or TAP test harness when rats blocks present)rugofn_NAME)exec.Command("sh", "-c", ...)for..in loops via rugo_iterable() / rugo_iterable_default()rugo_index_set() (type-switches arrays and hashes)rugo_array_index() runtime helperrugo_slice() for arrays and stringsbreak/next as Go break/continuespawn as IIFE with goroutine + rugoTask structparallel as IIFE with sync.WaitGroup + sync.Oncerugo_task_* runtime helpersrugo_dot_call runtime dispatch (.map, .filter, .reduce, etc.)//line directives for accurate .rugo source locations in panicssync+time conditionally emitted based on AST flags| Rugo construct | Go function name |
|---|---|
def greet(...) |
rugofn_greet(...) |
ns.func(...) (user module) |
rugons_ns_func(...) |
mod.func(...) (stdlib module) |
rugo_mod_func(...) |
puts(...) |
rugo_puts(...) |
__shell__(...) |
rugo_shell(...) |
__capture__(...) |
rugo_capture(...) |
Rugo has three import mechanisms:
| Keyword | Purpose | Example |
|---|---|---|
use |
Rugo stdlib modules (hand-crafted wrappers) | use "http" |
import |
Go stdlib bridge (auto-generated calls) | import "strings" |
require |
User .rugo files (local or remote) |
require "helpers" |
use)Modules self-register via Go init() using modules.Register(). Each module has:
runtime.go β Go struct + methods (tagged //go:build ignore, embedded as string)runtime.go, declares function signatures with typed argsAvailable modules:
| Module | Description |
|---|---|
ast |
Parse and inspect Rugo source files |
bench |
Benchmark framework |
cli |
CLI app builder with commands, flags, and dispatch |
color |
ANSI terminal colors and styles |
conv |
Type conversions |
fmt |
String formatting (sprintf, printf) |
http |
HTTP client |
json |
JSON parsing and encoding |
os |
Shell execution and process control |
queue |
Thread-safe queue for producer-consumer concurrency |
re |
Regular expressions |
sqlite |
SQLite database access (pure Go, no CGO) |
str |
String utilities |
test |
Testing and assertions |
web |
Chi-inspired HTTP router (routes, middleware, groups, static files) |
Use the module generator to scaffold:
rugo dev modgen mymod --funcs do_thing,other_func
This creates modules/mymod/ with registration, runtime, and stubs files, and adds the blank import to main.go. Fill in the method implementations in runtime.go.
Manual steps:
modules/mymod/ with mymod.go (registration) and runtime.go (implementation)interface{})modules.Register() specifying Name, Type, Funcs, GoImports, Runtimemain.go: _ "github.com/rubiojr/rugo/modules/mymod"docs/mods.md for the full referenceFuncDef.Args| ArgType | Go type | Conversion function |
|---|---|---|
String |
string |
rugo_to_string |
Int |
int |
rugo_to_int |
Float |
float64 |
rugo_to_float |
Bool |
bool |
rugo_to_bool |
Any |
interface{} |
none |
When Variadic is true on FuncDef, extra arguments beyond Args are passed as ...interface{}.
You can create modules in your own Go packages and build custom Rugo binaries. Create a main.go that imports your modules alongside standard ones and calls cmd.Execute(version). See docs/mods.md for examples (including GoDeps for third-party Go dependencies).
import)The import keyword gives direct access to whitelisted Go stdlib packages. The compiler generates type-safe Go calls with interface{} β typed conversions.
import "strings"
import "math"
puts strings.to_upper("hello") # HELLO
puts math.sqrt(144.0) # 12
Function names use snake_case in Rugo, auto-mapped to Go's PascalCase via the registry.
strings, strconv, math, math/rand/v2, path/filepath, sort, os, time, encoding/json, encoding/base64, encoding/hex, crypto/sha256, crypto/md5, net/url, unicode, slices, maps
Special cases use the Codegen callback on GoFuncSig (each bridge file owns its own codegen):
time.Sleep β msβDuration conversion, void wrapped in IIFEtime.Now().Unix() β method-chain calls (GoName contains .), int64βint castos.ReadFile β []byteβstring conversionos.MkdirAll β os.FileMode cast for permissionsstrconv.FormatInt/ParseInt β int64 conversionssort.Strings/Ints β copy-in/copy-out (mutate-in-place bridge)filepath.Split β tuple return mapped to Rugo arrayfilepath.Join β variadic string argsencoding/json β rugo_json_prepare() / rugo_json_to_rugo() for map type conversionsencoding/base64 β method-chain on StdEncoding/URLEncoding, []byte conversionsencoding/hex β []byte conversionscrypto/sha256/md5 β fixed-size array β hex string via fmt.Sprintfnet/url.Parse β struct decomposition into Rugo hashunicode β rune extraction via rugo_utf8_decode() helperslices, maps β runtime-only packages (NoGoImport: true), custom helper functionsGo (T, error) returns auto-panic, integrating with try/or:
n = try strconv.atoi("abc") or 0
import "os" as go_os when namespace conflicts with use "os".
require) and Remote Modulesrequire "helpers" # loads helpers.rugo, namespace: helpers
require "lib/utils" as "u" # loads lib/utils.rugo, namespace: u
Paths resolved relative to calling file. Directory entry points: <dirname>.rugo β main.rugo β sole .rugo file.
withrequire "mylib" with client, helpers # local directory
require "github.com/user/lib@v1.0.0" with client, issue # remote
require "github.com/user/rugo-utils@v1.0.0" as "utils"
Cached in ~/.rugo/modules/. Tagged versions and SHAs cached forever; branches locked to SHA on first fetch.
rugo mod tidy β generates rugo.lock recording exact commit SHA for all remote modulesrugo mod update β re-resolves mutable dependenciesrugo build --frozen β fails if lock file is staleSee remote/ package for implementation details.
go build -o bin/rugo .
go test ./... -count=1
RATS (Rugo Automated Testing System) tests live in rats/ organized by category. Load the rugo-rats skill for full details.
make rats is the canonical way to run the full RATS suite. Always run it before finalizing any work.
When developing, run specific directories or files first for fast feedback, then finish with a single make rats to verify everything passes:
# Fast feedback during development β run specific dirs/files first
bin/rugo rats rats/core/ # run core language tests
bin/rugo rats rats/stdlib/ # run stdlib module tests
bin/rugo rats rats/gobridge/ # run Go bridge tests
bin/rugo rats rats/tools/ # run tool tests
bin/rugo rats rats/core/03_control_flow_test.rugo # run a specific test file
# Then run the full suite before calling it done
make rats
rugo bench bench/ # run all _bench.rugo files
rugo bench bench/arithmetic_bench.rugo # run a specific benchmark
Benchmark files use use "bench" and bench blocks. Auto-calibrates iterations (β₯1s), reports ns/op. Output on stderr (respects NO_COLOR).
go test -bench=. ./compiler/ -benchmem
rugo run script/test
go run . run examples/hello.rugo
go run . emit examples/hello.rugo # inspect generated Go code
Use emit to see what Go code produced is this is the best way to debug codegen issues:
go run . emit script.rugo
rugo.ebnf, regenerate parser.go, then update the walker (ast/walker.go) and codegen.preference.md for the positional resolution design. The preprocessor is in ast/preprocess.go.docs/mods.md. Always add typed function signatures and Doc strings.go test ./... -count=1 and test with relevant examples.go fmt ./...| Command | Description |
|---|---|
rugo run script.rugo |
Compile and run |
rugo build script.rugo |
Compile to native binary |
rugo emit script.rugo |
Print generated Go code |
rugo rats [path] |
Run RATS test files |
rugo bench [path] |
Run benchmarks |
rugo doc [target] |
Show documentation |
rugo mod tidy |
Generate lock file |
rugo mod update |
Re-resolve remote modules |
rugo tool install|list|remove |
Manage CLI extensions |
rugo dev modgen |
Scaffold a new module |
Shorthand: rugo script.rugo is equivalent to rugo run script.rugo. Installed tools are discovered as subcommands: rugo linter ....
Rugo has two concurrency primitives backed by goroutines. See docs/concurrency.md for the full design doc.
spawn β single goroutine + task handletask = spawn
http.get("https://example.com")
end
task = spawn http.get("https://example.com") # one-liner sugar
task.value # block until done, return result (re-raises panics)
task.done # non-blocking: true if finished
task.wait(5) # block with timeout, panics on timeout
parallel β fan-out N expressions, wait for allresults = parallel
http.get("https://api1.com")
http.get("https://api2.com")
end
puts results[0]
.value/.done/.wait always compile to rugo_task_* helpers. The usesTaskMethods AST scan independently gates runtime emission.hasSpawn β sync+time; hasParallel β sync only; usesTaskMethods β sync+timespawn EXPR works at line-start or after =, not nested in function callsrugo tool)Tools are compiled Rugo programs installed to ~/.rugo/tools/ that extend the CLI:
rugo tool install ./tools/linter # local
rugo tool install github.com/user/rugo-fmt@v1.0.0 # remote
rugo tool install core # all official tools
rugo tool list
rugo tool remove linter
rugo linter smart-append examples/spawn.rugo # use installed tool
See docs/tools/tool-command.md for details. Core tools: linter, fuzz, runmd.
rugo doc)The rugo doc command provides introspection for .rugo files, stdlib modules, Go bridge packages, and remote modules.
| Component | Purpose |
|---|---|
doc/doc.go |
Text-level extractor: scans raw .rugo source, attaches # comment blocks to def/struct declarations via blank-line rule |
doc/format.go |
Terminal formatter: renders FileDoc, modules, and bridge packages in go doc-style output |
cmd/cmd.go docAction |
CLI dispatcher: routes to file/module/bridge/remote handlers, pipes through bat if available |
# lines immediately before def/struct (no blank line gap) = doc comment# block at top of file (before any code) = file-level doc# inside function bodies, after a blank line gap, or inline = regular comment (not shown)Both modules.Module/modules.FuncDef and gobridge.Package/gobridge.GoFuncSig have Doc string fields. All stdlib modules and bridge packages have populated docs. When adding new modules or bridge packages, always include Doc strings.
rugo doc file.rugo # all docs in a .rugo file
rugo doc file.rugo symbol # specific function or struct
rugo doc http # stdlib module (use)
rugo doc strings # bridge package (import)
rugo doc github.com/user/repo # remote module
rugo doc --all # list all modules and bridge packages
When bat is on PATH and NO_COLOR is not set, output is piped through bat --language=ruby --style=plain --paging=never for syntax highlighting.
parser.go is generated β never edit it directly; edit rugo.ebnf and regenerate.runtime.go files have //go:build ignore tags β they're embedded as strings, not compiled directly.ast/ package, not compiler/ β walker, nodes, and preprocessor live there.gobridge/ (top-level), not compiler/gobridge/.Codegen callback on GoFuncSig rather than adding cases to codegen.go.rats/core/, rats/stdlib/, rats/gobridge/, rats/tools/ β put new tests in the right subdirectory.