Skip to content

:script — The Gateway

“Explore fast. Ship when ready.”

:script is :core with the training wheels on. It’s designed for the moments when you just want to get something done — explore an idea, prototype a solution, or crunch some data. When you’re ready to ship, one command promotes your script to production-ready :core code.


  • Implicit types — The compiler figures it out
  • Top-level code — No main() wrapper required
  • AOT-first janus run — Compile through the normal backend, then run the native binary
  • Auto-imports — Common stdlib modules available by default
  • Script arena allocator — No explicit memory management

The $-family is what makes :script the replacement for awk, bash, Ruby, and Python. It’s inspired by awk’s $1, $NF — but with static type safety.

FormMeaning
$_Current pipeline element, whole
$1, $2, …Positional component of the current element
$#Index of the current element in the stream
$NNumber of positional components
$_1, $_2, …Closure arguments by position (multi-arg)
$@Full closure argument tuple

All resolved at compile time. If $5 doesn’t exist on your element type, you get a compile error — not a runtime crash.

Terminal window
janus run path/to/script.jans # Compile through AOT, then execute
janus ./path/to/script.jans arg1 # Shebang-friendly direct dispatch
janus run --jit path/to/script.jans # Development/debug runner

janus run uses the same AOT compilation path as janus build, writes a temporary script binary into a scratch location, forwards script arguments, and then executes the native binary. --jit is an explicit development escape hatch; --trace also opts into the JIT runner so trace output stays attached to the interpreter path.

Use std.command when a script needs to orchestrate other binaries. The API is argv-first by default, like Go’s os/exec, so arguments are not interpreted by a shell unless you explicitly ask for shell execution.

use std.command
var out: [4096]u8 = undefined
let n = command.output1("printf", "janus", out[0..4096])
var err: [4096]u8 = undefined
let result = command.capture2("sh", "-c", "printf out; printf err >&2; exit 3", out[0..4096], err[0..4096])
let bounded = command.capture1_timeout_ms("sleep", "2", 100, out[0..4096], err[0..4096])
if bounded.timed_out == 1 do
eprintln("child exceeded deadline")
end
if command.run4("test", "!", "alpha", "=", "beta") != 0 do
eprintln("test command failed")
end
let child = command.spawn2("sh", "-c", "exit 0")
let code = command.wait(child)

Use command.shell(...) or command.shell_output(...) only when shell expansion, pipes, redirects, or shell builtins are intentionally part of the program.


ExcludedAvailable In
Not publishable via Hinge:core (after promotion)
No explicit allocator control:core, :service

Promotion is one command:

Terminal window
janus desugar script.jans > script.jan

Perfect for:

  • Learning Janus interactively
  • Prototyping an idea in 10 minutes
  • Data exploration and analysis
  • One-off automation scripts
  • AI agent tasks (short-lived, disposable code)
  • Homework and algorithm competitions

The rule: Use :script to explore. Use :code (:core) to ship.


# Just run this - no main() needed
print("Hello from :script!")

This is the syntax that makes Nexus operators delete their ~/.zshrc:

<<p"access.log">>
|> map($_.fields())
|> filter($5.to_int()? >= 500)
|> group_by($7)
|> map(($_1, $_2.len()))
|> sort_by($2, desc)
|> take(10)
|> for_each(println)

What this does:

  1. Stream lines from access.log
  2. Split each line into fields (whitespace-separated)
  3. Filter to status codes >= 500 (errors)
  4. Group by the 7th field (URL path)
  5. Count items per group
  6. Sort by count descending
  7. Take top 10
  8. Print each

Compare to the shell equivalent:

Terminal window
cat access.log | awk '{print $5, $7}' | grep -v '^[0-4]' | sort | uniq -c | sort -rn | head -10

The Janus version is:

  • Type-checked$5.to_int()? fails at compile time if not a number
  • Provenance tracked — every line knows its source file and line number
  • Fused — compiles to a single loop, zero intermediate arrays
<<p"server.log">>
|> grep(r/ERROR.*connection reset/)
|> map($_.split(":").1)
|> unique()
|> for_each(println)
# Read JSON, filter, transform, output CSV
let data := read_json("users.json")
|> filter($_.age >= 18)
|> map($.name)
|> sort()
for name in data do
println(name)
end

ConcernawkBashRubyPythonJanus :script
Positional $N
Pipeline operator
Type-checked positions
Zero-alloc fusion
Sub-10ms cold start
Static binary output
Promotion to production


Explore fast. Ship with confidence.