// STRYKE โ€” THE HOTTEST LANGUAGE EVER CREATED ๐Ÿ”ฅ

stryke v0.16.31 ยท Server farms first ยท 100% TDP load testing ยท Distributed agents ยท Scriptable congregation API (28 verbs) ยท Cranelift JIT ยท 10,459 builtins (11,191 keys in %all) ยท LSP + DAP + JetBrains plugin

307,719 Rust src ยท 88,633 test src ยท 227,568 example src

Reference GitHub Issues
// Color scheme

>_STRYKE โ€” SERVER FARMS FIRST

The hottest language ever created. Literally. Pin millions of servers to 100% TDP from a single REPL. Distributed load testing with stryke agent + stryke controller. Also a blazing-fast Perl 5 interpreter: NaN-boxed values, Rayon work-stealing across every CPU, bytecode VM with Cranelift Block JIT, 10,459 builtins in %b (11,191 keys in %all), and pipe-forward syntax. 100% TDP โ€” beware.

Quickstart

Install from crates.io or source, then run scripts or one-liners with s (short for stryke):

# install
cargo install strykelang

# from source
git clone https://github.com/MenkeTechnologies/strykelang
cd strykelang && cargo build --release

# one-liners
s 'p "hello, world"'
s '1:20 |> fi even |> map { _ * _ } |> sum |> p'
s '~>1:20fi{even}map{_*_}sum p'
s 'qw(a b c) |> map uc |> join "," |> p'
s '~> qw(a,b,c) map{uc} join(",") p'

# parallel primitives
s '1:8 |> pmap expensive |> p'

# read + parse JSON
s 'read_json("data.json") |> to_yaml |> p'
s 'p to_yaml read_json "data.json"'

Full install + usage live in the README.

-e is optional. If the first argument isn't a file and looks like code, s runs it directly. s 'p 42' and s -e 'p 42' are equivalent.

Why stryke โ€” One-Liner Comparison

Feature stryke perl5 ruby python awk jq
No -e flag neededโ€”โ€”
No semicolons
Built-in HTTP
Built-in JSON
Built-in CSV
Built-in SQLite
Parallel map/grep
Pipe-forward |>|
Thread macro
In-place edit -iparallelsequentialsequential
Data viz (spark/bars/flame)
Clipboard
Stress testing / heat100% TDP
Distributed agents
JIT compilerCraneliftYJIT
Single binary44MBpkgpkgpkgpkg3MB

Character count โ€” real tasks

Task s perl5 ruby python
Sum 1:100s 'p sum 1:100' 15c45c28c38c
Word freqs -an 'freq(@F) |> dd' 22c61cโ€”โ€”
SHA256 files 'p s256"f"' 13c70c+โ€”80c+
CSV โ†’ JSONs 'csv_read("f") |> tj |> p' 28cneeds modulesneeds modules90c
Parallel maps '1:1e6 |> pmap{_ * 2}' 24cโ€”โ€”โ€”

Overview

  • Parser & compiler โ€” recursive-descent parser in strykelang/parser.rs, producing an AST consumed by the bytecode Compiler (strykelang/compiler.rs) that feeds the VM (strykelang/vm.rs). 100% lowered to bytecode. Cranelift Block JIT kicks in for hot blocks (strykelang/jit.rs).
  • Values โ€” StrykeValue is a NaN-boxed u64: immediates (undef, i32, raw f64 bits) and tagged Arc<HeapObject> pointers for big ints, strings, arrays, hashes, refs, regexes, atomics, channels.
  • Regex โ€” three-tier engine: Rust regex โ†’ fancy-regex (backrefs) โ†’ pcre2 (PCRE-only verbs).
  • Parallelism โ€” every parallel builtin uses rayon work-stealing across all cores. See pmap, pflat_map, pgrep, pfor, psort, preduce, pcache, ppool, fan, pipeline, par_pipeline_stream, pchannel, pselect, par_walk, par_lines, par_sed.
  • Stress testing โ€” heat(60) pins ALL cores to 100% TDP. stryke agent + stryke controller for distributed fleet-wide load testing. The hottest language ever created.
  • Binary size โ€” ~44 MB stripped with LTO + O3.

Language reflection

Everything the parser and dispatcher know about is exposed as plain Perl hashes, populated from the source of truth at compile time. build.rs parses category-labeled section comments in is_perl5_core / stryke_extension_name, the try_builtin match arms, and LSP hover docs in doc_for_label_text. Nine hashes โ€” every direct lookup is O(1). Plus per-package symbol-table stashes (%main::, %Foo::, โ€ฆ) and the live-binding view (%parameters).

Forward maps

Long nameShortKey โ†’ Value
%stryke::builtins%bprimary callable name โ†’ category. Callable-only โ€” keywords live in %k, never %b.
%stryke::keywords%kstryke language keyword โ†’ category ("control", "decl", "exception", "phase", "concurrency", "oo", "aop", "operator", "quote", "visibility", "special"). Disjoint from %b.
%stryke::all%allevery name stryke recognizes โ€” %a + %b + %k. Use for scalar keys %all.
%stryke::perl_compats%pcsubset of %b: Perl 5 core only, name โ†’ category
%stryke::extensions%esubset of %b: stryke-only, name โ†’ category
%stryke::aliases%aalias โ†’ canonical primary
%stryke::descriptions%dname โ†’ LSP one-liner (sparse)

Inverted indexes (O(1) reverse-query)

Long nameShortKey โ†’ Value
%stryke::categories%ccategory โ†’ arrayref of names ($c{parallel} โ†’ [pmap, pgrep, โ€ฆ])
%stryke::primaries%pprimary โ†’ arrayref of its aliases ($p{to_json} โ†’ [tj])

Live-binding view (zsh $parameters analogue)

%parameters is rebuilt on every read so it always reflects the current scope. Keys are sigil-prefixed names ($x, @a, %h, $Pkg::var); values are kind strings.

HashKey โ†’ Value
%parameterssigil-prefixed name โ†’ kind ("scalar" / "array" / "hash" / "atomic_array" / "atomic_hash" / "shared_array" / "shared_hash"). Includes lexicals (my $x) and globals (our $X) and the reflection / stash hashes themselves.

Package stashes (Perl-spec symbol table)

Per-package %Pkg:: hashes show what each package declares โ€” populated from our declarations and sub definitions, exactly like Perl 5. Lexical my bindings never enter a stash. Refreshed lazily on every read so newly-defined subs appear immediately.

HashKey โ†’ Value
%main:: / %Foo:: / %Foo::Bar::unqualified symbol name โ†’ kind ("scalar" / "sub"). Built from our $X + sub foo { } in that package. Stryke-spec note: our @arr / our %h currently do not enter the stash (only our $scalar and sub).

--compat boundary: the nine reflection hashes (%b / %k / %all / %pc / %e / %a / %d / %c / %p + their %stryke::* long names) and %parameters are stryke-only โ€” --compat turns them off entirely so they don't collide with user-declared hashes of the same name. Package stashes (%main::) and %ENV / %INC / %SIG stay on in every mode (Perl 5 spec).

Examples

s 'p $b{pmap}' # "parallel"
s 'p $b{to_json}' # "serialization"
s 'p $k{if}' # "control"
s 'p $k{class}' # "decl"
s 'p $all{tj}' # "serialization" (alias resolves via %all)
s 'p $all{while}' # "control" (keyword resolves via %all)
s 'p scalar keys %all' # total names โ€” callables + aliases + keywords
s 'p $pc{map}' # "array / list" (Perl core only)
s 'p $e{pmap}' # "parallel" (extensions only)
s 'p $a{tj}' # "to_json"
s 'p $d{pmap}' # LSP one-liner
s '$c{parallel} |> e p' # every parallel op, O(1) reverse-lookup
s '$p{to_json} |> e p' # every alias of to_json
s 'keys %pc |> sort |> p' # every Perl compat, sorted
s 'keys %e |> sort |> p' # every stryke extension
s 'keys %k |> sort |> p' # every keyword, sorted
s 'keys %all |> less' # browse every spelling in $PAGER
s 'my %f; $f{$b{_}}++ for keys %b; dd \%f' # per-category frequency
s 'for my $h (qw(b k all pc e a d c p)) { printf "%%%-4s %d\n", $h, scalar keys %$h }' # catalog
s 'p sort keys %{ merge_hash(\%all, \%parameters) }' # every callable + every live binding, sorted
s 'my @scalars = grep { /^\$/ } keys %parameters; p @scalars' # live scalars only
s 'package Foo; our $X = 1; sub hello {} ; p sort keys %Foo::' # X, hello (our + sub, not my)
s '--compat' -e 'print scalar keys %all' # 0 โ€” extensions off in --compat

Diagnostics & tab-complete dump

s 'doctor' # runtime health: version, flags, paths, toolchain (returns warning count)
s 'health' # alias of doctor
s 'lsp_words |> ep' # every name LSP tab-complete knows about (~18k entries)
s 'lsp_words |> ep' > strykelang/lsp_completion_words.txt # regenerate the static-analyzer / completion snapshot
s 'p len(lsp_words)' # count: bare names + sigil globals + CORE:: + main:: + keywords
s 'p now' # Unix epoch seconds (alias of time)
s 'p now("UTC")' # ISO-8601 datetime string in any IANA timezone
s 'my @qs = quantiles([1..1000], [0.25, 0.5, 0.75]); p @qs' # batch quantile lookup, single sort across all probs
s 'class P { v: Int = 0 }; my $p = P->new(v=>7); p $p->to_hash_rec' # recursively flatten class/struct trees to plain hashref

~p> chunk-parallel โ€” string sources

s '~p> "the quick brown fox jumps over the lazy dog" letters freq |> ddump' # parallel letter histogram of one string
s 'my $n = ~p> "the quick brown fox jumps over the lazy dog" letters freq ||> values |> sum; p $n' # 35 โ€” parallel extract, sequential merge
s 'my $n = ~p> "the quick brown fox jumps over the lazy dog" letters freq |then| values |> sum; p $n' # 35 โ€” English boundary marker
s 'my $r = ~> "abcdefghij" par_reduce { length(_) }; p $r' # 10 โ€” par_reduce auto-sum, single-block form
s 'my $r = ~> "abcdefghij" par_reduce { length(_) } { _0 + _1 }; p $r' # 10 โ€” explicit pairwise reducer
s 'my $m = ~> "the quick brown fox" words par_reduce { len(_) } { _0 > _1 ? _0 : _1 }; p $m' # 5 โ€” max word length via custom reducer

~p> chunk-parallel โ€” corpus workloads

s 'my $h = ~p> c("**.stk") letters freq; p len(keys %$h)' # distinct letters across every .stk file
s 'my $h = ~p> c("**.stk") words freq; p len(keys %$h)' # unique-word count across the corpus
s 'my $h = ~p> c("**.stk") sentences freq; p len(keys %$h)' # unique-sentence count
s 'my $n = ~p> c("**.stk") par_reduce { length(_) }; p $n' # total chars across the corpus (auto-sum)
s 'my $n = ~p> c("**.stk") par_reduce { my @l = split(/\n/,_); len(@l) }; p $n' # total lines across the corpus
s 'my $n = ~p> c("**.rs") par_reduce { my @m = (_ =~ /unsafe/g); len(@m) }; p $n' # count regex hits across all files
s 'my $n = ~p> c("**.stk") par_reduce { /threading/ ? 1 : 0 }; p $n' # files containing a pattern (truthy chunks)
s 'my $h = ~p> c("**.stk") words map { lc } map { substr(_,0,3) } freq; p $h->{the}' # histogram of 3-letter prefixes

~p> chunk-parallel โ€” top-N highlights

s 'my $h = ~p> c("**.stk") letters freq; p (sort { $h->{$_1} <=> $h->{$_0} } keys %$h)[0..4]' # top 5 letters across .stk
s 'my $h = ~p> c("**.stk") words freq; p (sort { $h->{$_1} <=> $h->{$_0} } keys %$h)[0..9]' # top 10 words across .stk

~d> distributed โ€” same surface, chunks ship over ssh

s 'my $c = cluster("build1:8","build2:16"); my @h = ~d> on $c c("**.stk") map { sha_256(slurp_raw($_)) }; say scalar @h' # hash every .stk in parallel across two hosts
s 'my $c = cluster("h1:4","h2:4"); my @r = ~d> on $c 1:1_000_000 map { $_ * 2 }; say "$r[0] .. $r[-1]"' # source-order preserved across slots
s 'my $c = cluster("h1:8"); my @r = ~d> on $c @urls map { fetch($_) } ||> uniq sort' # ||> switches back to sequential after the distributed stage
STRYKE_CLUSTER_LOCAL_BIN=$(which stryke) s 'my $c = cluster("localhost:4"); my @r = ~d> on $c 1:100 map { $_ + 100 }; say "@r[0..4]"' # loopback bypass โ€” verify ~d> without ssh keys

par { BLOCK } โ€” generic chunk wrapper

s 'my @a = ~> "Hello, World 123" par { letters }; p "@a"' # H e l l o W o r l d โ€” letters per chunk
s 'my @r = ~> "Hello, World" par { uc }; p "@r"' # per-chunk uppercase

~s> per-item streaming โ€” basics

s 'my $n = ~s> [1, 2, 3, 4, 5] map { _ * 10 } map { _ + 1 }; p "$n items"' # basic two-stage stream
s 'my $n = ~s> [1..10] grep { _ % 2 == 0 } map { _ * _ }; p "$n matched"' # filter then transform
s 'my $n = ~s> [1..5] map { sleep 0.05; _ * _ }; p "$n items"' # mixed I/O+CPU โ€” workers overlap

~s> per-item streaming โ€” real workloads

s 'my @raw = ({\"v\":10}, {\"v\":20}, {\"v\":30}); my $n = ~s> [@raw] map { _->{v} * 2 }; p $n' # streaming JSON-shape transform
s 'my @log = ("INFO ok","ERROR oops","WARN slow","ERROR bad"); my $n = ~s> [@log] grep { /ERROR/ } map { uc }; p $n' # streaming log filter (errors โ†’ upper)
s 'mysync $sum = 0; my $n = ~s> [10,20,30,40,50] map { $sum += _; _ }; p "$n items, sum=$sum"' # streaming with shared mysync accumulator

Notes

  • Every $h{name} lookup is O(1). Inverted indexes (%c, %p) let you do reverse-queries in O(1) too; filters like grep { cond } keys %h are still O(n).
  • Hash sigil namespace is separate from scalars/functions โ€” the short-alias letters don't collide with $a/$b sort specials, the e extension function, or any other stryke name.
  • %descriptions is sparse: exists $d{$name} doubles as "is this documented in the LSP?".
  • A value of "uncategorized" in %builtins flags a name that's dispatched at runtime but missing a // โ”€โ”€ category โ”€โ”€ section comment in parser.rs.

Pipe-forward

The |> operator (F# / Elixir) threads a value into the first argument of the next call โ€” parse-time only, zero runtime cost.

x |> f          # f(x)
x |> f(a, b)    # f(x, a, b)
x |> f |> g(2)  # g(f(x), 2)

# real pipeline โ€” count words per file, top 10 longest:
f("*.txt") |> map { [_, slurp(_) |> split(/\s+/) |> scalar] }
            |> sort { $b->[1] <=> $a->[1] }
            |> take(10)
            |> dd

Precedence sits between ?: and ||, so x + 1 |> f || y parses as f(x + 1) || y.

Pipe-RHS sugar

  • Thread macro โ€” t EXPR s1 s2 s3 expands to EXPR |> s1 |> s2 |> s3. Stages like pow(_, 2) use bare _ (or _) as a placeholder, auto-wrapped in a coderef. Bare _ = _ in any expression โ€” enables map{_*2}fi{_>5}.
  • >{ BLOCK } โ€” IIFE anywhere an expression is valid; also works as a pipe stage (lhs |> >{ body }).
  • @[...] โ€” sugar for @{[...]} (deref anonymous arrayref inline).
  • %[k => v] โ€” sugar for %{+{k => v}} (deref anonymous hashref inline, sidesteps the block-vs-hashref ambiguity).

Sketch algebra โ€” operator overloads on probabilistic data structures (world-first)

Probabilistic data structures are first-class operands for + | & ^ -. No other language ships this as a syntactic primitive โ€” Python/Ruby/Node/Java/Wolfram all require library function calls like bloom_union(a,b) or roaring.intersect(b). Operators are functional: a new sketch is returned, neither operand is mutated.

my $u = $bloom_a + $bloom_b;       # Bloom union (also: |)
my $u = $hll_a   + $hll_b;         # HyperLogLog union (also: |)
my $u = $cms_a   + $cms_b;         # Count-Min counter sum
my $u = $topk_a  + $topk_b;        # SpaceSaving heavy-hitter merge
my $u = $td_a    + $td_b;          # t-digest centroid merge
my $u = $rb_a | $rb_b;             # Roaring union
my $i = $rb_a & $rb_b;             # Roaring intersection
my $x = $rb_a ^ $rb_b;             # Roaring symmetric difference
my $d = $rb_a - $rb_b;             # Roaring andnot

Shape mismatch (HLL precision differs, Bloom capacity differs) falls back to the default numeric/bitwise operator path โ€” no panic, no silent truncation.

rkyv KV store โ€” zero-copy CRUD (world-first)

First-class CRUD store with rkyv as the on-disk codec. No other scripting language ships a zero-copy archive KV as core builtins โ€” Python has shelve (pickle), Ruby has PStore (Marshal), Perl has DBM_File (BerkeleyDB), Node has level bindings. Every one pays a parse + allocate per read. Stryke's kv_get is mmap + validate + cast โ€” the same primitive script_cache.rs already uses for cached bytecode.

my $db = kv_open("/tmp/cache.rkyv");
kv_put($db, "users:alice", { name => "Alice", age => 30 });
kv_put($db, "users:bob",   { name => "Bob",   age => 28 });
kv_commit($db);

for my $row (kv_scan($db, "users:")) {
    my ($key, $val) = @$row;
    say "$key -> $val->{name}";
}

SQLite-shaped API ergonomics, beats SQLite on reads for any store that fits comfortably in RAM. Atomic rewrite on commit (tmp + rename), versioned format header (STKV magic + format_version), all-or-nothing kv_batch. Phase 2 ships stryke kvd server + remote kv_connect client over the same archive bytes.

Surface: kv_open, kv_put, kv_get, kv_del, kv_exists, kv_keys, kv_scan, kv_len, kv_commit, kv_batch, kv_close, kv_stats.

String coordinates โ€” bytes vs codepoints

Stryke runs string code in two coordinate systems. Perl 5 builtins stay byte-indexed for binary-protocol and .pm-source compat. Stryke extensions are codepoint-indexed so search positions and slice bounds line up. They are never auto-converted โ€” pick one coordinate system per expression and keep it consistent.

OperationStryke (codepoints)Perl 5 (bytes)
Lengthlen $slength $s
Index char$s[$i]substr $s, $i, 1
Slice$s[$a:$b] (inclusive)substr $s, $start, $len
Search forwardcindex $s, $needle [, $from]index $s, $needle [, $from]
Search backwardcrindex $s, $needle [, $from]rindex $s, $needle [, $from]
Match positionโ€”pos $s (regex \G)
# `โ”€` is 3 bytes in UTF-8 / 1 codepoint
my $s = "hello โ”€ world"

length $s                    # 15  (bytes)
len    $s                    # 13  (codepoints)
index  $s, "world"           # 9   (byte position โ€” past the 3-byte `โ”€`)
cindex $s, "world"           # 7   (codepoint position)
$s[7]                        # "w" โ€” codepoint index
substr $s, 9, 1              # "w" โ€” byte index
$s[7:11]                     # "world" โ€” codepoint slice
substr $s, 9, 5              # "world" โ€” byte substr

Rule: never feed an index / pos / length result into a [$a:$b] slice, and never feed a cindex / crindex / len result into substr / index. The coordinate systems silently misalign on any string containing non-ASCII bytes.

--no-interop does not force this split โ€” both systems remain available because Perl 5 binary-protocol code legitimately needs byte positions. The split is a coordinate-system choice, not a stylistic one.

Builtin categories

10,459 primaries in %b (11,191 keys in %all, including aliases and keywords). Query them via keys %stryke::builtins โ€” the table below is a navigational overview, not a full index. Run s 'p scalar keys %b' for the live primary count.

Array / List (134+)

map maps flat_map grep sort reverse push pop shift unshift splice reduce fold fore e first any all none take take_while drop skip_while partition min_by max_by zip zip_with interleave frequencies count_by chunk windowed enumerate shuffle uniq dedup compact flatten concat pluck grep_v with_index sorted sorted_desc sorted_nums without take_last drop_last pairwise batch rotate swap_pairs sliding_pairs run_length_encode group_consecutive permutations combinations power_set cartesian_product

String (129+)

chomp chop length substr index rindex split join uc lc ucfirst lcfirst chr ord trim lines words chars snake_case camel_case kebab_case pascal_case constant_case capitalize swap_case title_case squish pad_left pad_right center truncate_at reverse_str rot13 rot47 caesar_shift count_vowels count_consonants first_word last_word left_str right_str wrap_text dedent indent strip_html levenshtein soundex extract_numbers extract_emails extract_urls

Math / Numeric (147+)

abs int sqrt sin cos tan asin acos atan sinh cosh tanh exp log rand srand inc dec avg stddev clamp normalize range even odd zero positive negative sign negate double triple half round floor ceil gcd lcm factorial fibonacci is_prime divisors sieve_primes cbrt log2 log10 hypot rad_to_deg deg_to_rad pow2 softmax argmax argmin

Conversion / Units (102+)

c_to_f f_to_c c_to_k k_to_c miles_to_km km_to_miles feet_to_m m_to_feet inches_to_cm kg_to_lbs lbs_to_kg bytes_to_kb bytes_to_mb bytes_to_gb seconds_to_minutes hours_to_seconds liters_to_gallons cups_to_ml joules_to_cal watts_to_hp pascals_to_psi to_bin to_hex to_oct from_bin from_hex from_oct to_base from_base

Validation (91+)

is_empty is_blank is_numeric is_upper is_lower is_alpha is_digit is_alnum is_space is_palindrome is_prime is_sorted is_subset is_superset is_valid_ipv4 is_valid_ipv6 is_valid_email is_valid_url is_valid_json is_valid_semver is_valid_base64 is_ascii is_printable luhn_check is_undef is_defined is_array is_hash is_code is_ref is_int is_float

Hash / Map (52+)

keys values each delete exists select_keys top invert merge_hash has_key has_any_key has_all_keys pick omit hash_size hash_from_pairs pairs_from_hash hash_eq keys_sorted values_sorted hash_insert hash_update hash_delete zipmap counter

Encoding / Crypto (64+)

sha1 sha256 sha384 sha512 md5 hmac_sha256 uuid crc32 base64_encode base64_decode hex_encode hex_decode url_encode url_decode gzip gunzip zstd zstd_decode jwt_encode jwt_decode html_escape_str html_unescape_str shell_escape sql_escape hex_dump random_password random_hex_str

I/O (62+)

print p say printf open close eof readline read seek tell slurp input capture pager/pg/less binmode flock getc select truncate read_lines append_file to_file read_json write_json tempfile tempdir file_size file_mtime file_atime is_symlink is_readable is_writable is_executable xopen/xo

Filesystem (84+)

files/f fr dirs/d dr sym_links glob glob_par basename dirname realpath which stat lstat size copy move spurt read_bytes path_ext path_stem path_parent path_join path_split path_is_abs path_is_rel strip_prefix strip_suffix ensure_prefix ensure_suffixfull zsh glob qualifier set ((/), (.), (@), (*), (L+N), (om[1]), (N), (D), ^, ,) on every glob entry-point.

Serialization

to_json/tj to_csv/tc to_toml/tt to_yaml/ty to_xml/tx to_html/th to_markdown/to_md/tmd ddump/dd stringify/str json_encode/decode yaml_encode/decode toml_encode/decode xml_encode/decode json_pretty json_minify escape_json

Parallel (31)

pmap pflat_map pgrep pfor psort preduce preduce_init pmap_reduce pmap_chunked pcache ppool pchannel pselect puniq pfirst pany fan fan_cap pipeline par_pipeline_stream glob_par par_walk par_lines par_sed par_find_files par_line_count pwatch watch

๐Ÿ”ฅ Stress Testing

stress_cpu stress_mem stress_io stress_test heat โ€” Pin ALL cores to 100% TDP. stryke agent + stryke controller for distributed fleet-wide load testing. The hottest language ever created. Literally.

โ›ช Scriptable Distributed Compute

congregation pray annex harvest chant lick bestow smite excommunicate interrogate โ€” 28-verb religious-themed API for fork-spawned worker pools, scatter-gather, %soul state harvest, secure-erase, in-process cathedral discovery, :cloistered ACL. World-first single-keyword distributed primitives.

Functional (56+)

reduce fold inject collect complement constantly coalesce default_to when_true when_false if_else safe_div safe_mod safe_sqrt safe_log scan accumulate keep_if reject_if group_consecutive normalize_list softmax argmax argmin juxt2 juxt3 tap_val debug_val converge iterate_n unfold

Matrix / Linear Algebra (29+)

dot_product cross_product matrix_add matrix_scale matrix_multiply identity_matrix zeros_matrix ones_matrix diagonal matrix_trace matrix_shape matrix_row matrix_col magnitude vec_normalize vec_add vec_sub vec_scale linspace arange

Data / Network (50+)

fetch fetch_json fetch_async par_fetch csv_read csv_write dataframe sqlite json_jq ipv4_to_int int_to_ipv4 email_domain email_local url_host url_path url_query url_scheme

Date / Time (48+)

time localtime gmtime is_leap_year days_in_month month_name weekday_name quarter_of now_ms now_us now_ns unix_epoch today yesterday tomorrow is_weekend is_weekday datetime_utc datetime_from_epoch datetime_strftime

System / Process (35+)

system exec exit fork wait waitpid kill alarm sleep os_name os_arch num_cpus pid ppid uid gid username home_dir temp_dir cwd is_root uptime_secs cmd_exists env_get env_has env_set env_keys env_pairs signal_name has_stdin_tty has_stdout_tty

Geometry (45+)

distance_2d distance_3d midpoint slope area_triangle area_circle circumference perimeter_rect area_rect point_in_circle point_in_rect triangle_area_heron triangle_area_pts polygon_centroid_b28 sphere_volume_b28 sphere_surface_b28 n_ball_volume cylinder_volume_b28 cone_volume_b28 torus_volume_b28 ellipsoid_volume tetrahedron_volume_b28 dist_point_line_2d dist_point_plane_3d closest_pt_segment_2d bbox_from_points haversine_distance_b28 great_circle_law_of_cos initial_bearing midpoint_great_circle shoelace_area polygon_is_convex convex_hull_jarvis euler_characteristic genus_from_euler picks_theorem covariance_matrix_pts

Physics / EM / Optics / Relativity (68+)

coulomb_force_b23 efield_point epotential_point capacitance_parallel_b23 capacitor_energy_b23 ohm_voltage power_vi power_i2r resistance_series_b23 bfield_wire bfield_solenoid lorentz_force_mag cyclotron_frequency_b23 larmor_radius_b23 faraday_emf inductor_energy_b23 lc_frequency rc_tau rl_tau poynting_magnitude em_intensity radiation_pressure em_wavelength em_frequency snell_theta2 critical_angle_b23 brewster_angle_b23 fresnel_rs fresnel_rp lensmaker thin_lens_v lens_magnification diffraction_grating_angle rayleigh_resolution lorentz_gamma time_dilation_b23 length_contraction_b23 rel_momentum rel_ke rel_total_energy relativistic_doppler rel_velocity_add compton_shift_b23 photon_momentum_b23 wave_string_speed sound_solid sound_gas doppler_classical sound_db plasma_frequency_b23 debye_length_b23 alfven_speed schwarzschild_radius_b23 grav_time_dilation grav_redshift

Chemistry (58+)

ph_from_h poh_from_oh pka_from_ka henderson_hasselbalch_b21 arrhenius_k eyring_k first_order_concentration first_order_half_life second_order_concentration zero_order_concentration michaelis_menten_b21 lineweaver_burk_b21 ideal_gas_n van_der_waals_p_b21 redlich_kwong_p compressibility_z partial_pressure_b21 mole_fraction_b21 kc_from_rates kp_from_kc reaction_quotient le_chatelier_dir gibbs_free_energy_b21 dg_from_k k_from_dg vant_hoff clausius_clapeyron antoine_p nernst_equation_b21 emf_from_half_cells faraday_mass_deposited beer_lambert_b21 transmittance ksp_from_concs ionic_strength_b21 debye_huckel cp_monatomic_ideal heat_capacity_q calorimeter_dt enthalpy_reaction avogadro_count moles_from_mass molarity_b21 molality_b21 dilution_v2 raoult_law bp_elevation fp_depression osmotic_pressure rydberg_lambda bohr_radius_n bohr_energy_ev photon_energy_freq photon_energy_lambda de_broglie

Biology / Ecology / Epidemiology (51+)

lotka_volterra_step_b22 logistic_growth_step logistic_growth_analytic gompertz_growth_step allee_growth_step exponential_growth_b22 doubling_time_b22 growth_rate_from_ratio sir_step_b22 seir_step seird_step sis_step r0_basic rt_effective herd_immunity_threshold generation_time shannon_diversity_b22 simpson_diversity_b22 inverse_simpson pielou_evenness margalef_richness menhinick_richness berger_parker jaccard_similarity_b22 sorensen_dice bray_curtis rao_quadratic_entropy hardy_weinberg_b22 selection_step fst_b22 nei_genetic_distance effective_pop_size petersen_estimator chapman_estimator lv_competition_step holling_type1 holling_type2 holling_type3 leslie_step net_reproductive_rate finite_rate_lambda kleibers_law q10 species_area macarthur_wilson_immigration island_equilibrium

Signal Processing (37+)

hamming_window hann_window blackman_window blackman_harris_window bartlett_window welch_window kaiser_window tukey_window gaussian_window hilbert_envelope goertzel_b25 biquad_step biquad_lowpass_coeffs biquad_highpass_coeffs biquad_bandpass_coeffs biquad_notch_coeffs biquad_allpass_coeffs biquad_peak_coeffs biquad_lowshelf_coeffs biquad_highshelf_coeffs butterworth_prewarp butterworth_order fir_moving_average fir_lowpass_design convolve_b25 xcorr_b25 periodogram_b25 spectrogram_simple zero_pad resample_nearest resample_linear quantize mu_law_encode mu_law_decode a_law_encode a_law_decode chirp_linear

Finance / Pricing (39+)

bs_call bs_put bs_vega_b20 bs_theta_call bs_rho_call implied_vol_b20 bachelier_call black76_call crr_american_call crr_american_put jr_european_call trinomial_call heston_price_simple sabr_implied_vol merton_jump_call asian_call_mc barrier_up_out_call digital_call lookback_call macaulay_duration bond_convexity forward_rate discount_continuous ytm_newton vasicek_bond cir_bond hull_white_drift cds_upfront black_karasinski_drift quanto_adjustment fx_forward garman_kohlhagen_call margrabe stulz_min_call sharpe_annualized treynor_ratio_b20 information_ratio_b20 jensen_alpha modified_sharpe

Numerical / ODE / Optimization (45+)

boole_rule gauss_legendre_5 gauss_kronrod_15 romberg_b19 adaptive_simpson_b19 tanh_sinh_quad_b19 midpoint_rule adams_bashforth_4 heun_method rk45_cash_karp milne_pc backward_euler crank_nicolson_ode brent_root ridders_root steffensen_root halley_root muller_root regula_falsi bisection_b19 secant_root anderson_step aberth_step inverse_quad_interp lm_step gradient_descent_step adam_step_b19 rmsprop_step_b19 nesterov_step adagrad_step cg_beta_pr cg_beta_fr bfgs_h_update_1d wolfe_strong_q dogleg_step nelder_mead_reflect nelder_mead_expand nelder_mead_contract sa_accept_prob sa_boltzmann_temp sa_cauchy_temp sa_geometric_temp acceptance_target

Graph Algorithms (32+)

tarjan_scc kosaraju_scc articulation_points_b24 bridges max_flow_ek min_cut_value hopcroft_karp closeness_centrality_b24 betweenness_centrality_b24 eigenvector_centrality_b24 katz_centrality hits_simple pagerank_damped cc_count cc_labels topological_sort_kahn has_cycle_directed has_cycle_undirected bfs_distances_b24 diameter_bfs eccentricity_b24 radius_bfs num_edges density_b24 k_coreness greedy_coloring chromatic_number_greedy sum_degrees avg_degree max_degree is_tree girth

ML / Activations / Metrics (49+)

gini_impurity entropy_bits information_gain gain_ratio nb_gaussian_likelihood nb_bernoulli_likelihood nb_multinomial_log_likelihood adaboost_alpha hinge_loss squared_hinge logistic_loss cross_entropy_b27 kl_div js_div wasserstein_1d_b27 sigmoid_b27 sigmoid_grad tanh_grad relu_b27 relu_grad leaky_relu_b27 elu_b27 selu_b27 swish gelu_b27 mish_b27 softsign hardswish prelu threshold_act confusion_counts mcc f_beta specificity npv_b27 balanced_accuracy cohen_kappa brier_score log_loss tversky mahalanobis_1d softmax_b27 log_softmax one_hot argmax_b27 topk_indices minmax_scale zscore_norm robust_scale

Special Functions (50+)

hyper2f1 hyper1f1 hyper0f1 pochhammer falling_factorial_b29 mathieu_ce0 mathieu_se1 parabolic_d0 parabolic_d1 whittaker_m struve_h0 struve_h1 lambert_w0 wright_omega sinhc cosh_minus1_over_x2 sine_integral_si cosine_integral_ci exp_integral_e1 fresnel_s_b29 fresnel_c_b29 dawson_function owen_t spherical_bessel_j0 spherical_bessel_j1 spherical_bessel_y0 spherical_bessel_y1 mod_sph_bessel_i0 mod_sph_bessel_i1 mod_sph_bessel_k0 coulomb_f0 polylog_li2 polylog_n hurwitz_zeta_b29 dirichlet_eta_b29 dirichlet_beta_b29 catalan_constant_b29 apery_constant_b29 ti2 clausen_cl2 bose_einstein_g fermi_dirac_int theta3 theta2 jacobi_sn_small_q jacobi_cn_small_q jacobi_dn_small_q riemann_xi bessel_jn_general bessel_in_general

Cryptography (41+)

fnv1a_32 fnv1a_64 djb2_hash_b26 sdbm_hash murmur3_32 xxhash32 siphash24 pbkdf2_hmac_step scrypt_round bcrypt_cost_iters argon2_block_mix hkdf_expand_step lfsr_galois_step mt19937_temper xorshift64 xorshift32 pcg32_step lcg_numrec_step splitmix64_step wyhash_mix crc32_b26 crc16_ccitt_b26 adler32_b26 xor_cipher_byte caesar_b26 rot13_b26 railfence_encrypt beaufort affine_encrypt substitution_encrypt letter_frequency english_chi2 index_of_coincidence kasiski_repeats deterministic_prime pollard_rho dh_shared rsa_encrypt_simple monobit_test runs_test_b26 approximate_entropy

Astronomy / Music / Color / Units (67+)

distance_modulus_b30 apparent_magnitude_b30 absolute_magnitude pc_to_ly ly_to_pc pc_to_au au_to_m solar_mass_to_kg solar_luminosity_to_w hubble_distance_mpc comoving_distance_approx critical_density et_freq_ratio midi_to_hz hz_to_midi cents_between just_intonation_ratio pythagorean_ratio beat_frequency bpm_to_spb note_name_to_midi rgb_to_hsl_b30 hsl_to_rgb_b30 rgb_to_yiq rgb_to_yuv601 srgb_to_xyz xyz_to_lab delta_e_76_b30 delta_e_94 c_to_f_b30 f_to_c_b30 inches_to_cm_b30 miles_to_km_b30 lb_to_kg kg_to_lb mph_to_kmh kmh_to_mph mps_to_kmh knots_to_kmh psi_to_pa_b30 pa_to_psi_b30 atm_to_pa mmhg_to_pa ev_to_joules joules_to_ev cal_to_joules btu_to_joules kwh_to_joules bpm_to_midi_tick_us iso226_phon_adjustment db_to_amp amp_to_db roman_encode roman_decode number_to_english

Color / ANSI

rgb_to_hex hex_to_rgb ansi_red ansi_green ansi_blue ansi_yellow ansi_cyan ansi_magenta ansi_bold ansi_dim strip_ansi darken lighten mix_colors is_dark is_light

Random

rand srand coin_flip dice_roll random_int random_float random_bool random_choice random_between random_string random_alpha random_digit random_password random_hex_str

Data Structures

set heap deque stack_new queue_new lru_new counter counter_most_common defaultdict ordered_set bitset_new bitset_set bitset_test bitset_clear

Async / Timing

async spawn await timer bench trace eval_timeout retry rate_limit every gen

Reflection

Nine O(1) hashes โ€” %b builtins, %all every spelling, %pc perl_compats, %e extensions, %a aliases, %d descriptions, %c categories, %p primaries, %parameters live bindings. Plus per-package stashes (%main::, %Foo::).

๐Ÿ”ฅ KILLER FEATURE: Infrastructure Load Testing

stryke is the first language designed for server farms. Pin millions of servers to 100% TDP from a single REPL. The hottest language ever created. Literally.

Stress Testing Builtins

BuiltinWhat It DoesPerformance
stress_cpu(secs)SHA256 hashing on ALL cores1117% CPU (18-core M3 Max)
stress_mem(bytes)Allocate + touch memory across all cores1GB in <100ms
stress_io(dir, iter)Parallel file I/O stressSaturates NVMe
stress_test(secs)Combined CPU + memory + I/OFull system stress
heat(secs)Maximum TDP, Ctrl-C to stop100% all cores indefinitely

One-Liners That Melt Silicon

# Pin all cores for 10 seconds
s 'heat(10)'

# Stress test with metrics
s 'dd stress_test(60)'

# Allocate and touch 8GB RAM
s 'stress_mem(8e9)'

# Maximum parallel computation โ€” billion iterations
s '~>1:1e9 pmaps{sha256 _} collect'

Distributed Agent/Controller Architecture

Deploy stryke agent across your fleet. Control from stryke controller REPL.

# On every server in your fleet
$ stryke agent --controller master.example.com:9999

# On your control node
$ stryke controller
[controller] Listening on 0.0.0.0:9999
[connected] node1 (18 cores, 64GB)
[connected] node2 (18 cores, 64GB)
[connected] node3 (18 cores, 64GB)

> status
  node1: idle (18 cores, 64GB)
  node2: idle (18 cores, 64GB)
  node3: idle (18 cores, 64GB)

> fire 60
[fire] 3 agents, duration=60s
๐Ÿ”ฅ All agents pinned to 100% TDP

> terminate
[terminate] All agents stopped

Scriptable Distributed Compute โ€” 28-Verb Religious Vocab

The REPL above is for human-typed interactive sessions. Scripts drive the same TCP+bincode infrastructure via a 28-verb religious-themed builtin API โ€” scatter work, gather results, manage worker state, all from inside a .stk file:

# Spawn 4 worker processes locally, scatter+gather, clean shutdown
my @workers = congregation(4)
my %results = harvest "render_frame()", @workers, 30_000
excommunicate(@workers)

# Or split scatter/gather, with a coderef closure body
my $div     = pray sub { 2 + $_ }, @workers
my %results = annex $div

# Continuous chant โ€” fires at current AND future joiners (late-join auto-receive)
my $vigil = chant "our %config = (max_depth => 8)", @workers
# ... slaves can profess("renderfarm") and auto-receive the chant on join ...
amen $vigil

Full taxonomy: lifecycle (congregation, anoint, ordain, muster, welcome, excommunicate, bow, profess, apostatize, cathedral) โ€ข scatter/gather (pray, annex, harvest, chant, amen, pilgrimage) โ€ข state inspection (lick, peruse, interrogate) โ€ข state mutation (bestow, smite, recant) โ€ข persistence (enshrine, exhume, smother, martyr, resurrect) โ€ข security (cloister, divine).

Workers maintain our %soul (externally-visible state) and our %gift (master-pushed config). :cloistered ACL via the AGENT_AUTH wire frame (0x1B) restricts membership to anointed workers carrying a valid STRYKE_AGENT_TOKEN. interrogate($pid) polymorphs on its argument shape โ€” a single scalar is an OS PID dump via sysinfo; an array is the agent-VM state path.

World-first claim: scriptable in-language single-keyword scatter-gather + distributed state harvest + secure-erase as language primitives does not exist anywhere โ€” MPI is a C library requiring mpirun; Erlang has process groups but no destructive harvest or peek/commit pair; Spark/Hadoop require cluster bootstrap and JVM; nothing in shell-language space comes close. The lick/annex/smother triple and the chant/amen continuous-rescatter pair are genuinely empty territory.

13 demos under examples/: distributed_congregation.stk (minimum-viable Tier 0), congregation_100x_scale.stk (tested clean at 100 + 250 workers), distributed_prime_sieve.stk (verifies ฯ€(10000)=1229 across N worker shards), distributed_wordcount.stk, distributed_log_aggregation.stk, harvest_oneshot.stk, bestow_then_lick.stk, pilgrimage_barrier.stk, chant_late_joiners.stk, smite_state_reset.stk, enshrine_exhume_roundtrip.stk, cloistered_acl_demo.stk, interrogate_pids.stk, multi_congregation.stk. All CI-safe (loopback fork only, no network), all clean under --no-interop.

See README ยง0x10c for full taxonomy with effect descriptions, killer-features-brainstorm.md "Scriptable Master/Slave" for the design rationale.

What stryke Validates

  • Cooling capacity โ€” Can your data center handle 100% TDP across all blades?
  • Power infrastructure โ€” Wire gauge, UPS failover, generator switchover
  • Hardware reliability โ€” Thermal throttling, component failures under stress
  • Operations response โ€” Alert latency, escalation procedures, incident response
  • BCP/DR exercises โ€” Validate failover, switchover, disaster recovery

Enterprise Deployment

# Zero-install: single static binary
scp stryke server:/usr/local/bin/
ssh server 'stryke agent'

# Kubernetes DaemonSet: deploy to every node
kubectl apply -f stryke-daemonset.yaml

# AOT compile your test suite into one binary
stryke build load_test.stk -o load_test
scp load_test node{1:100}:/tmp/

Compliance Use Cases

Frameworkstryke Validates
SOC 2CC7.2 System operations, CC7.4 Change management
PCI DSS11.3 Penetration testing, 12.10 Incident response
ISO 27001A.17.1 BCP, A.17.2 Redundancies
FedRAMPCP - Contingency Planning

See docs/STRESS_TESTING.md and RFCs for full documentation.

REPL โ€” Shell-Grade Interactive Prompt

Type s and you're in a programming-language REPL and a Unix shell at the same time. Every coreutils-style command works as a bareword; every builtin is callable with or without parens; tab-completion sees every name in %all; history persists; directory stack and aliases match shell muscle-memory.

# Startup banner โ€” reflection counts at-a-glance
$ s
 โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—   โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•—  โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—
 โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•
 โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—   โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—
 โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—  โ•šโ–ˆโ–ˆโ•”โ•  โ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ•โ•โ•
 โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•‘  โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•‘  โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—
 โ•šโ•โ•โ•โ•โ•โ•โ•   โ•šโ•โ•   โ•šโ•โ•  โ•šโ•โ•   โ•šโ•โ•   โ•šโ•โ•  โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•
 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 โ”‚ SYSTEM  status: ONLINE // os: macos arch: aarch64 pid: 30386   โ”‚
 โ”‚ CORES   18    MEM  56.2 / 64.0 GiB available                   โ”‚
 โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
 โ”‚ %b  builtins   10431  %a  aliases    643    %all 11159         โ”‚
 โ”‚ %k  keywords   85     %o  operators  70     %v   97            โ”‚
 โ”‚ %pc perl5 core 186    %e  stryke ext 10245  %d   4360          โ”‚
 โ”‚ %c  categories 316    %p  primaries  8914                      โ”‚
 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  >> PARALLEL PERL5 INTERPRETER // RUST-POWERED v0.16.31 <<

Shell-Like Builtins (no PATH walk, no fork)

These are all in-process Rust functions, not exec() calls to coreutils. No fork. No path resolution. No spawning. Each returns a stryke value usable in the very next pipeline stage.

FamilyBuiltinsBacked by
Filesystemcd pwd ls cat rm mv cp ln mkdir rmdir touch chmod chown realpath basename dirname mktemp mktempdir tree find globstd::fs, tempfile crate
Directory stackpushd popd dir_stackprocess-wide Mutex<Vec<PathBuf>>
Identitywhoami groups id hostname getpwuid getpwnam getgrgid getgrnamlibc
Processps top kill nice renice time sleep wait fork execlibc, nix
Text processinggrep sort uniq wc head tail cut tr tac rev_lines shuf column comm diff myers_diff patience_diffpure Rust
Searchwhich whereis type command$PATH walker
Networkingcurl_get curl_post http_fetch http_post fetch_async fetch_json fetch_url_tool openurl xdg_open openureq + OS launcher
Archivestar zip unzip xz gzip zstdtar, zip, xz2, flate2, zstd crates
Environmentenv env_get env_set env_keys env_pairsstd::env
Encodingiconv strftime date_iso_format format_bytes format_durationchrono, hand-rolled
Terminal controlclear cls reset beep ring_bell set_title set_term_title term_size term_width term_height tty_raw tty_cookedtermios ioctl + ANSI escapes
REPL statehistory repl_alias repl_unalias set_alias unset_aliasprocess-wide tables, reedline history
Inspectionperfview docs lsp_completion_words methods_for which_all fields to_hashintrospection on reflection hashes

Real REPL Session

strykeโฏ ls
total 408
-rw-r--r--    1   501    20     6148 May  9 19:13 .DS_Store
drwxr-xr-x    4   501    20      128 May 14 13:57 .claude
drwxr-xr-x    3   501    20       96 Apr 18 23:49 .cursor

# Coreutils-shaped builtins compose with stryke pipelines:
strykeโฏ ls() |> grep { /\.rs$/ } |> sort |> head 5

# pwd / pushd / popd dir stack โ€” zsh-style:
strykeโฏ pwd
/Users/wizard/code/strykelang
strykeโฏ pushd "/tmp"
/private/tmp
strykeโฏ dir_stack
(/Users/wizard/code/strykelang)
strykeโฏ popd
/Users/wizard/code/strykelang

# REPL aliases (renamed from `alias` so Perl typeglob `*alias = \&sub` keeps working):
strykeโฏ repl_alias("ll", "ls -la")
strykeโฏ repl_alias("g=git")

# Identity + system info โ€” libc-backed, no process spawn:
strykeโฏ whoami
wizard
strykeโฏ groups
(staff everyone admin _developer com.apple.access_ssh)
strykeโฏ term_size
(204 53)

# HTTP without leaving the REPL:
strykeโฏ curl_get("https://api.github.com/repos/MenkeTechnologies/strykelang") |> from_json |> { _->{stargazers_count} }

# Find the binary for a name:
strykeโฏ whereis "git"
(/usr/bin/git /usr/share/man/man1/git.1)

# Recursive tree view:
strykeโฏ tree(".", 2) |> p

# Built-in coreutils-style pipeline โ€” no fork chain:
strykeโฏ cat "/etc/passwd" |> grep { not /^#/ } |> map { (split /:/)[0] } |> sort |> uniq

# Open a URL in the system browser:
strykeโฏ openurl "https://stryke.menketech.com"

# Terminal title for tmux/iTerm window:
strykeโฏ set_title "stryke // 0x42"

What stryke REPL Has That zsh / bash Don't

  • Composable pipelines โ€” every shell-like builtin returns a stryke value, so ls() |> grep { /\.rs$/ } |> sort works without spawning subprocesses
  • 10,459 builtins in scope at all times โ€” math, crypto, AI, web, regex, parallel, codec โ€” every primitive listed in %b is callable from the REPL with zero imports
  • Perl 5 syntax for everything that isn't shell muscle-memory: $_, @ARGV, %ENV, here-docs, regex literals, captures, sub, my, local, our all work
  • Reflection at the prompt โ€” keys %b for the full builtin set, keys %a for aliases, keys %d for documented names, keys %p for primaries, keys %k for keywords, keys %c for category names
  • Live doc browser โ€” docs <name> drops into a TUI hover-doc reader (j/k nav, t for ToC, / search, ]/[ for next/prev chapter)
  • Parallel primitives โ€” 1..1_000 |> pmap { fetch "https://example.com/api/$_" } saturates all 18 cores immediately. No syntax change, no xargs -P
  • Tab completion โ€” backed by lsp_completion_words.txt (22,071 names + every alias + every keyword), routed through reedline
  • Persistent history โ€” reedline stores accepted lines; history() exposes them as a list usable in pipelines
  • No PATH walk, no fork, no exec โ€” every builtin is a Rust function call. grep in stryke is faster than /usr/bin/grep on stryke datasets because there's no process boundary

Coexisting With Perl 5 Semantics

The shell-like builtins don't shadow Perl. Renamed where conflicts existed:

  • alias stays a free Perl typeglob name (*alias = \&original); REPL alias table is repl_alias / set_alias
  • do FILE remains Perl's file-eval; shell-style source is not added (single-spelling rule per CLAUDE.md "no dups")
  • Names like open, close, read, write, print remain Perl 5 file-IO builtins with full Perl semantics; shell-equivalents (cat, tee, spurt) sit alongside as stryke extensions

Cold-start cost: < 10 ms from s to first prompt. The banner with reflection counts is computed on-demand from the actual %b / %a / %all tables โ€” not hard-coded โ€” so it always shows the current build's numbers.

AI Primitives โ€” ai is a builtin like print

Two letters, ubiquitous, unlimited power. Agent loop, MCP client+server, RAG memory, vector search โ€” all native to the language.

# Single-shot
my $r = ai "summarize this", $document

# Auto-routes to agent loop when tool fns are in scope
tool fn weather($city: string) "Get weather" {
    fetch "https://api.weather.com/" . uri_encode($city)
}
ai "what's the weather in Tokyo?"

# Vision, structured output, PDFs โ€” all via the same `ai` call
my $caption = ai "describe", image => "/photo.jpg"
my $user    = ai "extract", schema => +{ name => "string", age => "int" }
my $summary = ai "summarize", pdf => "/contract.pdf"

# Streaming as a real iterator
for my $chunk in stream_prompt("write a haiku") { print $chunk }

# Collection ops โ€” single batched LLM call across the list
my @kept = @{ ai_filter(\@docs, "is about cooking") }
my @summaries = @{ ai_map(\@articles, "summarize in one sentence") }

# RAG memory backed by sqlite
ai_memory_save("doc-1", "Stryke is the fastest Perl-5 interpreter")
my $hits = ai_memory_recall("which language is fast", top_k => 3)

# MCP server in one block
mcp_server "filesystem" {
    tool read_file($path: string) "Read file" { slurp $path }
    tool list_dir($path: string) "List dir"   { join("\n", readdir $path) }
}

Surface

BuiltinWhat it does
ai $prompt, opts...Single-shot or auto-routed agent loop / vision / PDF / structured output
tool fn name(...) "doc" { }Declare an agent-callable tool โ€” auto-schemas signature, auto-attaches to ai()
stream_prompt $p, on_chunk => sub { }SSE streaming with callback or iterator-context for-loop
ai_filter / ai_map / ai_classify / ai_match / ai_sort / ai_dedupeCollection ops โ€” one batched call across the list
embed / vec_cosine / vec_searchEmbeddings + cosine retrieval
ai_memory_save / recall / forgetSqlite-backed RAG memory
mcp_connect("stdio:...") / mcp_connect("https://...")MCP client (stdio + streamable HTTP)
mcp_server "name" { tool ... }Programmatic MCP server in-process
ai_session_*Multi-turn chat with auto-history
ai_budget($usd, sub { })Scoped USD cap; errors if block exceeds
ai_batch(\@prompts)Anthropic batch API at 50% cost
ai_pmap(\@items, "instruction", cluster => $c)Distributed AI across cluster nodes
ai_transcribe "audio.mp3" / ai_speak $textWhisper transcription / OpenAI TTS
ai_image $prompt, output => "out.png"Image generation (DALL-E 3, gpt-image-1)
ai_image_edit $prompt, image => $src, mask => $mImage-to-image edit with optional mask
ai_image_variation image => $src, n => 4Variations of an existing image (DALL-E 2)
ai_dashboard()ANSI summary: cost, tokens, cache hit-ratio
ai_pricing "claude-opus-4-7"Pre-flight pricing hashref (input/output per 1k or 1m)
ai_describe "img.png", style => "alt"Vision wrapper with concise/detailed/alt presets
ai_grounded $p, documents => [@paths]Multi-doc grounding with auto-citations
ai_session_export / ai_session_importPersist a chat session across runs as JSON
ai_models "openai"Live model catalog from any provider
ai_pdf $p, pdf => $f, citations => 1 + ai_citations()Anthropic grounded responses with char/page offsets
ai_file_upload / list / get / deleteOpenAI Files API
ai_file_anthropic_upload / list / deleteAnthropic Files API (beta)
ai_moderate $textOpenAI safety classifier (free)
ai_chunk $text, by => "sentences"RAG text chunker (pure local)
ai_warm(model => ...)Auth + reachability ping at script start
ai_compare $a, $b, criteria => "..."Structured semantic comparison
stryke ai "prompt"UNIX-filter CLI: --model, --system, --stream, --json

Providers: Anthropic (full surface incl. extended thinking, prompt caching, vision, PDF, batch), OpenAI (chat + tool-calls + streaming, Whisper, TTS), Voyage (embeddings, default), Ollama, OpenAI-compatible (openai_compat โ€” LM Studio / vLLM / llama-server, configurable STRYKE_AI_BASE_URL), Google Gemini. Mock with ai_mock_install + STRYKE_AI_MODE=mock-only for deterministic CI.

Full reference: docs/AI_PRIMITIVES.md.

Web Framework โ€” s_web

Rails-shaped scaffold. Generator emits .stk source files; framework runtime is web_* builtins. The whole framework + your entire app + SQLite ships as a single cargo build --release binary via s_web build.

# One command โ€” full-stack app, ~70 resources, dark cyberpunk theme,
# auth + admin + Dockerfile + GitHub Actions + PWA + migrations.
$ s_web new mega --app everything --theme cyberpunk \
    --auth --admin --docker --ci --pwa --migrate
$ cd mega && bin/server
# http://localhost:3000 โ€” signup, login, /admin browses every model,
# /health, /docs (Swagger UI), /openapi.json, ~490 CRUD routes.

Highlights

ConcernBuiltins
Routingweb_route, web_resources, web_root, /openapi.json + /docs auto-served
ORMArticle::all/find/create/update/destroy static methods generated per model; web_model_paginate/search/soft_destroy/with
Migrationsweb_create_table/drop_table/add_column + web_migrate/rollback + schema_migrations
Validationsweb_validate(+{title => "presence,length:1..100"})
Authweb_password_hash/verify, sessions, signed tokens, web_can, web_jwt_encode/decode, web_otp_* 2FA
ViewsERB engine, layouts, partials, helpers (web_link_to/form_with/text_field/button_to, etc.)
Themes9 baked-in: simple, dark, pico, bootstrap, tailwind, cyberpunk, synthwave, terminal, matrix
Presetsblog, ecommerce, saas, social, cms, forum, crm, helpdesk, amazon, facebook, learning, everything
Generatorss_web g {scaffold, model, migration, controller, app, auth, admin, api, mailer, job, channel, docker, ci, pwa}
Fat binarys_web build --out dist && cd dist && cargo build --release โ€” one self-contained executable, no runtime deps

Full reference: docs/WEB_FRAMEWORK.md and stryke_web/README.md.

Expect โ€” Interactive Automation

PTY-driven scripting; the modern Tcl/Expect successor with native cluster fanout.

my $h = pty_spawn("ssh user@host")
pty_expect($h, qr/password:/, 30)
pty_send($h, "$pw\n")
pty_expect($h, qr/\$ /, 30)
pty_send($h, "uptime\n")
my $output = pty_expect($h, qr/\$ /, 30)
pty_close($h)

# Table form (Tcl `expect { ... }` block)
my $tag = pty_expect_table($h, [
    +{ re => qr/password:/, do => fn { pty_send($h, "$pw\n"); "ok" } },
    +{ re => qr/yes\/no/,   do => fn { pty_send($h, "yes\n"); "confirmed" } },
    +{ re => qr/denied/,    do => fn { die "auth failed" } },
], 30)

# Method-form sugar
my $h = PtyHandle::spawn("ssh host")
$h->expect(qr/password:/, 30)
$h->send("$pw\n")
$h->interact()   # raw-mode handoff, Ctrl-] to detach

# Parallel SSH automation across N hosts
my $cluster = cluster(["host1:8", "host2:8", "host3:8"])
pmap_on $cluster @hosts -> $host {
    my $h = pty_spawn("ssh $host")
    pty_expect($h, qr/\$ /, 30)
    pty_send($h, "apt update && apt upgrade -y\n")
    pty_expect($h, qr/\$ /, 1800)
    pty_close($h)
}

Full design: docs/expect-feature-idea.md.

Package Manager

Cargo-shaped manifest + lockfile, hash-pinned reproducible builds, parallel resolver. Single binary surface โ€” no separate cargo-style entry point.

$ stryke new myapp                  # scaffold project at ./myapp/
$ cd myapp
$ stryke add http@^1.0 json         # write deps to stryke.toml
$ stryke install                    # resolve + write stryke.lock
$ stryke update                     # re-resolve and rewrite stryke.lock
$ stryke outdated                   # report deps drifted from their lock pin
$ stryke audit                      # lockfile vs vulnerability advisory feed
$ stryke tree                       # resolved dep graph
$ stryke info http                  # lockfile entry for a dep
$ stryke vendor                     # snapshot store deps to ./vendor/
$ stryke clean                      # wipe target/
$ stryke run greet                  # npm-style task from [scripts]
$ stryke install -g ../mytool       # link [bin] launchers into ~/.stryke/bin/
$ stryke build --release            # AOT-compile to single static binary

Project layout

myapp/
โ”œโ”€โ”€ stryke.toml         # manifest (name, deps, [scripts], [bin], [workspace])
โ”œโ”€โ”€ stryke.lock         # exact versions + integrity hashes
โ”œโ”€โ”€ main.stk            # entry point
โ”œโ”€โ”€ lib/                # module sources (require/use)
โ”œโ”€โ”€ bin/                # additional executables
โ”œโ”€โ”€ t/                  # tests (`stryke test t/`)
โ”œโ”€โ”€ benches/            # benchmarks (`stryke bench`)
โ””โ”€โ”€ target/             # build outputs (gitignored)
    โ””โ”€โ”€ release/myapp   # โ† native machine code, scp-ready

Workspaces

# stryke.toml at workspace root
[workspace]
members = ["crates/*"]

[workspace.deps]
shared = { path = "../shared" }     # one version pinned for the whole monorepo

# In any member's stryke.toml
[deps]
shared = { workspace = true }       # inherit version + features

Deps live globally in ~/.stryke/store/name@version/. No node_modules-shaped per-project tree. Every dep hash-pinned in the lockfile (Nix-style reproducibility, Cargo-style ergonomics). Status: path deps + workspaces + full CLI surface (new/init/add/remove/install/update/outdated/audit/tree/info/vendor/clean/run/install -g) are wired and tested today; registry/git deps + PubGrub semver land when the registry endpoint is deployed (search/publish/yank stubs already return clear "registry not deployed yet" diagnostics). Full design: docs/PACKAGE_REGISTRY.md.

Parallel primitives (highlights)

Every p* primitive uses rayon work-stealing, saturates all cores by default, and takes three surface forms:

# block form   ($_ = element, bare _ is shorthand for $_)
pmap { _ * 2 } 1:1_000_000

# expression form
pmap _ * 2, 1:1_000_000

# bare-fn form   (fn double { _ * 2 })
pmap double, 1:1_000_000

fan / pchannel / ppool

# fan โ€” run a block N times in parallel (_/_0 = index 0:N-1)
fan 16 { heavy_work(_) }

# pchannel โ€” bounded MPMC queue
my ($tx, $rx) = pchannel(100)
async { $tx->send(_) for 1:1000; undef $tx }
while (defined(my $v = $rx->recv)) { p $v }

# ppool โ€” persistent worker pool
my $pool = ppool 4, fn {
  while (defined(my $j = $rx->recv)) { process($j) }
}
$tx->send(_) for @jobs;  undef $tx
$pool->join

Pipelines

# sequential (each stage drains list before next)
pipeline(
  fn { map { _ * 2 } @_ },
  fn { grep { _ > 10 } @_ },
  fn { sum(@_) },
)->run(1:1000)

# streaming โ€” bounded crossbeam channels, concurrent stages
par_pipeline_stream([\&stage1, \&stage2, \&stage3], \@input)

Mutex / semaphore โ€” blocking sync primitives

Backed by parking_lot::Mutex + Condvar โ€” blocking lock / acquire wait on the condvar (no busy-wait, no spin). Arc-shared via StrykeValue::Mutex / StrykeValue::Semaphore so closure capture across spawn / fan shares the same primitive without needing mysync on the handle. Compose with defer for RAII โ€” there's no lock { BLOCK } sugar because the basic shape already does the job.

FunctionAliasesReturnsBehavior
mutex()โ€”Mutexfresh unlocked mutex
mutex_lock($m)โ€”undefblocks on Condvar until acquired; sets held=true
mutex_unlock($m)โ€”undefclears held, notify_one; no-op if already unlocked
mutex_try_lock($m)โ€”1 \| 0non-blocking; 1 if acquired this call, 0 if held by anyone
mutex_is_locked($m)โ€”1 \| 0introspection
semaphore($n)semSemaphoren permits (panics if n < 0)
semaphore_acquire($s)sem_acquireundefblocks until permits > 0; decrements
semaphore_release($s)sem_releaseundef+1 permit, notify_one
semaphore_try_acquire($s)sem_try_acquire1 \| 0non-blocking
semaphore_permits($s)sem_permitsintegercurrent available count
semaphore_limit($s)sem_limitintegerinitial N (immutable)
# Mutex โ€” protect a shared counter across spawn / fan workers
my $m = mutex()
mysync $counter = 0
fan 10000 {
    mutex_lock($m)
    defer { mutex_unlock($m) }
    my $old = $counter; $counter = $old + 1   # genuine read-modify-write
}
p $counter   # exactly 10000

# Semaphore โ€” bound concurrent expensive workers to N
my $s = semaphore(4)
fan 100 {
    sem_acquire($s)
    defer { sem_release($s) }
    do_expensive_work(_)   # at most 4 in flight at any moment
}

# try_lock / try_acquire โ€” non-blocking probes
if (mutex_try_lock($m)) {
    defer { mutex_unlock($m) }
    # critical section โ€” only one worker runs this branch
} else {
    # fall through fast path
}

flock โ€” cross-process advisory locks

For locks that survive a single stryke process (multi-process coordination via a lockfile), flock wraps the POSIX flock(2) syscall directly:

open(my $fh, ">", "/tmp/job.lock") or die
flock($fh, 2)             # LOCK_EX โ€” blocking exclusive
defer { flock($fh, 8) }   # LOCK_UN
# critical section across processes

# non-blocking probe
if (flock($fh, 4)) {      # LOCK_NB | LOCK_EX
    defer { flock($fh, 8) }
    # got it
} else {
    # someone else holds the lock; bail or retry
}

Op constants match Perl 5: LOCK_SH=1, LOCK_EX=2, LOCK_NB|LOCK_EX=4, LOCK_UN=8. The lock is released automatically when the file descriptor closes โ€” process crashes don't leak locks.

AOP โ€” before / after / around advice

Aspect-oriented advice on user subs. Glob pointcuts, three advice kinds, AspectJ-style around with proceed(). Same surface as zshrs's intercept, adapted as keyword statements instead of a CLI builtin.

# before โ€” sees $INTERCEPT_NAME, @INTERCEPT_ARGS
before "fetch" { warn "calling fetch with @INTERCEPT_ARGS" }

# after  โ€” sees $INTERCEPT_RESULT, $INTERCEPT_MS, $INTERCEPT_US
after "fetch" { warn "fetch returned $INTERCEPT_RESULT in ${INTERCEPT_MS}ms" }

# around โ€” block's value is the call's return; proceed() runs original
around "expensive" {
    my $cached = cache_get($INTERCEPT_ARGS[0])
    return $cached if defined $cached
    my $r = proceed()
    cache_put($INTERCEPT_ARGS[0], $r)
    $r
}

# Glob pointcuts: *, ?
before "log_*" { ... }
before "*"     { trace($INTERCEPT_NAME) }

# Management
intercept_list();   # [[id, kind, pattern], ...]
intercept_remove($id);
intercept_clear();

The leading keyword only commits to advice parsing when followed by a string literal, so before(...) as a normal sub call still works. The first matching around wraps; before/after on the same name all fire in registration order. Re-entrancy guard prevents infinite loops when an advice body calls the advised sub. Coverage: user-defined subs only.

Reserved Words

These cannot be used as function names (they are lexer-level operators or language keywords):

y tr s m q qq qw qx qr if unless while until for foreach given when else elsif do eval return last next redo goto my our local state sub fn class struct enum trait use no require package BEGIN END CHECK INIT UNITCHECK and or not x eq ne lt gt le ge cmp

Attempting fn y { } or fn goto { } produces: `y` is a reserved word and cannot be used as a function name

Topic-slot names are also rejected: fn _, fn _<, fn _0, fn _N, fn _N<+, plus the sigil-prefixed forms fn $_, fn @_, and the package-qualified forms fn Foo::_, fn Pkg::_0. A user-defined sub by any of these names would shadow the topic in expression position and silently break every _-aware builtin. Names like fn _foo, fn _NAME, fn my_helper still work โ€” only EXACT topic-slot spellings reject.

Range literals

The : range operator (and the !!! separator for IPv6) recognize several built-in literal types as a single token. The runtime expansion dispatches on the surface form so each type gets the right step semantics.

1~5~1 # numeric (~ works universally) 192.168.1.1:192.168.1.5:1 # IPv4, octet step 2022-01-01:2022-01-05:1 # ISO date, day step 2022-01:2022-04:1 # year-month, month step 0x00:0xFF:1 # hex, preserves prefix/width/case 2001::1~2001::5~1 # IPv6 (must use ~ โ€” : collides) fe80::ff~fe80::fc~-1 # IPv6 reverse step ::1~::5~1 # IPv6 zero-comp prefix

Hex case preservation โ€” uppercase iff EITHER endpoint has any uppercase letter. 0x00:0xFF:1 emits 0x00, 0x01, โ€ฆ, 0xFF (uppercase wins from TO). Outside range context, 0xFF is still the integer 255 โ€” only range-context lookahead triggers the string-typed literal.

Why ~ for IPv6 โ€” IPv6 uses : internally; reusing it as the range separator would be ambiguous. The universal ~ separator dodges the collision and works for all range types. ! can't be used because it's the paired char-index delimiter ($x!N!); inside paired ~โ€ฆ~ char-index/slice subscripts ($x~5~, $_~1:3~) the range op is suppressed so closing delimiters don't get eaten.

Topic chain โ€” _< and the positional matrix

Inside a closure (map/grep/sort block, sub body, fan/fan_cap), _< reads the topic of the enclosing scope โ€” the value _ held in the frame that called this closure. The chain extends up to five frames: _<<, _<<<, _<<<<, _<<<<<. Numeric positional slots get the same matrix: _0<, _1<<, โ€ฆ, _N<<<<<. Bareword and sigil forms ($_<) are equivalent.

Indexed-ascent _<N โ€” past depth 2, counting chevrons gets error-prone. The lexer accepts _<N (where N is a positive integer) as syntactic sugar for N chevrons: _<3 โ‰ก _<<<, $_2<5 โ‰ก $_2<<<<<. The disambiguator preserves slice syntax: _<3> and _<3:5> remain string slices; _<3 (without trailing > or :) is the indexed-ascent form.

# Nested map โ€” `_<` reaches the outer iter's `_`:
map { my @inner = (1, 2); map { _< + _ } @inner } (10, 20)
# 11, 12, 21, 22

# Inside a 3-arg fn, _0<5 (โ‰ก _0<<<<<) reaches arg 0 five frames up:
fn deep($_0, $_1, $_2) {
  ~> 1:1 map { ~> 1:1 map { ~> 1:1 map { ~> 1:1 map { ~> 1:1 map {
    p _0<5, _1<5, _2<5      # preferred: indexed form
    p _0<<<<<, _1<<<<<     # equivalent: chevron form
  } } } } }
}

Iter re-entry โ€” the chain shifts on the FIRST set_topic call in a given frame; subsequent iterations of the same loop only refresh _, so _< keeps pointing at the enclosing scope's topic instead of rolling to the previous iter's value. Bound-but-undef chain entries fall back to _ so $h->{_<} at the outermost iter reads the iteration key.

Mutation semantics โ€” topic variants align with |param| block params

A user writing $_ = ... or $_< = ... inside a block mutates only the current frame. Topic variants follow the exact same rule as |$x| block params and inner my $x: writes do not leak outward, and the chain shift on the next frame entry is purely a function of the outer topic value, never the inner mutation.

form mutation propagates to outer scope? mechanism
|$x| block paramNO โ€” frame-localparam binding lives in callee frame
my $x inside a blockNO โ€” frame-localnew lexical binding in current frame
my $x outer + inner closure writes $xrejected at compile timeDESIGN-001 (closures capture by value)
mysync $x outer + inner closure writes $xYES โ€” explicit Arc<Mutex> opt-inshared cell, atomic compound ops
our $xYES โ€” package-global by designsymbol table, not lexical
$_, $_<, $_<<, $_<<<, $_<<<<, $_<<<<<NO โ€” frame-localFrame::set_scalar_raw bypasses CaptureCell write-through
$_0, $_1, โ€ฆ $_N and $_N<+ chain formsNO โ€” frame-localsame path as topic-chain writes

Implementation: strykelang/scope.rs::Scope::set_scalar recognizes topic-variant names via is_topic_variant_name (regex ^_[0-9]*<*$) and routes the write through Frame::set_scalar_raw, which bypasses the CaptureCell write-through that named outer-scope my variables use. Result: $_< always reads the lexical outer-scope topic of the current closure, never an in-flight mutation from a sibling iteration.

Coderef-in-block-position

Wherever a { BLOCK } is accepted in a per-element list operator (grep, map, sort, first, any, all, none, take_while, drop_while, reject, partition, min_by, max_by, plus their pipe-forward variants), a coderef-shaped expression also works directly. Runtime check: if the EXPR evaluates to a code ref, it is called with the current element(s) as positional args; otherwise the value's truthiness drives filtering (or its result becomes the mapped value, comparator integer, etc.). Eliminates the { $f($_) } / { $f->($_) } boilerplate.

# grep / map โ€” single coderef, no block:
my $is_big = fn ($x) { $x > 3 }
my @r = grep $is_big, @l                  # was: grep { $is_big->(_) } @l
my @r = @l |> grep $is_big                # pipe-forward variant
my @doubled = map fn { _ * 2 }, @l        # inline coderef

# Sort comparators receive ($a, $b) positionally โ€” no $a/$b global magic:
my $cmp = fn ($a, $b) { $b <=> $a }       # or fn { _0 <=> _1 } using positional aliases
my @s = sort $cmp @l

# Tier-2 builtins โ€” no parens, no block:
my $r = first $is_big, @l
my $a = any $is_big, @l
my @t = take_while $is_big, @l
my @r = reject $is_big, @l                # inverse of grep

Threading ~> excluded โ€” whitespace-delimited stages can't disambiguate ~> @l grep $f from "two stages", so threading still requires { $f(_) }. Use |> for the bare-coderef form, or stay with { } blocks under ~>.

Under --compat: dispatch is skipped, restoring Perl's "evaluate EXPR per element, filter by truthiness" semantics. A coderef value is always truthy, so grep $f, @l keeps every element under --compat.

Implicit zero-arg coderef from bare positional

At module top level, a my $f = โ€ฆ declaration whose RHS contains a free bare positional alias (_, _0, _1, _<, โ€ฆ; no $ sigil) is auto-wrapped as a zero-arg coderef. "Free" means at brace-depth 0 inside the RHS: a bare _ inside a nested fn { _ < 4 }, hash literal, map/grep/sort block, or match arm is bound by that block and doesn't trigger the wrap. Inside any block-bodied declaration site, bare _ is the bound topic, so this sugar only fires where _ would otherwise be unbound.

# at top level โ€” all of these auto-wrap into `fn { โ€ฆ }`:
my $sq  = _ * _                  # โ†’ fn { _ * _ }
my $up  = uc _                   # โ†’ fn { uc _ }
my $rev = ~> _ >{split //, _} rev join ""
my $add = _ + _1                 # multi-arg via _1, _2, โ€ฆ
$sq->(5)                         # 25
$add->(3, 4)                     # 7

# nested fn { } binds inner _ โ†’ outer `my` stays eager:
fn call($f, $n) = $f->($n)
my $r = call(fn { _ < 4 }, 5)    # 0  โ€” call() runs eagerly

# inside a block, _ is the topic โ€” NOT auto-wrapped:
fn dbl { my $x = _; $x * 2 }     # $x captures the first arg
map { my $i = _; $i * 2 } 1:3    # $i captures each element

Thread-macro RHS โ€” only the input expression counts

When the RHS starts with a thread-macro intro token (~>, ->>, ~>>, ~p>, ~s>, ~d>, and their -Last variants), the macro itself binds _ for every stage expression. Only the input expression immediately after the arrow can trigger the wrap โ€” bare _ tokens inside stage call arguments are the threaded placeholder, not free topic reads.

# input is concrete (10) โ†’ eager; the `_` in div(_, 2) is the
# thread-last placeholder, not a free topic:
fn Demo::div = _0 / _1
my $r = ->> 10 Demo::div(_, 2)   # 5     โ€” eager call

# input is a free bare `_` โ†’ wraps so the call site supplies it:
my $up = ~> _ uc                 # โ†’ fn { ~> _ uc }
$up->("ya")                      # "YA"

# input is concrete (an array) โ†’ eager even with `_` in stages:
my @r = ~> @nums map { _ * _ } grep { _ > 10 }

Forcing eager evaluation โ€” use the $_ sigil

To opt out of the wrap and evaluate the RHS against the current topic, use the sigil-prefixed form $_ / $_0 / $_1 / $_<. Sigil-prefixed topic reads are eager by definition and never trigger the coderef sugar.

# bare _ โ†’ coderef (deferred)
my $sq = _ * _
p $sq->(5)                       # 25

# $_ sigil โ†’ eager evaluation against the current topic
$_ = 7
my $sq2 = $_ * $_
p $sq2                           # 49

# mixed: free bare _ wins โ†’ the whole RHS wraps,
# and $_ inside the closure reads the topic at call time:
my $mix = $_ + _                 # โ†’ fn { $_ + _ }
$mix->(3)                        # 6    (both $_ and _ are the arg)

Rule: the wrap fires only when (a) we're at block_depth == 0, (b) the LHS is a single scalar, (c) the RHS contains a free (depth-0 within the RHS) bare positional token โ€” match wildcards inside match { _ => โ€ฆ } arms sit at depth โ‰ฅ 1 so they don't false-trigger, and a thread-macro RHS only counts the input expression after the arrow โ€” and (d) the RHS isn't already a coderef-shaped value (fn { โ€ฆ }, \&name, \&{โ€ฆ}). Disabled under --compat.

Hash-key power forms

Stryke extends Perl's bareword autoquoting in three ways:

  • Topic-slot barewords โ€” _, _<, _0, _0<, _N<+ resolve to the topic value, not the literal name. { _ => 1 } โ‰ก { $_ => 1 }; $h->{_<} reads the outer-scope topic.
  • Operator keywords โ€” eq, ne, lt, gt, le, ge, cmp, and, or, not, x autoquote inside {โ€ฆ} hash subscripts. $h->{ne}, $h{eq}, etc. read normally.
  • +{ EXPR } force-hashref โ€” block-vs-hashref disambiguation falls back to a code block when the body doesn't fit KEY => VAL. The + prefix forces hashref interpretation; a list-yielding body is paired up to produce a hash. +{ map { ($_, $_ * 2) } (1,2,3) } gives {1 => 2, 2 => 4, 3 => 6}.

s,/tr,/y, with comma delimiter

The s, tr, and y operators accept any non-alphanumeric delimiter, including ,. The lookahead heuristic gates the comma form on having three commas before the statement ends, so $obj->y method calls and struct Pt { x, y, z } field declarations stay barewords.

stryke -pe 's,a,b,g' < input stryke -pe 'tr,A-Z,a-z,'

Perl-compat highlights

  • OOP โ€” @ISA, C3 MRO (live, not cached), $obj->SUPER::method, tie for scalars/arrays/hashes with TIESCALAR/TIEARRAY/TIEHASH, plus EXISTS/DELETE; use overload with full binary dispatch, nomethod, unary neg/bool/abs. Native class syntax with inheritance, traits, abstract/final, visibility, static fields, operator overloading, and reflection โ€” see OOP section below.
  • Specials โ€” $? packed POSIX status, $| autoflush, $. line count (undef until first read), @ARGV, %ENV, %SIG (SIGINT/SIGTERM/SIGALRM/SIGCHLD), format/write (partial).
  • Phases โ€” BEGIN / UNITCHECK / CHECK / INIT / END run in Perl order; ${^GLOBAL_PHASE} matches Perl.
  • Interpolation โ€” $var, #{expr}, $h{k}, $a[i], @a, @a[slice], $#a, $0, $1..$n; \x{hex} and unbraced \x.
  • Strict / modules โ€” per-mode use strict 'refs' etc., @INC built from -I, vendor/perl, system Perl's @INC, script dir, STRYKE_INC. List::Util is native Rust.

Full list in the README ยง0x08.

Object-Oriented Programming

Stryke supports both Perl 5 OOP (bless, @ISA, tie, use overload) and a native class syntax with inheritance, traits, visibility, static fields, operator overloading, and full reflection.

Class basics

Declare classes with typed fields, defaults, and instance methods. Fields get auto-generated getters/setters. $self is implicit in methods.

class Dog {
    name: Str
    breed: Str = "Mixed"
    fn bark { "Woof from " . $self->name }
}

my $d = Dog(name => "Rex")           # named construction
my $d = Dog("Rex", "Lab")             # positional construction
p $d->name                            # getter โ†’ "Rex"
$d->name("Max")                       # setter
p $d->bark()                          # "Woof from Max"

Methods with parameters

class Calculator {
    value: Int = 0
    fn add($n) { $self->value + $n }
}
my $c = Calculator(value => 10)
p $c->add(5)                          # 15

Static methods

class Math {
    fn Self.add($a, $b) { $a + $b }
    fn Self.pi { 3.14159 }
}
p Math::add(3, 4)                      # 7
p Math::pi()                           # 3.14159

Inheritance (extends)

Single and multiple inheritance. Parent fields and methods are inherited. C3 MRO for diamond resolution.

class Animal {
    name: Str
    fn speak { "Animal: " . $self->name }
}
class Dog extends Animal {
    fn speak { "Woof from " . $self->name }  # override
}
my $d = Dog(name => "Rex")
p $d->speak()                         # "Woof from Rex"

# multiple inheritance
class A { a: Int = 1 }
class B { b: Int = 2 }
class C extends A, B { c: Int = 3 }
my $c = C()
p $c->a . "," . $c->b . "," . $c->c  # "1,2,3"

Abstract classes

Cannot be instantiated directly. Subclasses must implement abstract methods.

abstract class Shape {
    name: Str
    fn describe { "Shape: " . $self->name }
}

# Shape(name => "x")  โ†’ ERROR: cannot instantiate abstract class

class Circle extends Shape { radius: Int }
my $c = Circle(name => "ring", radius => 5)
p $c->describe()                      # "Shape: ring"

Final classes and methods

final class prevents subclassing. final fn prevents method override in subclasses. Both checked at declaration time.

final class Config { value: Int = 42 }
# class Bad extends Config { }  โ†’ ERROR: cannot extend final class

class Base {
    final fn id { 42 }
    fn label { "base" }                # can be overridden
}
class Child extends Base { }
my $c = Child()
p $c->id()                            # 42 (inherited, cannot override)

Visibility (pub / prot / priv)

Applies to both fields and methods. Runtime enforcement on access.

class Secret {
    pub visible: Int = 1               # public (default)
    priv hidden: Int = 42              # own class only
    prot internal: Int = 99            # class + subclasses

    fn get_hidden { $self->hidden }    # internal access ok
}
class Child extends Secret {
    fn get_internal { $self->internal } # prot: ok from subclass
}
my $s = Secret()
p $s->get_hidden()                    # 42
# $s->hidden  โ†’ ERROR: private field

my $c = Child()
p $c->get_internal()                  # 99
# $c->internal  โ†’ ERROR: protected field (outside class hierarchy)

Static fields

Shared across all instances. Access via ClassName::field() (getter) / ClassName::field(value) (setter).

class Tracker {
    static total: Int = 0
    name: Str
    fn BUILD { Tracker::total(Tracker::total() + 1) }
}
my $a = Tracker(name => "a")
my $b = Tracker(name => "b")
p Tracker::total()                     # 2

BUILD / DESTROY hooks (custom constructor / destructor)

fn BUILD is the custom-constructor hook โ€” runs automatically after field init, parent first then child. Use it for invariant checks, default-injection of computed fields, or post-init wiring. fn DESTROY is the destructor โ€” runs automatically when the object is reaped, child first then parent. Both are method-name conventions the runtime auto-dispatches; no caller-side init step.

class Connection {
    host: Str
    port: Int = 5432
    socket: Any = undef
    connected_at: Float = 0

    # Custom constructor โ€” auto-called after fields populate. Open the
    # socket, stamp the connect time, validate inputs.
    fn BUILD {
        die "host required" unless length $self->host
        $self->socket(open_tcp($self->host, $self->port))
        $self->connected_at(time())
    }

    # Destructor โ€” auto-called on object reap. Close the socket.
    fn DESTROY {
        close_tcp($self->socket) if defined $self->socket
    }
}

my $c = Connection(host => "db.example.com")   # BUILD fires
# ... use $c ...
# DESTROY fires when $c goes out of scope

Inheritance ordering โ€” parent BUILD first so child code can see parent invariants; child DESTROY first so subclass state unwinds before parent state:

class Base {
    log: Str = ""
    fn BUILD   { $self->log("base") }
    fn DESTROY { Base::audit(Base::audit() . "base,") }
    static audit: Str = ""
}
class Child extends Base {
    fn BUILD   { $self->log($self->log . "+child") }
    fn DESTROY { Base::audit(Base::audit() . "child,") }
}

my $c = Child()
p $c->log                             # "base+child"   (parent BUILD ran first)
$c->destroy()
p Base::audit()                       # "child,base,"  (child DESTROY ran first)

Runnable demo: examples/build_destroy.stk.

Traits

Interface contracts with required and default methods. Enforced at class declaration.

trait Loggable {
    fn log_prefix { "LOG" }            # default (optional to implement)
    fn log_msg                         # required (no body)
}

class Event impl Loggable {
    msg: Str
    fn log_msg { $self->msg }
}
my $e = Event(msg => "hello")
p $e->log_msg()                       # "hello"
p $e->does("Loggable")               # 1

# Missing required method โ†’ compile-time error
# class Bad impl Loggable { }  โ†’ ERROR: missing required method 'log_msg'

Late static binding (static::)

Resolves to the runtime class of $self, unlike SUPER:: which is compile-time.

class Base {
    fn class_name { static::identify() }
    fn identify { "Base" }
}
class Child extends Base {
    fn identify { "Child" }
}
my $c = Child()
p $c->class_name()                    # "Child" (not "Base")

Operator overloading

Define op_add, op_sub, op_mul, op_div, op_mod, op_pow, op_eq, op_ne, op_lt, op_gt, op_le, op_ge, op_spaceship, op_neg, op_bool, op_abs, op_concat, stringify, and more.

class Vec2 {
    x: Int
    y: Int
    fn op_add($other) {
        Vec2(x => $self->x + $other->x, y => $self->y + $other->y)
    }
    fn op_neg { Vec2(x => -$self->x, y => -$self->y) }
    fn stringify { "(" . $self->x . "," . $self->y . ")" }
}
my $a = Vec2(x => 1, y => 2)
my $b = Vec2(x => 3, y => 4)
my $c = $a + $b
p "$c"                                 # "(4,6)"
my $d = -$a
p "$d"                                 # "(-1,-2)"

Reflection / introspection

class Animal { name: Str; fn speak { "..." }; fn eat { "nom" } }
class Dog extends Animal { breed: Str }
my $d = Dog(name => "Rex", breed => "Lab")

p join(",", $d->fields())             # "name,breed"
p join(",", $d->methods())            # "speak,eat"  (inherited)
p join(",", $d->superclass())         # "Animal"
p $d->isa("Dog")                      # 1
p $d->isa("Animal")                   # 1  (inherited)
p $d->isa("Cat")                      # ""  (false)

Built-in instance methods

MethodDescription
$obj->fields()List of all field names (including inherited)
$obj->methods()List of all method names (including inherited)
$obj->superclass()List of parent class names
$obj->isa("Class")Checks inheritance chain
$obj->does("Trait")Checks trait implementation
$obj->clone()Deep copy (independent instance)
$obj->with(k => v)Functional update โ€” returns new instance with changed fields
$obj->to_hash()Convert to hash reference
$obj->destroy()Explicit destructor call (triggers DESTROY chain)
"$obj"Stringify โ€” Class(field => val, ...) or custom stringify

Field types

Runtime validation on setter. Float accepts both int and float. Custom types (struct/enum names) also work.

class Typed {
    count: Int               # integer
    name: Str                # string
    ratio: Float             # int or float
    items: Array             # array reference
    map: Hash                # hash reference
    any_val                  # untyped (Any)
}

Extensions beyond stock Perl 5

  • Native data โ€” CSV (csv_read/csv_write), columnar dataframe, embedded sqlite, TOML/YAML helpers.
  • HTTP โ€” fetch / fetch_json / fetch_async / par_fetch.
  • Crypto / compression โ€” sha256, hmac_sha256, jwt_encode/decode, gzip / gunzip / zstd.
  • Standalone binaries โ€” s build SCRIPT -o OUT bakes a script into a self-contained executable.
  • Inline Rust FFI โ€” rust { pub extern "C" fn ... } blocks compile to a cdylib on first run, dlopen + register as Perl-callable subs.
  • Bytecode cache โ€” single rkyv shard at ~/.stryke/scripts.rkyv, mmap + zero-copy ArchivedHashMap lookup, skips lex/parse/compile on warm starts. 11x faster per-process than the old SQLite cache. stryke test FILE participates in the same cache so test reruns skip compile too. Disable with STRYKE_CACHE=0. Migration story & benchmarks โ†’
  • Interactive REPL โ€” reedline-based, line history at ~/.stryke/history, columnar tab completion for keywords / lexicals / sub names / arrow methods on blessed objects. Edit mode (emacs/vi) configured via ~/.stryke/config.toml [repl] mode = "vi" or per-session via STRYKE_REPL_MODE=vi. The config file is auto-seeded on first launch with all keys commented (documents the schema; default behavior unchanged until you uncomment).
  • Distributed compute โ€” cluster([...]) builds an SSH worker pool; pmap_on $cluster { } @list fans an element-wise map across persistent remote workers; ~d> on $cluster SRC stages ships chunks of SRC to remote slots and runs the full stage chain per chunk (the ~p> surface, distributed). Both share the same dispatcher: ssh-per-slot handshake once, JOB frames per item, per-job retry, order-preserving collect.
  • Shared state โ€” mysync declares atomic variables safe to mutate from parallel workers.
  • Mutex / semaphore primitives โ€” mutex() and semaphore($n) return blocking sync primitives backed by parking_lot::Mutex + Condvar (no busy-wait). Pair with defer { mutex_unlock($m) } for RAII across spawn / fan / ~p> workers. Surface: mutex_lock, mutex_unlock, mutex_try_lock, mutex_is_locked; semaphore_acquire, semaphore_release, semaphore_try_acquire, semaphore_permits, semaphore_limit (all with sem_* aliases). For cross-process locking, flock($fh, OP) calls the POSIX advisory-lock syscall directly (LOCK_SH=1, LOCK_EX=2, LOCK_NB|LOCK_EX=4, LOCK_UN=8).
  • Language server โ€” s lsp (or s --lsp) runs an LSP server over stdio with diagnostics, hover, completion. The nine reflection hashes above are part of the completion surface.

Language server (stryke --lsp)

One binary, two roles: stryke runs scripts AND speaks LSP over stdio. Wire it into any LSP-aware editor โ€” VS Code, JetBrains, Neovim, Helix โ€” by pointing the client at stryke --lsp. No separate stryke-lsp binary to maintain.

Diagnostics

Strict-vars is on by default in the IDE (CLI stryke check stays lenient โ€” gated on explicit use strict;), so undefined $scalar / @array / %hash typos surface inline without per-file opt-in. Sensible exemptions baked in:

  • Bare sigil-vars inside double-quoted string interpolations ("got $fh ..." is template/description text โ€” no false positives on test descriptions).
  • Perl $^X / $^O / $^V / $^W family special vars, plus bare $$ (process id).
  • AOP advice-body context vars: $INTERCEPT_NAME, @INTERCEPT_ARGS, $INTERCEPT_RESULT, $INTERCEPT_MS, $INTERCEPT_US.
  • The full block-param grammar: _, _N, _<, _<N, _<<<<<, _N<<<<<, _N<M, plus all sigiled forms ($_, $_<3, $_<<<<<, โ€ฆ).
  • $#arr last-index references resolve via @arr (inside strings too โ€” "got $#arr items" is OK).
  • open(my $fh, ...) lexical filehandle declarations register $fh in scope.
  • exists(&Pkg::sub) introspection skips the sub-defined check โ€” the point is to ask whether the sub exists.
  • Parser-internal _thread_par_run desugaring targets emitted by ~p> / ~s> thread macros.

OOP-aware checks

  • Constructor key validation. Class(field => v, ...) and Class->new(...) validate keys against declared fields. Walks extends parents and impl traits โ€” Dog extends Animal accepts Dog(name => "Rex", breed => "Lab") where name is on Animal.
  • Positional vs keyed auto-detection. Task(1, "title", Priority::High) is recognized as positional (first arg isn't a field name) and skipped โ€” only fat-comma-style calls get the key check.
  • Method lookup walks the inheritance chain. $self->method / $obj->method resolve via the full extends + impl graph, BFS-style, cycle-guarded. class Person impl Greetable with fn greeting on the trait resolves $p->greeting() correctly.
  • Universal-method whitelist โ€” isa, can, DOES, does, VERSION, new, BUILD, DESTROY, clone, with, to_hash, to_hash_rec, to_hash_deep, fields, methods, superclass never flag (runtime provides them on every instance).
  • Match-arm enum-variant typos flagged. Sig::Term2 => when Sig has variants Hup, Int, Term, Kill emits "no variant Term2 on Sig; available: Hup, Int, Kill, Term".

Cross-file require

require "./lib/Foo/Bar.stk" walks up from the source file looking for the project root (any ancestor with a sibling lib/ directory) โ€” the classic Perl/CPAN layout. Subs, classes, traits, enums, constants declared in required files all join the active completion + diagnostics index. Falls back to the source file's own directory for simple sibling-script cases.

Completion

Every identifier category the editor expects:

  • Sigil variables โ€” declared via my / our / state / local / mysync / oursync, foreach my $x, sub signature params, open(my $fh, โ€ฆ) filehandle decls.
  • Subs โ€” bare and qualified, with suffix-only insertText for qualified completions (typing Demo::โ”‚ produces Demo::handle, not Demo::Demo::handle).
  • User-declared Types โ€” classes (CompletionItemKind::CLASS), structs (STRUCT), enums (ENUM), traits (INTERFACE), plus every enum variant as a qualified EnumName::Variant.
  • Constants from use constant NAME => โ€ฆ and the use constant { A => 1, B => 2 } hash form.
  • Loop labels for last LOOP / next LOOP / redo LOOP.
  • Hash-key completion driven by builtin return schemas. my $info = pool_info(); $info->{cursor} lists the actual keys pool_info returns (cpus, rayon_threads, arch, os, perf_cores, eff_cores). Same registry covers par_bench, stress_test, cache_stats, uname, audio_info, id3_read, git_log, git_show, git_status, git_branches, git_blame, git_authors, du_tree, process_list, net_interfaces, perfview, mounts, html_parse, css_select, xml_parse, xpath. foreach my $row (git_log()) { $row->{cursor} } also resolves through the loop-var binding.
  • Stryke keywords (fn, class, struct, enum, trait, match, mysync, frozen, โ€ฆ) and ~10k builtins from %all.
  • In-progress parse recovery โ€” cursor inside a fragment that breaks the parse (Demo::โ”‚) retries with the cursor's line blanked so the rest of the file still indexes.
  • Trigger characters include { so $h->{ / $h{ auto-popup the relevant key set.

Hover

Full markdown cards for every builtin, special variable, AOP keyword, phase block, and reflection-hash alias. Hash-returning builtins ship full key tables โ€” pool_info()'s hover lists every key with type + meaning. Hover is suppressed inside string literals ("length" in a string doesn't pop the length builtin doc) but still fires inside #{ EXPR } interpolations (real code).

Refactor

Goto / References / Rename โ€” package-aware. Rename of struct/class/enum/trait fields and methods is AST-based with no textual fallback, so my %h = (width => 1) is never mistaken for a field reference when renaming width. The server defensively strips a :: qualifier from newName so clients that send the full prefilled identifier still produce the correct bare replacement. Cross-file rename walks the require graph BFS-style.

Extract Variable / Constant / Parameter / Function (Cmd-Opt-V / -C / -P / -M), Wrap-in-p, toggle line comment. Extract works on caret-only (no manual selection needed) and inside double-quoted strings / backticks.

JetBrains plugin (editors/intellij/)

First-class plugin for RustRover, IDEA Ultimate, GoLand, PyCharm Pro, WebStorm, RubyMine, PhpStorm, CLion, Rider, DataGrip, Aqua. .stk file association, 48-slot color scheme, hand-rolled lexer with finer-grained token categories (declaration vs control keywords, sigil variants, topic / block-param / special-var splits, pipe / arrow / range / regex-bind operators). The lexer correctly handles:

  • Every Perl regex-operator form as one atomic REGEX token โ€” s/PATTERN/REPL/FLAGS, tr/.../.../, y/.../.../, m/.../, qr/.../ โ€” including embedded quote chars (s/"/""/g), paired-bracket delimiters (s{foo}{bar}), and mixed delimiters.
  • Keyword spellings used as method names (fn state, fn transition) tokenize as FUNCTION_DECL, not the matching control/decl keyword.
  • Keyword spellings used as hash keys ($h->{state}, state => 1) tokenize as IDENTIFIER.
  • Perl-style @{[ EXPR ]} array-ref interpolations inside double-quoted strings tokenize the interior as code.
  • The full block-param grammar (_, _<, _<2, _<<<<<, $_<3, $_2<<<) lexes as single BLOCK_PARAM tokens.
  • <<TAG heredocs after a closing } (block close) correctly tokenize as HereDoc, not ShiftLeft. Numeric/sigil RHS still hits ShiftLeft so 1 << 4 and $x << $shift still work.

LSP client wired to stryke --lsp โ€” completion, hover, goto / refs / rename, semantic tokens, signature help, code actions, folding ranges, document formatting (Cmd-Opt-L), diagnostics. Rename dialog prefills with the bare segment (no ::-walking) so editing Red โ†’ Redgg doesn't accidentally qualify the result as TrafficLight::Redgg. Run configurations with --no-interop / --disasm / --profile / --flame / -d toggles. Debugger over DAP. Reflection tool window with searchable trees of every reflection hash. Build with ./gradlew buildPlugin and install the zip from editors/intellij/build/distributions/.

CLI flags (common)

-e CODE                // one-line program (multiple -e's allowed)
-E CODE                // like -e, but enables all optional features
-c                     // syntax check only
--lint / --check       // parse + compile bytecode without running
--disasm               // print bytecode disassembly before VM run
--ast                  // dump parsed AST as JSON and exit
--fmt                  // pretty-print parsed Perl to stdout and exit
--profile              // wall-clock profile stderr (flamegraph-ready)
--flame                // flamegraph: terminal bars (TTY) or SVG
--no-jit               // disable Cranelift JIT (bytecode interpreter only)
--compat               // Perl 5 strict-compat: disable all stryke extensions
-n / -p / -i           // stdin line-mode + in-place edit
-j N                   // parallel threads for multi-file execution
--script               // force script lookup when name conflicts with builtin
-d -- SCRIPT           // TTY debugger (perl -d style REPL on stdin/stderr)
--dap                  // DAP server over stdio (Debug Adapter Protocol)
--dap HOST:PORT        // DAP server over TCP (JetBrains plugin transport)
--lsp                  // LSP server over stdio (semantic tokens, hover, ...)

// Subcommands
s build SCRIPT -o OUT  // AOT compile to standalone binary
s build --project DIR  // bundle project (main.stk + lib/) into binary
s check FILE...        // parse + compile without executing (CI/editor)
s disasm FILE          // disassemble bytecode
s profile FILE         // run with profiling (--flame for SVG)
s fmt -i FILE...       // format source files in place
s bench                // run benchmarks from bench/ or benches/
s test                 // run tests from t/
s init NAME            // scaffold new project (lib/, bench/, t/)
s repl --load FILE     // REPL with pre-loaded file
s lsp                  // language server over stdio
s completions zsh      // emit shell completions
s ast FILE             // dump AST as JSON
s prun FILE...         // run multiple files in parallel
s convert FILE...      // convert Perl to stryke syntax
s deconvert FILE...    // convert stryke back to Perl
s --remote-worker      // worker process for distributed cluster

Command Resolution Hierarchy

When you run stryke ARG, resolution follows this order:

  1. Subcommand โ€” build, docs, fmt, test, bench, init, repl, run, serve, check, disasm, profile, lsp, ast, prun, convert, deconvert, completions, agent, controller
  2. Builtin function โ€” if ARG is a known builtin AND no file named ARG exists, call ARG(@ARGV) and print result
  3. Script file โ€” if file exists, run as script
  4. Inline code โ€” if ARG looks like code (contains operators, parens, etc.), execute as one-liner

Use --script to force script lookup when a builtin name conflicts with a script file.

# Call builtins directly from CLI
s pin                       # calls pin() โ€” 100% TDP forever
s heat 60                   # calls heat(60) โ€” 100% TDP for 60s
s basename /foo/bar/baz.txt # calls basename(@ARGV) โ†’ "baz.txt"
s dirname /foo/bar/baz.txt  # calls dirname(@ARGV) โ†’ "/foo/bar"
s gethostname               # calls gethostname() โ†’ hostname
s avg 1 2 3 4 5             # calls avg(@ARGV) โ†’ 3

# Aliases work too
s bn /foo/bar/baz.txt       # basename alias
s dn /foo/bar/baz.txt       # dirname alias
s hn                        # gethostname alias
s faf                       # fire_and_forget alias (same as pin)

# Force script when name conflicts
s --script basename         # run ./basename script, not builtin

Editor tooling โ€” LSP, DAP debugger, JetBrains plugin

The same stryke binary that runs scripts also serves Language Server Protocol and Debug Adapter Protocol โ€” no separate stryke-lsp / stryke-dap binaries to install. Editors point their LSP/DAP clients at stryke --lsp / stryke --dap and get hover docs, completion, semantic tokens, line breakpoints, step over/into/out, and recursive variable drill-down.

LSP โ€” stryke --lsp

  • Hover over any builtin โ†’ category + signature + description from %stryke::descriptions / %stryke::categories (4,255 documented names)
  • Completion with full builtin set (every spelling in %all โ€” 11,191 keys including 643 aliases)
  • Semantic tokens โ€” keywords, builtins, sigils, types, regex flags get distinct token categories so themes can paint them independently
  • Signature help on function-call cursor
  • Code actions โ€” server-side fix suggestions for common diagnostics
  • Go-to-definition for user subs + classes + structs + enums

DAP debugger โ€” stryke --dap

Standard Debug Adapter Protocol over stdio or TCP. Same Debugger state machine the TTY REPL (stryke -d) uses โ€” breakpoints, step modes, scope inspection, call-stack tracking all shared. Just two front-ends on one core.

  • Line breakpoints โ€” gutter clicks fire on the exact source line; lexer-line drift fixed so class declarations don't poison subsequent BPs
  • Function breakpoints โ€” stop on entry to any named sub
  • Step over / step into / step out โ€” depth-tracking on every UDF entry/exit so step-over stays in the current frame instead of diving into call sites
  • Pause / Continue / Run-to-Cursor
  • Recursive Variables panel โ€” every hash, array, struct, enum variant, class instance, and sketch (TDigestSketch, BloomFilter, HllSketch, CmsSketch, TopKSketch) expands inline. Class fields show visibility markers (+pub, #protected, -priv) plus the __class / __isa chain
  • Magic-block-param sort โ€” user my $foo vars first, then topic family ($_, $_0, $_1, โ€ฆ), then $a/$b, then runtime builtins. Compiler-synthetic __foreach_i__ etc. hidden
  • Evaluate dialog with scalar prelude injection so $a * $b returns the right value from the paused frame
  • Console output โ€” p / print / say stream into the IDE Console in real-time (forced $| = 1 autoflush in DAP mode)

JetBrains plugin โ€” editors/intellij/

First-class JVM plugin for the entire JetBrains paid lineup: RustRover, IDEA Ultimate, GoLand, PyCharm Pro, WebStorm, RubyMine, PhpStorm, CLion, Rider, DataGrip, Aqua. Built on the IntelliJ Platform Gradle Plugin 2.16.0, Kotlin 2.0.21, JDK 17, SDK 2024.2.4 against builds 242..261.*. Community editions don't have the LSP API and won't load the plugin. Talks to the in-tree strykelang/lsp_extras.rs + strykelang/dap.rs over JSON-RPC; no upstream lsp-server / dap-types crates anywhere on the Rust side. JetBrains' own LspServerSupportProvider is the only LSP4J consumer. Compiled build/distributions/stryke-intellij-<v>.zip is self-contained: only Kotlin stdlib + IntelliJ Platform classes at runtime. Full source-of-truth feature list lives in editors/intellij/README.md.

Install

# Settings โ†’ Plugins โ†’ โš™ โ†’ Install Plugin from Diskโ€ฆ
editors/intellij/build/distributions/stryke-intellij-<version>.zip

After install: restart the IDE โ†’ open any .stk file โ†’ the LSP starts automatically โ†’ the debugger activates the first time you click Debug. The stryke (or st) binary must be on $PATH, or configured under Settings โ†’ Tools โ†’ Stryke โ†’ Stryke executable. The plugin tries st first, then stryke.

Editor surfaces

SurfaceBehavior
File association.stk (configurable; add pl / pm / etc. under Settings โ†’ Tools โ†’ Stryke โ†’ File extensions)
LexerHand-rolled in StrykeLexer.kt โ€” instant first-paint highlighting before the LSP semantic-tokens response lands
Color slots48 stable STRYKE_* TextAttributesKeys under Settings โ†’ Editor โ†’ Color Scheme โ†’ Stryke, grouped Comments / Strings / Numbers / Regex / Keywords / Names / Variables / Operators / Punctuation / Errors
Brace matching{} () [] via StrykeBraceMatcher.kt
CommentsCmd/Ctrl-/ for # line comments via StrykeCommenter.kt. POD =pod ... =cut requires column-0 markers, so the platform block-comment binding routes to multi-line # instead
Quote handler", ', `, regex /โ€ฆ/ and m{โ€ฆ} / qr// auto-pair
Smart-EnterIndent + auto-close for fn / sub / class / struct / if / while / foreach / match / try blocks
Goto declarationCmd-click / Cmd-B routed through StrykeGotoDeclarationHandler.kt over LSP

Lexer token coverage

CategoryExamples
Comments# line, ## doc, POD =pod โ€ฆ =cut
Strings"โ€ฆ", 'โ€ฆ', qw(โ€ฆ), qq(โ€ฆ), qr/โ€ฆ/, heredocs (<<EOT, <<~EOT, <<'EOT')
Numbers42, 3.14, 0xFF, 0b1010, 0o755, 1_000_000, 1e10
Regex (atomic REGEX tokens)/โ€ฆ/flags, m{โ€ฆ}flags, s/โ€ฆ/โ€ฆ/flags, tr/โ€ฆ/โ€ฆ/flags, y/โ€ฆ/โ€ฆ/, qr/โ€ฆ/ โ€” handles embedded quote chars (s/"/""/g), paired-bracket delimiters (s{foo}{bar}), mixed delimiters
Declarationsmy / our / state / use / package / frozen
Function/typefn / sub / class / struct / trait / enum
Control flowif / unless / elsif / else / while / until / for / foreach / do / match / try / given / when
Phase blocksBEGIN / END / INIT / CHECK / UNITCHECK
Word operatorsand / or / not / xor / eq / ne / lt / gt / cmp / x
Booleanstrue / false / undef
Builtinsfull canonical set from %stryke::builtins
Parallel builtinspmap / pgrep / pfor / preduce / pforks / โ€ฆ
Sigil vars$x scalar, @x array, %x hash, $! / $@ special, $_ / @_ topic, $_0 / $_1 block params
Block-param grammar_, _<, _<2, _<<<<<, $_<3, $_2<<< all lex as single BLOCK_PARAM tokens
Package pathsFoo::Bar, Foo::Bar::baz()
Operators=>, ->, ~>, |>, =~, !~, .., ..., <=>
Punctuationparens / braces / brackets / commas / semicolons split into independent color categories
Edge casesKeyword spellings used as method names (fn state) tokenize as FUNCTION_DECL, not the matching control keyword. Keyword spellings used as hash keys ($h->{state}, state => 1) tokenize as IDENTIFIER. Perl-style @{[ EXPR ]} interpolations inside double-quoted strings tokenize the interior as code. <<TAG heredocs after a closing } correctly tokenize as HereDoc, not ShiftLeft; numeric/sigil RHS still hits ShiftLeft (so 1 << 4 and $x << $shift still work).

LSP capabilities

The LSP server is in-process inside the stryke binary โ€” stryke --lsp (or st --lsp) spawns it over stdio with Content-Length-framed JSON-RPC. Hand-rolled framer on top of serde_json โ€” no lsp-server / lsp-types crates. Plugin side starts it via StrykeLspServerSupportProvider.kt; descriptor in StrykeLspServerDescriptor.kt. Optional STRYKE_LSP_LOG=<path> env var dumps every request/response for debugging.

CapabilityTrigger / scope
completiontrigger chars $ @ % : _ plus all letters; resolveProvider; 60 snippet templates keyed by prefix
hoverfull markdown cards from lsp_docs_domains.rs; category-stub fallback for any %stryke::builtins entry without a hand card; โ‰ˆ25k hover-card-backed identifiers spanning every builtin / keyword / operator / Perl5-compat / extension / parallel primitive / sketch / phase block
definition / declaration / references / documentHighlightcross-file via the server's SymbolTable
documentSymbolevery sub / fn / class / struct / enum / trait / package decl, plus top-level my / our
foldingRangeevery { โ€ฆ } block, POD =pod โ€ฆ =cut, 3+ consecutive # comment runs
rename (with prepareRename)scalars / arrays / hashes / subs / functions / types / packages; cross-file for package-scoped symbols
semanticTokens/fulltoken classes mirroring the lexer; LSP overlay refines what the hand lexer approximates
signatureHelpparameter hints with active-arg tracking
codeActionline-local quickfixes (Wrap line in p, Comment / Uncomment line) plus Extract refactorings โ€” see Code Actions below
formattingCmd/Ctrl-Opt-L pipes the document through stryke's built-in formatter (fmt::format_program): 4-space indent, normalized operator spacing, single-line rewrites for short blocks
publishDiagnosticsparse + compile errors with line/col

Completion snippets (60 templates)

Tab walks the ${1:...} placeholders to the final ${0} cursor:

FamilyPrefixes
Control flowif / ifelse / ifelsif / elsif / else / while / until / for / forrange / foreach / do / match / try / given
Declarationsmy / fn / sub / class / struct / enum / trait
Parallel primitivespmap / pmaps / pgrep / pfor / preduce / pforks / pwhile
Phase blocksBEGIN / END / INIT / CHECK
Module setupuse / strict / shebang / main
Scaffoldsweb / svg / system / git / test

Code actions

Surfaced under Alt-Enter (intentions popup). The IntelliJ Refactor menu (Ctrl-T) is reserved for native PSI-based refactorings; LSP-driven extract / inline / rewrite kinds show up in the intentions popup, not under that menu. All three Extract actions use kind: "refactor.extract" (parent kind), so they match refactor.extract.method / .variable / .constant queries via prefix.

ActionSelection shapeEdit shape
Extract to variable (Cmd-Opt-V)single-line, full-line OR sub-expressioninserts my $name = <rhs> above, replaces selection with $name
Extract to constant (Cmd-Opt-C)single-line, full-line OR sub-expressioninserts my frozen $NAME = <rhs> above (uppercase placeholder), replaces selection with $NAME
Extract to function (Cmd-Opt-M)multi-linewraps the selection in fn extracted_fn { โ€ฆ } above the block, replaces the original range with a call. v1 does no free-variable analysis.
Wrap line in pcaret on linewraps the current statement in p(โ€ฆ) for quick print debugging
Comment / Uncomment linecaret on linetoggles a # prefix on the current line

Reflection tool window (9 tabs)

View โ†’ Tool Windows โ†’ Stryke (right edge). Fed live from stryke -e 'p tj({%stryke::*})' on first open (โ‰ˆ25k entries):

TabSource hashNotes
All %allmerged unionone tree spanning every category
Builtins %b%stryke::builtinsevery builtin grouped by category (math, IO, string, regex, โ€ฆ)
Keywords %k%stryke::keywordsreserved words + control-flow keywords
Operators %o%stryke::operatorsarithmetic / logical / regex-bind / arrow / range / list
Special vars %v%stryke::special_vars$_, $@, $!, @ARGV, %ENV, @INC, $0, $$, $0..$9 regex captures
Perl5 %pc%stryke::perl_compatsevery name stryke supports for Perl5 compatibility
Extensions %e%stryke::extensionsstryke-only additions (parallel primitives, sketches, async / trace / timer / aop, rust FFI)
Aliases %a%stryke::aliasesalias โ†’ canonical name
Descriptions %d%stryke::descriptionsname โ†’ one-line summary

Each tab is a tree grouped by category, with a per-tab search field filtering across name + category.

InteractionEffect
Left-click on leafAnchored docs popup. Renders stryke docs NAME with ANSI colors decoded via IntelliJ's AnsiEscapeDecoder + ConsoleView โ€” same body as a terminal stryke docs <name> lookup.
Right-click on leafContext menu: Show Docs + Copy Name
Toolbar โ†’ RefreshRe-runs stryke -e 'p tj({%stryke::*})' and reloads every tab
Toolbar โ†’ SettingsOpens Settings โ†’ Tools โ†’ Stryke

Run configurations

SurfaceBehavior
Run config (StrykeRunConfigurationType)toggles for --no-interop / --disasm / --profile / --flame / -d / -D; working directory + script args + interpreter args
Context menuRun with stryke on any .stk file in the editor or project view; auto-creates a config
ProducerStrykeRunConfigurationProducer materializes a run config from the active file
OutputStandard ConsoleView โ€” p / print / printf / say stream in real time
File โ†’ New โ†’ Stryke FileStandard New-File dialog; pick Script (shebanged, fn main stub), Library / module, or Empty. Same entry surfaces in the Project-view right-click New submenu.

DAP debugger

DAP-backed, over a loopback TCP socket. Plugin spawns st --dap 127.0.0.1:<port>; stryke connects back. TCP transport keeps OSProcessHandler stdout/stderr free for the program's own p/print output.

FeatureNotes
Line breakpointsGutter toggle / enable / disable; persistent across sessions
Function breakpointsRun โ†’ View Breakpoints โ†’ +
SteppingContinue / Step Over / Step Into / Step Out / Pause / Run to Cursor โ€” standard XDebugger actions
Framesfile:line per frame, click to navigate source
Variables panelThree-tier sort โ€” user my vars on top, magic block params ($_, $_0, $_1, โ€ฆ, $a, $b) middle, stryke built-ins ($stryke::VERSION, %ENV, %term, @INC, โ€ฆ) bottom. __synthetic__ compiler internals hidden.
Recursive expansion[N] (key => val, โ€ฆ) summary with disclosure triangles, drill in to key = value rows; depth-capped at 12, count-capped at 2000
Rich drill-downStructInstance / ClassInstance (one row per field, +/#/- visibility markers, __class + __isa metadata); EnumInstance (variant + carried data); Set (Set(N) {a, b, c}); every sketch type (TDigestSketch โ†’ count/min/max/mean/sum/p50-p99/compression; BloomFilter โ†’ inserted/bit_count/k/fpr; HllSketch / CmsSketch / TopKSketch)
Evaluate dialogPure expressions (55 + 3, sqrt(2), len(@INC)) plus expressions using the current frame's scalars ($a * $b) via prelude injection into a st -e subprocess
ConsoleProgram p / print / printf / say output in real time (autoflush + flush-on-pause)
Two debuggersst -d file.stk (TTY REPL, perl -d style) and st --dap HOST:PORT (consumed by this plugin) share one Debugger state machine

DAP protocol โ€” plugin and server flow

Plugin side (com.menketechnologies.stryke.dap):

  1. StrykeDebugRunner.doExecute opens a ServerSocket(0) on 127.0.0.1, captures the port.
  2. Spawns st --dap 127.0.0.1:<port> via KillableColoredProcessHandler โ€” OSProcessHandler keeps the process's stdio for Console output, exclusively.
  3. Waits up to 10 s for stryke to connect back, then runs DAP over that socket.
  4. Creates an XDebugSession via XDebuggerManager.startSession and returns the descriptor via getMockRunContentDescriptorIfInitialized reflection โ€” bypasses the platform's split-debugger Logger.error("[Split debugger] โ€ฆ") toast that the deprecated runContentDescriptor getter fires.
  5. StrykeDebugProcess.createConsole builds a ConsoleView and attachToProcess(processHandler) so program stdout streams in real time.
  6. StrykeDapClient reads Content-Length-framed JSON-RPC from the socket โ€” byte-based, not char-based โ€” so multi-byte UTF-8 in variable reprs doesn't desync framing.
  7. On stopped event, onStopped synchronously fetches stackTrace + scopes + variables, builds StrykeStackFrame objects with pre-populated children, calls session.positionReached. No async expansion on the UI thread โ€” IntelliJ 2026.1's split-debugger drops those.
  8. StrykeEvaluator sends evaluate requests for the Evaluate dialog; stryke does scalar-prelude injection so $a * $b returns the right value from the paused frame.

Stryke side (strykelang/dap.rs + strykelang/debugger.rs):

  • Debugger state machine (breakpoints, step modes, call depth) shared between TTY and DAP front-ends. Step-over depends on enter_sub / leave_sub being called at every VM call dispatch site so call_depth matches the program's logical call stack โ€” without these hooks step-over drops into UDFs instead of skipping them.
  • Same-line guard tracks both last_stop_line and last_stop_depth. Without the depth half, step-in fires on the same source line as the call site (first opcode of the call setup has the same line as my $r = foo()), requiring two clicks to actually enter foo.
  • set_topic for implicit for (@arr) { โ€ฆ } loops so $_ / $_0 / _ / _0 all alias.
  • Snapshot capture (capture_locals_with_map) walks the scope, builds per-variable refs for hashes / arrays / structs / classes / enums / sets / sketches, recursively expanding their children into a var_ref_map (depth 12, count 2000) so the DAP variables request resolves any ref to its rows.
  • stdout/stderr autoflush + flush on every pause so output lands in the Console before the suspend UI takes over.

Refactor / Rename

Shift-F6 on any of these identifiers renames it across the workspace via textDocument/rename:

  • Scalar / array / hash variables ($x, @xs, %h) โ€” the sigil is included in the identifier extraction, so $pass and the pass builtin no longer collide.
  • my / state / our declarations (and the frozen my constant form).
  • Subroutine / function declarations (fn, sub).
  • Struct / class / enum / trait names and their constructor / method call sites (Point->new, Color::Red).
  • Package names declared via package Foo::Bar;.

Cross-file rename fires when the symbol is package-scoped (sub, type, our, package). The server scans every other open document, finds exact-name matches in its SymbolTable, and falls back to a textual qualified-name scan for files that reference the symbol without re-declaring it. Locally-scoped my / state decls and sub parameters are file-scoped and never cross files. Rename of struct/class/enum/trait fields and methods is AST-based with no textual fallback, so my %h = (width => 1) is never mistaken for a field reference when renaming width. The server defensively strips a :: qualifier from newName so clients that send the full prefilled identifier still produce the correct bare replacement. Hovering on the format key in $opts{format} or the exec selector in $db->exec does NOT show the format / exec builtin card โ€” those are hash keys / method selectors, not builtin references. Implementation: StrykeRenameHandler.kt + strykelang/lsp_extras.rs::rename.

Configuration โ€” Settings โ†’ Tools โ†’ Stryke

SectionSettingDefaultNotes
InterpreterStryke executablefirst st then stryke on $PATHabsolute path or blank
LSPEnable LSPonmaster toggle
LSPExtra LSP argsemptypassed after --lsp
LSPLSP environmentemptyKEY=VAL pairs (e.g. STRYKE_LOG_LEVEL=debug)
LSPAuto-restart LSP on settings changeonrestart picks up new env
LSPShow builtin hoversonserver-provided cards
LSPLog LSP traffic to fileoffsets STRYKE_LSP_LOG=<path>
EditorDisable lexer highlightingoffrely only on LSP semantic tokens
EditorFile extensionsstkcomma-separated; add pl / pm to enable for Perl files
Run configsDefault --no-interopoffstrict stryke parser by default

Logs

Two append-only logs, both under ~/.stryke/ (or $STRYKE_HOME/ when set):

FileSourceContents
~/.stryke/stryke-plugin.logKotlin (plugin)LSP command line built, DAP send / receive (seq + command + bytes), rename / semantic-token routing, breakpoint handler steps
~/.stryke/stryke.logRust (stryke --lsp / --dap)Levelled events (TRACE / DEBUG / INFO / WARN / ERROR) โ€” startup, initialize, every request method (TRACE), didOpen / Change / Close + diagnostics (DEBUG), rename / hover outcomes, DAP launch / breakpoints / step / pause / disconnect, milestone events (stopped / terminated / exited)

Tail both with tail -f ~/.stryke/stryke.log ~/.stryke/stryke-plugin.log. Server-side level controlled by $STRYKE_LOG_LEVEL (trace / debug / info default / warn / error, case-insensitive). Rotation: STRYKE_LOG_MAX_BYTES (default 5242880 = 5 MiB; 0 disables) + STRYKE_LOG_MAX_FILES (default 5). When the active log hits the cap, stryke.log.N-1 shifts to stryke.log.N (eldest overwritten), then active moves to stryke.log.1. Default bounds the log dir at โ‰ˆ(N+1) ร— MAX_BYTES = ~30 MiB. $STRYKE_LOG_FILE=/abs/path/to.log overrides the resolved path entirely (used by tests + sandbox runs). Per-IDE chatter still goes to idea.log via Logger.getInstance(...).

Building

cd editors/intellij
./gradlew buildPlugin             # โ†’ build/distributions/stryke-intellij-<v>.zip
./gradlew runIde                  # launches a sandbox IDE with the plugin installed
./gradlew verifyPlugin            # plugin verifier against recommended IDE matrix
./gradlew test                    # StrykeLexerTest + StrykeColorSettingsPageTest + StrykeSmartEnterProcessorTest

First build downloads the IntelliJ Platform SDK (~1 GB), takes a few minutes, and is cached under editors/intellij/.intellijPlatform/ (gitignored).

Plugin architecture

editors/intellij/
โ”œโ”€โ”€ build.gradle.kts                   # IntelliJ Platform Gradle Plugin 2.16
โ”œโ”€โ”€ gradle.properties                  # platform version, plugin version, JVM
โ”œโ”€โ”€ settings.gradle.kts
โ””โ”€โ”€ src/main/
    โ”œโ”€โ”€ kotlin/com/menketechnologies/stryke/
    โ”‚   โ”œโ”€โ”€ StrykeLanguage.kt          # Language singleton
    โ”‚   โ”œโ”€โ”€ StrykeFileType.kt          # .stk โ†’ Stryke
    โ”‚   โ”œโ”€โ”€ StrykeIcons.kt             # icon loader
    โ”‚   โ”œโ”€โ”€ StrykeColors.kt            # 48 STRYKE_* TextAttributesKey constants
    โ”‚   โ”œโ”€โ”€ StrykeTokenTypes.kt        # token type enum
    โ”‚   โ”œโ”€โ”€ StrykeLexer.kt             # hand-rolled lexer
    โ”‚   โ”œโ”€โ”€ StrykeSyntaxHighlighter.kt # token โ†’ color mapping
    โ”‚   โ”œโ”€โ”€ StrykeColorSettingsPage.kt # IDE color-scheme entries
    โ”‚   โ”œโ”€โ”€ StrykeBraceMatcher.kt      # {} () []
    โ”‚   โ”œโ”€โ”€ StrykeCommenter.kt         # `#` line comments
    โ”‚   โ”œโ”€โ”€ StrykeQuoteHandler.kt      # " ' ` / m{} qr// auto-pair
    โ”‚   โ”œโ”€โ”€ StrykeSmartEnterProcessor.kt # indent + auto-close blocks
    โ”‚   โ”œโ”€โ”€ StrykeParserDefinition.kt
    โ”‚   โ”œโ”€โ”€ StrykeSettings.kt          # persistent settings
    โ”‚   โ”œโ”€โ”€ StrykeSettingsConfigurable.kt
    โ”‚   โ”œโ”€โ”€ StrykeDebugLog.kt          # plugin-side log writer
    โ”‚   โ”œโ”€โ”€ lsp/
    โ”‚   โ”‚   โ”œโ”€โ”€ StrykeLspServerSupportProvider.kt
    โ”‚   โ”‚   โ””โ”€โ”€ StrykeLspServerDescriptor.kt
    โ”‚   โ”œโ”€โ”€ refactor/
    โ”‚   โ”‚   โ”œโ”€โ”€ StrykeRefactoringSupportProvider.kt   # Extract Method/Var/Const routing
    โ”‚   โ”‚   โ””โ”€โ”€ StrykeRenameHandler.kt
    โ”‚   โ”œโ”€โ”€ navigate/
    โ”‚   โ”‚   โ””โ”€โ”€ StrykeGotoDeclarationHandler.kt       # Cmd-click + Cmd-B
    โ”‚   โ”œโ”€โ”€ actions/
    โ”‚   โ”‚   โ”œโ”€โ”€ RunStrykeFileAction.kt
    โ”‚   โ”‚   โ””โ”€โ”€ CreateStrykeFileAction.kt              # File โ†’ New โ†’ Stryke File
    โ”‚   โ”œโ”€โ”€ run/
    โ”‚   โ”‚   โ”œโ”€โ”€ StrykeRunConfigurationType.kt
    โ”‚   โ”‚   โ”œโ”€โ”€ StrykeRunConfigurationOptions.kt
    โ”‚   โ”‚   โ”œโ”€โ”€ StrykeRunConfiguration.kt
    โ”‚   โ”‚   โ”œโ”€โ”€ StrykeRunConfigurationEditor.kt
    โ”‚   โ”‚   โ”œโ”€โ”€ StrykeRunConfigurationProducer.kt
    โ”‚   โ”‚   โ”œโ”€โ”€ StrykeProgramRunner.kt    # Run executor
    โ”‚   โ”‚   โ””โ”€โ”€ StrykeDebugRunner.kt      # Debug executor (DAP)
    โ”‚   โ”œโ”€โ”€ dap/
    โ”‚   โ”‚   โ”œโ”€โ”€ StrykeDapClient.kt        # byte-based DAP protocol client
    โ”‚   โ”‚   โ”œโ”€โ”€ StrykeDebugProcess.kt     # XDebugProcess
    โ”‚   โ”‚   โ”œโ”€โ”€ StrykeDebuggerEditorsProvider.kt
    โ”‚   โ”‚   โ”œโ”€โ”€ StrykeBreakpointType.kt   # xdebugger.breakpointType
    โ”‚   โ”‚   โ”œโ”€โ”€ StrykeBreakpointHandler.kt
    โ”‚   โ”‚   โ”œโ”€โ”€ StrykeStackFrame.kt
    โ”‚   โ”‚   โ”œโ”€โ”€ StrykeSuspendContext.kt
    โ”‚   โ”‚   โ”œโ”€โ”€ StrykeValue.kt            # XValue with recursive children
    โ”‚   โ”‚   โ””โ”€โ”€ StrykeEvaluator.kt        # Evaluate dialog backend
    โ”‚   โ””โ”€โ”€ toolwindow/
    โ”‚       โ””โ”€โ”€ StrykeReflectionToolWindow.kt
    โ””โ”€โ”€ resources/
        โ”œโ”€โ”€ META-INF/plugin.xml
        โ””โ”€โ”€ icons/stryke.svg

Rust-side counterparts:

ModulePurpose
strykelang/lsp_extras.rsLSP server (stryke --lsp) โ€” hover, completion, codeAction, rename, semanticTokens, foldingRange, signatureHelp, diagnostics, formatting
strykelang/dap.rs + strykelang/debugger.rsDAP server (stryke --dap HOST:PORT) โ€” breakpoints, stepping, scopes, variables (recursive), evaluate
strykelang/lsp_docs_domains.rsHover-card bodies grouped by domain (math, IO, string, regex, parallel, sketches, โ€ฆ)
strykelang/fmt.rsFormatter (stryke --fmt) โ€” same engine textDocument/formatting invokes
strykelang/reflection.rsBuilds the %stryke::* hashes that the reflection tool window serializes via st -e 'p tj({%stryke::*})'

Version compatibility

Plugin version tracks the strykelang Cargo version. gradle.properties controls the supported IDE range via pluginSinceBuild / pluginUntilBuild. Currently targets 2024.2.4 SDK against builds 242..261.* โ€” every paid JetBrains IDE on 2024.2 + loads it (RustRover, IDEA Ultimate, GoLand, PyCharm Pro, WebStorm, RubyMine, PhpStorm, CLion, Rider, DataGrip, Aqua). Community editions don't have the LSP API and won't load the plugin.

Limitations

  • No PSI tree or structural navigation โ€” relies entirely on the LSP for symbol navigation. Disabling the LSP under Settings disables them all.
  • Debugger v1: no conditional or hit-count breakpoints, no exception breakpoints, no watch expressions, no Set Value, single-thread only. Step-into across used modules works only if the called sub has line mapping in the same compilation unit.
  • Evaluator injects only scalars from the current frame. Expressions referencing user-defined @arr / %hash see them as empty in the subprocess. Builtins / globals (@INC, %ENV, etc.) work.
  • Lexer is a regex-class scanner; complex constructs (heredocs, qw(), nested string interpolation) are not fully tokenized โ€” they fall back to "string". Server-side semantic tokens fill in where the lexer is approximate.
  • [Split debugger] toast on Debug start โ€” the IDE's deprecated XDebugSession.runContentDescriptor accessor fires Logger.error even when bypassed via reflection if any third-party code touches it during session bring-up. JetBrains' own debug runners suffer the same noise in 2024.3+. Cosmetic only; the debugger works.
  • Reflection hashes (%stryke::*) populate lazily on first user access in DAP mode (eager ensure_reflection_hashes triggers a VM stack overflow that's still TBD). The standalone Stryke tool window fetches them via st -e and is unaffected.

Other editors

  • Neovim โ€” editors/stryke.lua + ALE / vim-lsp / coc.nvim integration via stryke --lsp
  • Vim โ€” editors/stryke.vim
  • Helix โ€” editors/helix-languages.toml
  • VS Code โ€” coc + editors/vscode-settings.json bridge

Bytecode Cache (rkyv)

stryke caches compiled bytecode in a single rkyv-archived shard at ~/.stryke/scripts.rkyv. First run of a script parses + compiles + persists into the shard. Every subsequent run mmaps the shard, validates the archived root once, looks up the entry by canonical path in a zero-copy ArchivedHashMap, and skips lex/parse/compile entirely.

stryke my_app.stk    # cold: parse + compile + write to shard
stryke my_app.stk    # warm: mmap shard + lookup + dispatch

Why rkyv (and not SQLite, like before)?

The original cache was a SQLite database โ€” port of an early zshrs design. We migrated to rkyv shards in v0.10.3. Apples-to-apples bench (strykelang/cache_bench.rs, 100 scripts, M-series, OS page cache warm):

WorkloadSQLite (zstd + bincode)rkyv (uncompressed)Speedup
Steady-state per hit8.60 ยตs2.49 ยตs3.46ร—
Per-process p50 (open + 1 lookup + close)241 ยตs22 ยตs10.79ร—
Per-process total (1000 invocations)263.62 ms22.95 ms11.48ร—
File size (100 scripts)84 KB270 KB0.31ร— (3.2ร— bigger)

The per-process number is the one that matters for s test t โ€” each test file spawns a fresh process that opens the cache, does one lookup, exits. SQLite pays its full Connection::open + WAL pragmas + statement prepare cost on every process; rkyv pays one mmap + one check_archived_root validation, then ArchivedHashMap::get.

The 3.2ร— size growth is the zstd compression we dropped on the inner blobs (kept bincode for now โ€” phase 2 will derive Archive directly on Chunk/Program for true zero-copy load). Disk size is acceptable: 1000+ scripts cached in ~15 MB.

Cache invalidation

Four conditions evict an entry โ€” no stale bytecode is ever served:

  • Source mtime mismatch โ€” edit a .stk file โ†’ cache miss โ†’ recompile.
  • stryke_version mismatch โ€” Cargo version bump.
  • Pointer-width mismatch โ€” cross-build between 32- and 64-bit targets.
  • Binary mtime newer than cached entry โ€” any cargo build advances the binary's mtime โ†’ every cached script invalidates automatically. Catches edits to compiler.rs / parser.rs / vm.rs that don't bump CARGO_PKG_VERSION.

Inspection & tuning

cacheview()                    # list all cached scripts
cacheview("pattern")           # filter by path
cacheview("--count")           # just the count
cache_stats()                  # {count, bytes, path, enabled}
cache_exists("script.stk")     # 1 if cached, 0 if not
cache_clear()                  # wipe the cache

STRYKE_CACHE=0 stryke app.stk  # disable caching for this run

Bypassed for -e / -E one-liners, -n / -p, --lint, --check, --ast, --fmt, --profile modes. Cache is enabled by default for file-based scripts.

Aligned with zshrs

Same rkyv shard pattern as zshrs/src/daemon/shard.rs โ€” mmap + check_archived_root + zero-copy ArchivedHashMap lookup. zshrs uses per-source-tree shards driven by a daemon; stryke uses a single global shard since scripts are individually invoked. Same crate (rkyv = "0.7" with validation, archive_le, size_32 features), same crash-safety story (flock on scripts.rkyv.lock, atomic-rename writes via tmp file + fsync), same versioning fields (magic: u32 + format_version: u32).

Full migration rationale, file-by-file diff, and reproducible bench harness at docs/CACHE_RKYV_MIGRATION.md.

Threading macros vs Racket / Clojure

Every other language with arrow-threading treats the arrow as pure compile-time syntax: a macroexpansion-time AST rewrite into nested calls. Concurrency, when wanted, is a separate primitive (pmap, future, core.async, racket places). Stryke is the first language to bake execution semantics directly into the threading-arrow family โ€” same surface grammar, distinct runtime models.

OperatorLangDesugar / runtimeConcurrencyBackpressurePer-chunk
(-> x (f a)) Clojure macroexpand → (f x a) at compile time none โ€” sequential n/a n/a
(->> x (f a)) Clojure macroexpand → (f a x) (LHS as last arg) none โ€” sequential n/a n/a
(as-> x $ (f $) (g $)) Clojure macroexpand with explicit $ slot none โ€” sequential n/a n/a
(cond-> x p? (f)) / (some->) Clojure conditional / nil-short-circuit macros none โ€” sequential n/a n/a
(~> x (f a)) / (~>> x ...) Racket (threading) macroexpand to nested call (lib macro) none โ€” sequential n/a n/a
x |> f Elixir / F# / OCaml / R / Hack / JS proposal parse-time / inline-fn rewrite to function application none โ€” sequential n/a n/a
~> SRC stage1 stage2 stryke parse-time rewrite (compatible with Racket ~> / Clojure ->) none โ€” sequential n/a n/a
~>> SRC stage1 stage2 stryke parse-time, thread-last (Clojure ->> compatible) none โ€” sequential n/a n/a
|> rhs stryke parse-time pipe-forward (Elixir-compatible spelling) none โ€” sequential n/a n/a
~s> / ~s>> stryke each stage compiled to a worker thread connected by bounded channels (per-item streaming) concurrent across stages yes โ€” bounded channels n/a (per-item)
~p> / ~p>> stryke input split on type-aware chunk boundaries; whole pipeline runs per-chunk via rayon work-stealing pool with auto-merger parallel (N workers, N=CPU) n/a (eager) yes โ€” UTF-8 / element-aligned
~d> / ~d>> stryke โ€” distributed same chunk-block semantics as ~p> but chunks ship to remote workers on a cluster via pmap_on (ssh job-queue, fault-tolerant retry). Syntax: ~d> on $cluster SOURCE stage1 stage2 ... distributed (M hosts ร— N slots each) n/a (eager) yes โ€” per-slot chunk boundaries
par { BLOCK } / par_reduce { โ€ฆ } [{ โ€ฆ }] stryke generic parallel-chunk thread-stage with auto-merge (hash-of-num → key-wise add, num → sum, array/string → concat) parallel n/a yes
||> / |then| stryke boundary marker: switches the rest of a ~p> chain back to sequential ~> mode (terminal merge stages run on the merged whole) boundary — switches mode n/a n/a

What's structurally novel

  • Pure-syntax tradition: Clojure / Racket / Elixir / F# / OCaml / R / Hack / JS treat threading as compile-time AST rewrite. To get parallelism you call a separate function (Clojure pmap, Racket future, Elixir Task.async, OCaml Domain.spawn) โ€” never via the arrow.
  • Stryke axis-2 โ€” execution model: ~s> and ~p> use the same surface grammar as ~> but emit different bytecode. One-token swap changes "evaluate sequentially" to "spawn a per-stage worker pool with channels" or "split into chunks and reduce in a tree." No re-plumbing required.
  • Boundary markers: ||> / |then| are the world's-first operator that changes the threading mode mid-chain. Lets a single declaration mix parallel chunked extraction with sequential terminal stages without breaking the macro.
  • Auto-merger for parallel chunks: par_reduce { extract } picks a merge strategy by inspecting the first chunk's result type (hash → key-wise add, scalar → sum, array → concat, string → concat). Canonical histogram merge in one stage; no plumbing.
  • Compatibility floor: ~> / ~>> / |> behave exactly like Racket ~> / Clojure ->> / Elixir |>. Polyglots get the familiar semantics for free; the parallel forms are pure addition.

Equivalent rewrite vs. one-token swap

To get the equivalent of ~p> @data map { _ * 2 } sum in Clojure today, you write:

(->> data
     (partition-all (chunk-size))
     (pmap (fn [chunk] (->> chunk
                            (map #(* 2 %))
                            (reduce +))))
     (reduce +))

Five lines, three nested operations, two manual reduces, and an explicit chunk size. Stryke compresses the same shape to:

~p> @data map { _ * 2 } sum

Same input, same output, same parallelism โ€” different operator. The chunk split, worker dispatch, and tree-merge are the operator's job.

Inventions

Designs that originated in stryke. Other languages are invited to absorb, adapt, or build on any of these under MIT โ€” see CREATORS.md ยง Porting stryke ideas for the attribution norm. The list also doubles as the canonical reference porters cite.

InventionFormWhat it does
Three-axis threading-operator family |> · ~> · ->> One operator family with three distinct semantics (pipe-forward, Racket-style, Clojure-style), each accepting bare-fn / arrow-block / placeholder forms per stage. Collapses what Clojure does with three separate macros (->, ->>, as->) into one.
Streaming & chunk-parallel thread macros ~s> · ~s>> · ~p> · ~p>> Threading macros with execution semantics baked into the operator: ~s> / ~s>> run each stage as a worker connected by bounded channels (per-item streaming with backpressure), ~p> / ~p>> chunk the input and run the whole pipeline per-chunk in parallel with an auto-merger (canonical histogram merge for hash-of-numeric, numeric add, array/string concat). ||> / |then| mid-pipeline boundary switches a ~p> back to sequential ~> for terminal stages. No other language overloads its threading macros with concurrent / chunk-parallel semantics โ€” Clojure's -> family is purely syntactic.
Distributed thread macros ~d> · ~d>> Same chunk-block semantics as ~p> / ~p>>, but the work-stealing pool spans multiple hosts. Syntax: ~d> on $cluster SOURCE stage1 stage2 .... Chunks ship over ssh to remote workers on a cluster() handle, executed in parallel across M hosts ร— N slots each, results streamed back over the same JOB-frame protocol that backs pmap_on (retry on transient failure, ordered merge on completion). The threading-macro syntax is identical to ~p> โ€” only the dispatcher changes โ€” so a local pipeline scales to a cluster by swapping one operator. World-first: no other language exposes distributed compute through a thread-macro operator. Sugar for dist_reduce on $cluster { stages } SOURCE.
Mid-chain threading-mode-switch operator ||> · |then| Boundary marker that terminates the chunk-parallel section of a ~p> chain and switches the merged result into a sequential ~> continuation for terminal stages that operate on the whole. Pure-syntax pipe operators in F# / Hack / Elixir / OCaml / Scala / Julia / R / Clojure / Racket have no equivalent because their threading macros lack execution semantics in the first place โ€” there is no “mode” to switch out of. The English alias |then| reads as English without changing semantics.
Generic parallel-chunk wrapper par { BLOCK } · par_reduce { extract } [{ merge }] Replaces the explosion of per-fn parallel-prefixed builtins (pletters, pchars, pwords, puc, plc, psplit, pjoin, psum, pmin, pmax, pany, pall, pcount, pfind, ...) with one wrapper that splits input on type-aware chunk boundaries (UTF-8 char-aligned for strings, element-aligned for arrays) and runs any block per chunk in parallel. par_reduce adds an auto-detected merger so frequency-style fused extract+count pipelines (par_reduce { letters |> freq }) achieve 3.5×+ wall speedup without materialising 50M-element intermediate vectors. No prior language exposes “take any block, parallelize it across chunks of input” as a single composable primitive.
Implicit-positional closure params with depth _ · _< · _<< · _<3 · _0 · _0< · _0<< · _N<<<< Two-axis closure-arg notation: N selects positional slot, < count selects closure-nesting depth outward (_< = depth 1, _<<<<< = depth 5, _<N = indexed shortcut). Four-way alias for slot 0 (_, $_, _0, $_0) unifies Scala / Perl / Mathematica / sigil-positional traditions. Bare _< is the sigil-less slot-0 outer-topic shortcut; equivalent to $_<, _0<, $_0<.
Polymorphic literal-typed range 1:10 · 'a':'z' · 'I':'X' · '2026-01-01':'2026-12-31' Range syntax where literal type infers range type โ€” integer / character / roman numeral / ISO date / RFC3339 datetime / dotted-quad IP. Step semantics inferred from inferred type.
Parse-time idiomatic firewall --no-interop Parser-enforced rejection of Perl-isms (sub, say, $a/$b globals, scalar, etc.) in favor of stryke idioms (fn, p, $_0/$_1, len). Documented rules become parser errors so LLMs and bots can't ignore them.
AOP intercepts as language primitive before · after · around · intercept Pre / post / around hooks first-class in the language. Not a library โ€” the parser, compiler, and VM all know about the intercept layer.
AI primitives as syntactic forms ai · tool fn · mcp_server_start Calling Anthropic / OpenAI is a builtin verb on par with print. Tool-use, batch fan-out, and MCP server / client are language-level constructs, not third-party crates.
Encyclopedic-stdlib design axis 10,459 builtins in %b / 11,191 keys in %all in 44 MB single static binary, zero-import Inverts the prevailing scripting-language design philosophy โ€” “core minimal, libraries optional” becomes “core encyclopedic, libraries unnecessary.” Shipped builtins span crypto, stats, linear algebra, networking, codecs, terminal viz, git, jq. Density: ~3.6 KB amortized per builtin, ~200× denser than Wolfram Engine v14 (~5–6 GB on disk for ~7,000 builtins). The single-binary qualifier is structural, not just a packaging choice โ€” no kernel boot, no paclet manager, no licensing daemon, no library() resolution.
Live-binding hash with kind strings %parameters zsh-$parameters analogue: keys are sigil-prefixed names ($x, @a, %h), values are kind strings ("scalar", "array", "hash", "atomic_array", ...). Rebuilt on every read so the snapshot is always current.
Lazy package-stash refresh %main:: · %Foo:: Per-package symbol-table stashes refreshed lazily on every read so newly-defined subs / our vars are visible immediately. Symbol-name โ†’ kind string ("scalar" / "sub").
Compiled Unix shell substrate fusevm + zshrs First compiled Unix shell. Stryke and zshrs share a bytecode VM and Cranelift JIT path; the shell hosts the scripting language with zero-fork dispatch via @expr syntax.
Distributed compute as primitive cluster([...]) · pmap_on $c { } @list · ~d> on $c SRC stages Multi-host SSH worker pool as a language-level value. pmap_on fans a parallel map across persistent remote workers with fault tolerance; ~d> is the same chunk-block thread-macro surface as ~p> but each chunk ships to a remote slot for chunk-aware distributed pipelines. Not a framework, not a library โ€” a builtin triple.
AOT script binary deployment s build script.stk Single static binary output from a script. No runtime install on target, no node_modules, no bundle install, no Docker required โ€” scp ./binary prod: is the deploy.

When a port lands in another language, a one-sentence credit in that language's docs (feature reference / release notes / “Influences” section) referring to stryke as the invention source is the asked attribution form. See CREATORS.md for the exact wording.

Demo gallery โ€” runnable feature tours

Every demo runs clean under stryke --no-interop (idiomatic stryke, no Perl 5 shortcuts). Each is pinned by a behavior test in tests/suite/demos_pin.rs so regressions trip CI.

rkyv KV store

Sketches & probabilistic data structures

  • sketch_algebra.stk โ€” world-first operator overloads + | & ^ - on Bloom / HLL / CMS / TopK / t-digest / Roaring
  • sketches_tier2.stk โ€” rate limiter, hash ring, SimHash, MinHash, interval tree, BK-tree, rope, diff

Numerical & modern IDs

  • numerical_ids.stk โ€” ULID (sortable + monotonic), Kahan compensated summation, Welford online stats

Language fundamentals

Parallelism & concurrency

  • parallel_primitives.stk โ€” pmap (0.05s vs 0.40s serial), pgrep, pfor, psort, preduce, pmap_reduce, fan
  • async_tasks.stk โ€” async { } + await, spawn alias, racing tasks, async ingestors โ†’ sketch

OOP, AOP, regex, glob

  • oop_classes.stk โ€” typed fields, BUILD hook, extends, method dispatch
  • aop_intercepts.stk โ€” before/after/around, proceed(), glob-pattern intercepts
  • regex_three_tier.stk โ€” regex / fancy-regex / pcre2 auto-tier escalation, named captures
  • glob_qualifiers.stk โ€” world-first zsh-style qualifiers (N) (.) (/) (D) (oL) (Lk+1) in a scripting language

Practical I/O

  • iterator_ops.stk โ€” enumerate, dedup, nth, range, flatten, zip, colon-range 0:9
  • file_streams.stk โ€” slurp/spurt, glob recursive, find, file metadata
  • crypto.stk โ€” SHA-2 / SHA-3 / BLAKE3 / MD5, HMAC, Argon2, JWT
  • codecs.stk โ€” JSON / YAML / TOML / CSV roundtrips, cross-format conversion
  • datetime.stk โ€” strftime, ISO 8601, RFC 2822, time arithmetic, ISO keys sort chronologically

Shell & reflection

  • shell_repl.stk โ€” whoami, pushd/popd, strftime, tac, tree, mktemp
  • run_source.stk โ€” run(target, ...args) spawns isolated subprocess; source(path) loads stryke definitions into the current REPL
  • reflection_hashes.stk โ€” %b / %a / %k / %all / %c / %d queries, alias inversion

Worked end-to-end programs

Full multi-stage programs (not snippets) that exercise stryke's parallel, AOP, parser, and AI primitives together. Each ships with embedded assertions and passes under stryke --no-interop.

Repository & links

  • Source โ€” GitHub repo
  • Crate โ€” crates.io (cargo install strykelang)
  • Rust API docs โ€” docs.rs
  • Issues โ€” issue tracker
  • Full README โ€” README on GitHub (install, parallel primitives, mysync, CLI flags, architecture, benchmarks, standalone binaries, inline Rust, bytecode cache, distributed pmap_on, LSP).
  • Parity โ€” parity/cases/*.pl holds 20k+ Perl-vs-s test cases run by parity/run_parity.sh.