|
A Guide for an AI when Vibe Coding |
This guide is written for AI language models generating Sheerpower code. It focuses on the patterns most likely to produce incorrect output when models apply habits from C, Python, JavaScript, or Java — and shows the correct Sheerpower form alongside each trap.
Generate only syntax that appears in this guide or in the official Sheerpower tutorials. When a statement, function name, parameter, or syntax rule cannot be confirmed from either source, do not invent a plausible-looking substitute. Instead, choose one of the following actions:
I am not certain whether Sheerpower's string-search
function is pos(), index(), or find(). Please confirm before I
continue.
Before writing the file I/O section, I need to re-read
the File I/O tutorial. Is it available?
// UNVERIFIED: exact syntax for sorting a cluster array descending // confirm against Cluster Sorting tutorial before running
A compile error on a marked gap is recoverable. A confabulated routine name buried in otherwise correct-looking code is not — it fails silently, erodes trust, and can take longer to track down than the original task.
Takeaway: Stop, mark, or ask — in that order. If a piece of Sheerpower syntax cannot be traced to this guide or the tutorials, that is the signal to pause, not proceed.
This document is structured around error patterns observed in generated code — not around language features.
The goal is simple: eliminate predictable errors in generated code before they reach execution.
Human readers will also find this a useful quick-reference for the rules that differ most from mainstream languages.
//, but ! is okay for all new code.call prefix keyword, use named parameters.with arguments are read-only within the routine — compiler enforced.select case — always include case else.retry vs. continue.do / repeat do / end do.$, ?, and bare names.sprintf$() format specifiers.f$() formatted strings — text with values in [[]] slots instead of concatenation, and the one rule for routines.collect cluster before for each.findrow() for fast single-row lookup.VIEW statement.element$(), elements(), size(), str$(), and pass._GID$ special variable.This guide is long. If you retain nothing else, retain these ten rules. They cover the mistakes AI code generators make most often, and every one is a hard rule: breaking it means a failed compile or a wrong-running program, not a style complaint. (Style preferences — naming, routine ordering, decorated headers — are marked as such in their own sections below.)
call, no positional arguments.
Wrong: result = calc_tax(income, rate).
Right: calc_tax with income, rate, returning tax result
— or the function form with named arguments:
result = calc_tax(income = income, rate = rate).break, return, throw,
catch, def,
+=, --. See Keywords That Do Not Exist.end if (two words), end routine,
end when — and a for loop closes with
next varname, never end for."\n" is a backslash and an n. Use chr$(10),
or %text ... %end text for multi-line literals.select case needs case else.
Without it, an unmatched value crashes at runtime.collect cluster before every for each.
A for each without a prior collect is wrong even when it
seems to work on a small test.findrow(), never a scan loop.
It is an O(1) hash lookup on any field; findvalue()
collapses lookup-and-read into one expression.if count then.
Right: if count <> 0 then; strings compare against
"".days(), date$(), and
seconds(); read the Date and Time Functions tutorial
before writing any date/time code."" or -1 to mean "not supplied" and
fix it up inside the routine (with parameters are read-only
— that fix-up will not compile).
Right: routine send_alert with message$, priority$ = "normal"
and simply leave the parameter out when calling.To maximize portability, compatibility, and long-term maintainability, use only ASCII characters in all Sheerpower source code.
Sheerpower is case-insensitive. For consistency, write all code in lower-case and use snake_case for multi-word identifiers. Where ever possible, use meaningful variable names. This helps both code validation and code maintenance later on.
This is the single most common error in AI-generated Sheerpower code.
Sheerpower has no call prefix keyword when invoking routines.
Routines are invoked by just their name, followed by with
for inputs and returning for outputs. Sheerpower routine
names cannot contain $ or ? characters.
All with arguments are read-only within the routine —
compiler enforced.
call routine_name(args) or result = routine_name(args).call keyword and no positional argument list:
the statement form binds by name with with / returning,
and the function form (next section) takes name = value arguments
in parentheses -- one leading value may stand alone, and binds the first
parameter (calc_tax(1500, tax_rate = 0.12), or
1500 |> calc_tax(tax_rate = 0.12)).call. Never write
result = routine_name(a, b) -- a second positional value is a
compile error. Use the with / returning syntax below, or the
function form with named arguments.
Parameters are read-only inside the routine by default. Do not
attempt to assign to a with parameter — it is not
allowed by the compiler.
A parameter's ending types it, as a variable's does: name$ takes text,
count% a whole number, ok? true / false, rate# a
real, and a bare name such as income adopts the type of the FIRST call
compiled and keeps it for the whole program. The compiler checks every call against
the header, wherever the header sits: text passed to a numeric parameter, or a number
to a $ one, is a compile error at the call (Expected type string, got
real), so convert at the call with str$() or val(). A
parameter written values(*) takes a whole array, by reference, in either
direction. Up to 16 with and 16 returning parameters; a
cluster carries more.
A routine with exactly one returning
parameter can be called as a function inside any expression. The
parentheses mark function use; the arguments are the same named
bindings the statement form uses; and the function's value is the
final content of the returning parameter. Nothing changes inside
the routine — every existing single-returning routine is
function-callable already.
The last character of the name tells Sheerpower what kind of value the
call produces — the same spelling rule variables already follow. A
plain name like calc_tax(...) produces a number. A name
ending in $, like full_name$(...), produces
text. % means a whole number and ? means a
true/false value. That ending must agree with the routine's returning
parameter — if it doesn't, the compiler stops and tells you —
unless the returning parameter is a plain (flexible) name like
tax, which can hand back whichever kind of value it holds.
Routines may be defined after their call sites, as usual. A routine with
zero returning parameters, or more than one, cannot be called as a
function; the compiler says so. Neither can a once routine
(routine name once): a function is evaluated at every call,
but a once body runs only the first time — so the
compiler stops that too, rather than let later calls silently receive
the first call's result. Calling a routine as a function costs
exactly the same as the statement form — both compile to identical
code.
Routines with multiple returning values keep using the statement form, which handles up to 16 of them clearly.
A with parameter can declare a default value. Calls that
omit the parameter get the default; calls that pass it override it. The
default is an expression, evaluated fresh at every call that
omits the parameter — never once at definition time.
"" or -1, and patch the real
value inside the routine. In Sheerpower that pattern is not just
unnecessary — it does not compile, because with
parameters are read-only.stamp$ = fulltime$ is
freshly current on every call, and a module-variable default reads the
variable's value at each call.
Because arguments are named, there is no “defaults must come
last” rule and no positional omission: a default may appear anywhere
in the parameter list, and a call may omit any subset of defaulted
parameters, in any order. A default may be a literal, a constant, a module
variable, a built-in function, an expression combining them, or an earlier
parameter in the same list (width = length); it may not call
a routine. Declare the referenced parameter before the default
that uses it: names in a default resolve left to right, and a name that
only becomes a parameter later in the list silently binds the
module variable of that name instead — a wrong value, not a compile
error. returning parameters cannot take defaults.
Function-form calls use defaults the same way:
calc_tax(amount = 1000) applies a declared
tax_rate default.
A parameter with no default is required — omitting it is a
compile-time error at the call site
(Parameter RATE -- was not passed and has no default value).
Do not treat a parameter as optional unless its declaration shows a
default.
Every block in Sheerpower has an explicit named terminator. Missing or mismatching a terminator is a common AI error, especially when generating nested structures.
end ___ keyword — no braces.
There are no braces. There is no endif as a single word —
it must be written end if with a space.
for loops — both numeric and for each —
close with next varname, not an end keyword.
select case Requires case else
When generating a select case block, always include
case else. If no case matches and there is no
case else, the program can fail at runtime.
select case blocks with only the expected cases. This is
fragile because real data can contain unexpected values.case else as
the fallback branch.select case
block should include case else.
VAL() as the Primary Way to Parse Numbers
From Text
In Sheerpower, the primary way to extract numbers from text is
VAL(). When numeric data appears inside a larger string,
do not assume the text must be split apart first, and do not invent
manual parsing helpers unless they are truly necessary.
Use VAL(string$) when the entire string should be a
number. Use VAL(string$, Nth) when the string may contain
embedded numbers and you want the first, second, third, or later
numeric value. This makes VAL() the standard tool for
parsing values from user input, reports, logs, messages, and mixed
text.
Given the string
"I need 5 widgets at $12.99 each", extract the
numbers directly:
This extracts 5 and 12.99 without any manual
parsing or string splitting.
If parsing may fail, check _integer immediately after the
call. A value of 0 means no valid number was found. When needed, use
the optional parsing options to handle separators, ignored characters,
negative formats such as parentheses, or European number conventions.
In short: when an AI needs to get a number out of text in Sheerpower,
it should think of VAL() first.
Sheerpower has two forms of exception handler. The words
retry and continue mean something specific
here and must not be confused with loop control.
retry and
continue, or use throw / raise
/ except from other languages.cause exception.retry = re-run the failing statement after fixing the cause.
continue = skip the failing statement and move on.
Never write throw, raise, except,
or catch.
Sheerpower's main loop construct is do / end do.
The keyword that restarts the loop from the top is
repeat do — not continue.
The keyword that exits is exit do.
continue inside
a loop intending to restart the iteration. In Sheerpower,
continue is an exception-handler keyword only.repeat do to restart a
do loop. Use exit do to break out.repeat do is explicit about
intent — there is no ambiguity about which loop is being restarted
in nested structures.continue.
Write repeat do to cycle, exit do to leave.
continue inside
for loops to skip to the next iteration or break
to exit. Neither keyword exists for loop control in Sheerpower.iterate for skips to the next
for iteration. repeat do restarts a
do loop. Never use continue or
break for loop control.
There is no return statement in Sheerpower.
To exit a routine before reaching its end, use exit routine.
Sheerpower uses visual suffixes to declare variable type automatically. These are part of the variable name, not operators.
? suffix
on boolean variables, generating is_active = true
instead of is_active? = true.? when declared by suffix. Without the
suffix, Sheerpower creates a REAL variable and true
stores as 1.var, let, int, or
float declarations needed for most cases.? suffix. Check every string variable name for
the $ suffix.
const pi = 3.14159 (or
maxnum, true, date$) out of habit.pi is built in — use it as is. A
constant or variable named after a built-in value or a system variable
(_integer, _extracted) is a compile error: "pi is built
into Sheerpower -- a constant cannot take its name; choose another (my_pi)".
Sheerpower string literals are treated as raw text. There are no escape sequences inside strings.
\n, \t, or
\\, or double-escape backslashes unnecessarily.Do not apply escape rules from other languages. Write exactly the characters you want stored in the string.
For a string spanning several lines — HTML, SQL, help text, test
data — use a %text block. Everything between
%text and %end text is one string literal,
exactly as typed, with lines joined by CR+LF.
The body is raw: comment indicators such as // and
!, quotation marks, and indentation are all part of the
string. %text must be the last thing on its line;
%end text ends the block. Add the trim
option — %text trim — to remove the
indentation the lines share, so the block can be indented to match
the surrounding code. One literal holds up to 16K; concatenate
blocks for more.
f$()
Most text a program builds is a sentence with values in it: a message,
a log line, a label. Do not assemble it from pieces with +
and str$(); write the sentence and put each value in a
[[]] slot. A slot holds any expression — a variable,
arithmetic, a function call, a whole array — and is evaluated when
f$() runs. A number renders as str$() writes it
(no padding); to choose decimals, commas or width, put a
sprintf$() inside the slot.
Rules that matter for generated code:
with parameters and private variables — and the module variables too, exactly as the routine's own statements do.UNKNOWNVAR exception with the identifier in
_string$, the same rule as eval() — a
misspelled slot is loud, not silent.+ on every call is a new string every call and
recompiles every call.[[]] (else a catchable
BADFORMAT); brackets and quoted text inside a slot are fine,
so [[name$[1:3]]] and [["a]]b"]] both work; a
single [ is plain text.sprintf$(), and the
fastest way to place values in text. Big literals — HTML, SQL,
%text blocks — cost nothing to assign or pass: the
runtime shares a literal's bytes rather than copying them, so write large
templates as literals, not as strings assembled at run time.
One catch worth knowing on top of the rule:
seconds() requires a full datetime string, not just a
date. A variable containing only YYYY-MM-DD will cause
an illegal time format error.
Problem: In most mainstream languages (Python, Java,
JavaScript, C++), numeric calculations use IEEE 754 binary
floating-point. These values cannot exactly represent many
decimal numbers. This leads to small but visible errors in
results (for example,
0.1 + 0.2 = 0.30000000000000004).
Because of this, AI-generated code often adds
round(), floor(), or similar
adjustments after calculations to "correct" the result.
Sheerpower Difference: Sheerpower uses Exact REAL values stored in a decimal-accurate format. Decimal values are represented exactly, so calculations produce correct results without adjustment.
Instruction for AI:
Do not add rounding to compensate for floating-point error.
In Sheerpower, the result of a calculation is already
correct. For example, 0.1 + 0.2 evaluates
exactly to 0.3.
Business Logic Only:
Apply rounding only when it is explicitly required by the
business rules, for example, "round tax to the nearest cent."
Do not round as a defensive habit.
Trust the VM:
Sheerpower's Virtual Machine is designed for precise decimal
arithmetic. You can rely on the direct result of any numeric
expression.
Range and rounding facts (so you do not compensate for them):
Whole numbers are exact to 54 digits and promote automatically beyond
that — do not add BigDecimal-style ceremony, overflow guards, or
string-based big-number workarounds. Division rounds half-to-even at
16 fraction digits by language rule (1/3 is sixteen
threes) — do not hand-round quotients. Equality with
= is exact and safe — do not generate
tolerance-comparison patterns like abs(a - b) < epsilon
unless the values came from a C-double source such as trig or logs.
One more: the exact range is an ABSOLUTE grid with 16 digits after
the decimal point, so relative precision falls for very small
magnitudes (0.0000000001 / 3 keeps six significant
digits). For quantities that live at tiny scales, scale the UNITS
— store cents rather than fractional dollars, micrograms
rather than fractional grams — the standard practice in every
fixed-point system.
Takeaway:
If you are about to write round(total, 2) just to
ensure cents are correct, stop. In Sheerpower, the cents are
already correct.
An AI trained on C-family languages writes an index loop for everything. In
Sheerpower an array is a value: most of those loops are one statement, and the
compiler does the work in native code. Arrays are 1-origin; x(i) is an
element (parentheses -- square brackets are slices and gathers); dim x(*)
is expandable and x(*) = v appends; a whole-array result needs a declared
array target, never a plain variable.
Wrong (do not generate): for i = 0 to n - 1 (arrays start
at 1), x[i] for an element (that is a gather -- use x(i)),
x.length / len(x) for an array (size(x)),
x.append(v) (x(*) = v), a loop that sums, filters, sorts or
uppercases element by element (stats$sum(x), filter(),
sort(), ucase$(x$)). For a table of related columns use a
cluster (next section), not parallel arrays. The full surface --
masks, where(), unique(), isin(), 2-D arrays,
matmul() / solve(), axis: reductions -- is on the
Arrays and Array Math pages.
Every n-th element, and tables of flags. A loop that writes the same
value to every n-th element is one statement: fill x(start step n) with v,
or the slice store x[start:*:n] = v. A ? array stores one bit
per element (500 million flags take 62.5 MB), fill flags? with true sets
them all, and stats$sum(flags?) counts the true ones. When one plain value
is written across a large boolean, REAL or INT array, the engine spreads the work over
the processor's cores.
Wrong (do not generate): for i = start to last step n /
flags?(i) = false / next i to write one value (more than ten
times slower on a large table), or an integer array with hand-made bit masks for a
table of flags.
Clusters are Sheerpower's primary structured data type.
The field-access operator is ->.
for order in orders and then access fields as
order->amount. There is no such iteration variable
in Sheerpower.for each loop,
use the cluster name itself with -> to access fields:
orders->amount.for each iterates the
collected result set directly with no temporary object allocation.The file type picks the reader: .csv, .tsv, a
.json array, or .jsonl -- JSON Lines, one object per line, the
form logs and data exports use. cluster output name '@sales_out.jsonl': sales
writes a cluster back out in any of those forms (the name chooses), one row per line
for JSON Lines.
collect Before for each
Before iterating a cluster array with for each, collect
the cluster. This step prepares the current result set for iteration.
Without a collect cluster, a for each loop will
generate an exception.
for each loops without first collecting the cluster.collect cluster before
every for each iteration, even when no filtering or
sorting is needed.collect defines the active
result set. It can also apply filtering, sorting, and selection before
iteration begins.collect is the
required setup step before for each.
findrow() for Single-Row Lookup
When looking up a single row in a cluster, use
findrow(). Do not generate a full
collect / include / for each / exit for loop just to find
one matching row.
findrow() for direct
lookup when the goal is to find one matching row.findrow() is the standard
Sheerpower way to perform fast single-row lookup, rather than scanning
rows manually.findrow() before writing a loop.
VIEW creates a zero-copy window into a source string.
It does not allocate a new string — it references a position
and length within the original buffer. The clauses must appear
in the correct order.
VIEW
entirely and generate slow substring loops, or get the clause
ordering wrong.INTO names the source. The remaining clauses
such as MID, PIECE, MATCH, and related options describe how to
locate the window.VIEW before reaching for mid$() or
string concatenation.
An empty piece is still a piece: "red,,blue" has three, and a blank
line in a file is an empty one. So a loop ends on _integer, not on
an empty view: use the view (len() forces it to resolve), and
_integer is -1 once MATCH runs past the last piece.
Read it in the very next statement, before anything else sets it.
The following keywords are common in other languages. None of them should be used in new Sheerpower code.
Output uses print. Items are separated by semicolons.
A trailing semicolon suppresses the newline. Tab alignment uses
tab(n).
A number in a print list is padded: a space before it (the sign's
place) and one after, so print "x = "; 42 shows x = 42 .
A fraction below 1 prints with its leading digit, 0.25, as every
other language prints it; str$() gives the same text without the
padding. When a test compares output, run the program and paste what it printed
rather than predicting the spacing.
sprintf$() Format Specifiers
Sheerpower uses sprintf$() for formatted string output.
Do not assume that Sheerpower format strings are the same as C
printf, JavaScript template strings, or Python format
strings.
Several Sheerpower format specifiers are especially important because they appear often in real programs and have no direct equivalent in mainstream languages.
sprintf$() as if it were C printf or
Python formatting. This leads to wrong assumptions about
substitution, pluralization, money formatting, and number-to-words
output.sprintf$() already has
the needed format specifier.
The %p format is used for automatic pluralization. It
takes the count and the singular word:
The bare % format is a general substitution marker. It is
not limited to one data type.
The %m format is commonly used for comma-separated numeric
output, especially money-style output.
The %w format outputs the written-in-words version of a
number, useful for check-writing and business documents.
Use line input when the input may contain spaces or commas.
Use input for simple values.
Several small built-in routines and statements are common in real Sheerpower programs. AI models often invent mainstream-language substitutes for them.
For delimited string work use split(text$ [, delim$]) to get
every piece as a string array and join$(array [, delim$]) to
write one back; element$() / elements() read one
piece at a time. split() returns an ARRAY: its target must be
dim words$(*), never a plain string.
Use size() to get the number of rows in a cluster.
Use str$() to convert a number to a string. In
Sheerpower, str$() does not add leading spaces.
Use pass to run an operating system command.
Many built-ins leave a second result beside the one they return: _integer
is a count or a position (where val() stopped, the row a
findrow() found, the HTTP status of a URL open, a DLL's return),
_real a measure (a p-value), and _string$ a text (the detail of
the last exception, a server's error body). They are last-result values: read them in
the next statement, not later and not inside a longer expression --
n = val(text$) then if _integer = 0 then is the numeric-test
idiom.
Sheerpower has four routine scopes. Generating the wrong scope
is a common AI error, especially confusing local
and private.
A routine with exactly one returning parameter can also be
called as a function in any expression: calc_tax(amount = 1500,
tax_rate = 0.12). Routines can return up to 16 values via the
statement form. Sheerpower also has many dozens of built-in functions.
private when
they mean local, or generate a with clause
on a local routine that shares the parent's scope.local routine sees all
of the parent routine's variables directly. It does not need
parameters for data the parent already has. A private
routine is isolated and must receive data through parameters.local.
If it is a standalone reusable unit, use private or
routine.
Whenever you need a unique ID, use the _GID$ special variable.
It returns a 30-character, browser-safe identifier that combines a date stamp
and a unique ID. The result is both globally unique and sortable by creation date.
Because _gid$ includes a universally unique identifier, two
instances of the same handler running simultaneously will never produce the
same value, even when running on different systems. This eliminates the need
for counters, sequence generators, or inter-process coordination.
Only wrap code in a when exception in block when you intend to
handle the error differently than Sheerpower would on its own. Sheerpower
already stops on unhandled exceptions and automatically reports the error type,
description, call stack, and source location.
A handler earns its place only when it does something Sheerpower's automatic crash report cannot — such as retrying with a fallback, logging before continuing, or allowing the program to recover and keep running.
use block that only prints
extext$ and stops gives the user less information than
Sheerpower's automatic crash report. Delete it and let Sheerpower handle it.
use block is FATAL by design — it does not bubble to
any other handler. Keep use blocks minimal: assignments,
setting a flag, printing to an already-open channel. Do the risky
recovery work AFTER end when, guarded by the flag:
Style guidance, not a compile rule — Sheerpower resolves forward references, so any order builds. Follow it anyway; it is the house convention.
Order routines so that every routine appears before the routines it calls. The main logic area comes first, then the high-level routines it calls, then the lower-level routines those call, and so on down to leaf routines at the bottom.
Think of it as a newspaper article — headline first, detail later. A reader should be able to start at the top and follow the logic downward without ever needing to jump back up.
Style guidance, not compile rules — but generate these patterns by default in any program beyond a quick script.
Correct code and maintainable code are different goals. These patterns are how experienced Sheerpower developers structure programs that stay healthy for years.
A plain routine shares the module's variable space: every
bare variable inside it is global, and two routines using the same
scratch name silently share it. Professionals default to
private routine, pass data through parameters, and name
true globals with a main$ prefix so every global access is
visible at the call site:
A maintainer reading main$tax_rate knows instantly it is
shared state. A bare name tells them nothing.
Values that are loaded once and must never change afterward should be
declared freezable and frozen after load. A later
accidental write raises an exception instead of silently corrupting
the configuration:
A bare number in logic is a question every future reader must answer. Give it a name once:
The standard Sheerpower routine header documents the contract in four
sections. Generate one for every routine; write none
rather than omitting a section:
Documenting entry constants matters most: "what is 0.0825?" should
never need asking twice. And if the routine has side effects —
writes a file, changes a main$ global, adds cluster rows
— say so under "Results on exit": side effects a maintainer
cannot see from the call site are the ones that hurt.
%include merges a file into the SAME variable space as
the including program — it is not a namespace. In a
.spinc file included by many programs, a bare local like
count can collide with any future caller's constant of
the same name. Either prefix the include's working variables with the
include's own name, or wrap its routines in a
module ... end module block, which gives real isolation.
When a program's content grows — more products, more rooms, more message templates — prefer a small generic engine reading CSV rows over adding a routine per case. New content then means new DATA ROWS with zero code change, and one generic validator checks all of it. Reserve per-case routines for genuinely one-off logic.
Style guidance, not compile rules — but generate these patterns by default in any program beyond a quick script.
The Professional Patterns above are about where data lives and how it is guarded. The seven habits below are about how the code reads and how it changes. They share one idea: make the next change easier. Software is rarely hard because of the first version written — it becomes hard when the next fifty changes are harder than they need to be.
Problem: A routine that must check several preconditions is easy
to write as nested if blocks, one inside the other. The code
works, but the actual operation ends up buried three or four levels deep,
and every reader has to hold all the conditions in their head to find
it.
Solution: Check each precondition up front and
exit routine the moment one fails. The main path then reads
top to bottom at the shallowest indentation.
Efficiency: Early exits cost nothing, and a reader finds the real
work in the first screen of the routine.
Takeaway: Nesting is allowed; burying the important logic inside
nesting is not. Guards first, work last.
Problem: Names like data, result,
item, temp, and x force the reader to
look somewhere else to learn what a value represents. Every one of them is
a small piece of detective work.
Solution: Name a variable after what it holds
(pending_order) and a routine after what it does
(process_order). The name should say what the business
concept is.
Efficiency: Good names reduce the time spent reading a codebase
more than any other single habit, and they make Sheerpower's implied
parameter passing (calc_tax with income, rate) read like
prose.
Takeaway: Names need not be long. Make the concepts that carry
weight obvious.
Problem: Programs talk to systems they do not control —
payment providers, vendor data feeds, email services, other APIs. When the
external service's field names are used directly throughout the program,
a rename on their side ripples through every routine that touches those
fields.
Solution: Convert external data into the program's own cluster,
with the program's own field names, in one routine. Everything
downstream reads the program's cluster and never sees the vendor's
names.
Efficiency: When the external service changes, one conversion
routine changes. Nothing else does.
Takeaway: Keep external complexity at the edge. The rest of the
program should not know the vendor exists.
Problem: When every field is "maybe present," every routine has
to keep asking: does this exist, is it valid, can I do this? A single
orders cluster where payment_id$ is sometimes
"" means every shipping, invoicing, and reporting routine must
re-check that the order was paid.
Solution: Represent state accurately. A row that reaches the
paid_orders cluster has a payment id by construction, so
nothing downstream needs to check. Use the ? suffix so a
flag is a real boolean, name constants with const, and
freeze configuration after loading so a later write raises an
exception instead of silently corrupting it.
Efficiency: Fewer existence checks, fewer defensive branches, and
a whole class of "how did this row get here" bugs that cannot occur.
Takeaway: You will not eliminate every error, but you can make the
wrong state impossible to build in the first place.
Problem: A business rule ("only verified adults may use this
feature") gets written in the same routine that updates the database and
sends the email. The rule cannot be tested without triggering the side
effects, so it rarely gets tested at all.
Solution: Put the decision in a routine that takes inputs and
returns a verdict — no file I/O, no email, no database. Put the
actions in a separate routine that acts on the verdict.
Efficiency: A decision routine is trivially testable with a
self-judging test program (see Debugging and Test Methods That
Work). This pattern applies to permissions, pricing, validation,
retry policy, and notification rules.
Takeaway: Make important decisions easy to test without firing the
side effects they control.
Problem: An error that says "something went wrong" tells a
developer nothing and gives the calling program nothing it can act on.
Solution: Every failure carries two things: a human-readable
message and a stable numeric code. Text is for humans; codes are for
systems. Name the codes with const, return them alongside
the message, and when raising deliberately, use cause
exception with the named code. When logging, include the context
a developer needs — identifiers, the operation, the relevant
state — and never log passwords, tokens, keys, or other
secrets.
Efficiency: A caller tests one number instead of parsing a
string, and a log line that names the order id saves a debugging
session.
Takeaway: A message plus a code, with safe useful context. A bare
"failed" is not an error report.
To stop the program deliberately with a code the crash report will
carry, use cause exception with the named constant:
Problem: One edit that adds a feature, refactors an old routine,
changes a cluster layout, and rewrites the retry logic may work perfectly
— and be impossible to review, test, or roll back.
Solution: One change, one purpose. When asked to do several things,
deliver them as distinct, clearly labeled edits, each compiled and tested
on its own. Note anything you noticed but deliberately left alone with a
%TODO so it is not lost and not mixed in.
Efficiency: This is the same discipline as one edit, one
compile, one test. Sheerpower's compile speed makes it free; a
focused change that fails points at exactly one cause.
Takeaway: Each change should be reviewable, testable, and
reversible by itself. If a description of the change needs the word
"and," it is probably two changes.
exit routine early.data, result, item.? booleans, const, freeze.A design lesson that follows from the habits above, and the single most useful smell to recognize.
When a routine has to know “where am I” or “what is the current value” — a position in a string, which field is being filled, whether a session is open — there are two ways to answer it. You can track it: keep a variable that holds the answer and update it as it changes. Or you can re-derive it: recompute the answer from the data every time, and guard the awkward cases. The first is a fact you maintain; the second is a guess you keep correcting.
Re-finding the current field on every character by scanning for the delimiter that follows it — then guarding the cases where the scan goes wrong:
Track the field's start and length; update the length by one as each character is added or removed. “Is the cursor still in this field?” becomes a single comparison, and the guards disappear:
Same idea everywhere: a parser that keeps its position instead of re-scanning from
the top; a loop that carries a running total instead of re-summing; a handler that
sets a ? flag when a session opens instead of inferring it from the
data each time. If a routine keeps asking the data a question it could have
remembered the answer to, remember the answer.
A URL is a file: open file ch: name url$ fetches it (10-second
timeout by default), line input #ch reads the reply, and
jsonutil$(text$, '/path/to/value') picks a value out of JSON. A POST is
open file ch: name 'http-post://' + quote$(url$), data body$, headers h$
(header lines one per line; a Content-Type line replaces the form
default; verb 'PUT' changes the method). The reply is readable:
ask #ch: status code, ask #ch, header 'Content-Type': value ct$.
A reply of 400 or more raises the catchable filenotfound exception with
the server's body in _string$ and the status in _integer --
read them in the use block. For a service called many times,
set session 'api': base 'https://api.example.com', headers 'Authorization: Bearer ' + key$
then open file ch: name '/v1/items', session 'api': the base, the headers,
the cookies and the timeout ride along, and a routine named in the session's
call refreshes a credential on a 401.
Sheerpower web applications commonly use a CGI-style handler pattern. This is not like Flask, Express, PHP, or browser-side JavaScript. Do not invent a web framework pattern from another language.
The common Sheerpower pattern is:
open file cgi://.line input #cgi_ch.getsymbol$() to read submitted values.[[%spscript]] templates for generated output.cgi://, line input,
getsymbol$(), and [[%spscript]] pattern.
Before generating a full Sheerpower web app, re-read the relevant web tutorial or a working web application. This pattern is a major Sheerpower pattern and should not be guessed from other languages.
To write correct and efficient Sheerpower programs, it is important to understand how the compiler processes source code. The lifecycle is simple, predictable, and designed for both speed and reliability.
Problem:
Many developers assume a traditional multi-phase compiler with separate
analysis, optimization, and linking stages. This leads to incorrect
mental models, especially when reasoning about forward references,
performance, and error behavior.
Solution:
Sheerpower uses a streamlined compilation model with four key stages:
directive processing, incremental super-p-code generation, back-patching, and
virtual machine (SPVM) execution.
Efficiency:
Because code is emitted as it is parsed, compilation is extremely fast.
The final back-patching step resolves forward references without
requiring multiple full passes or complex linking stages.
Takeaway:
Think of Sheerpower as a single-pass compiler with a lightweight
fix-up phase. This mental model explains both its speed and its
predictable behavior.
1. Directive Processing
Before compilation begins, directives such as %include,
%debug, and related settings are processed. This stage
determines which source files are merged and which compile-time
behaviors are enabled.
The result is a fully assembled source stream that the compiler will process as a single, continuous program.
2. Incremental Super-P-Code Generation
As the compiler parses each statement, it immediately generates intermediate instructions, called super-p-code. There is no delayed code generation phase and no separate intermediate representation tree.
Problem:
Traditional virtual machines execute many small, low-level instructions,
which increases interpretation overhead and reduces performance.
Solution:
Sheerpower uses super-p-code, where each instruction represents a
higher-level operation, for example a full expression evaluation or
data movement, rather than a single primitive step.
Efficiency:
By combining multiple low-level operations into a single instruction,
the VM executes fewer steps, reducing dispatch overhead and improving
runtime speed.
Takeaway:
Super-p-code is a "super-instruction" model: fewer, richer instructions
that execute more work per step, making the VM both fast and predictable.
This direct emission model is a key reason Sheerpower can compile very large codebases in extremely short timeframes.
3. Back-Patching
After the initial pass, the compiler performs a fix-up phase known as back-patching. During this step, unresolved addresses are filled in for:
This allows developers to write code in a natural top-down style without needing to predeclare or reorder definitions.
4. VM Execution
The finalized super-p-code is executed by the Sheerpower Virtual Machine. The VM uses highly optimized super-instructions to deliver consistent and predictable performance across platforms.
Because execution is handled by the VM, behavior remains stable and portable, independent of the underlying operating system.
Before submitting generated Sheerpower code, run through this checklist mentally:
call keyword anywhere?with and returning?end ___ terminator?next varname — not end for???$?repeat do for do loops or iterate for for for loops — not continue?exit do or exit for — not break?exit routine — not return?retry or continue — not return?for each loops access fields as clustername->field$?collect cluster appears before every for each?select case includes case else?throw, raise, catch, new, return, break, or end for?VIEW clauses use target INTO source, PIECE delimiter, MATCH index?VIEW loop over pieces ends when _integer is -1 right after the view is used — not on an empty piece?VAL() first?split() into a dim words$(*) array (or element$() / elements() for one piece), and join$() to write them back?findrow() — not a full cluster scan?fill x(start step n) with v or a slice store x[start:*:n] = v?dim name?(n) — not integer arrays or bit masks?size()?str$()?pass?sprintf$() formats such as %, %p, %m, and %w?cgi://, line input, getsymbol$(), and [[%spscript]] pattern?exit routine — main path not buried in nested if blocks?data, result, item, temp) where the business meaning is known?%TODO?"\n" is "\" followed by "n"?exit do or exit for inside a use block — set a flag and exit after end when?use blocks kept minimal so nothing inside them can raise a second exception, with follow-up logic testing _error after end when?var--, var+=, or var-= expressions?var++ appears only as a statement, never inside an expression?Vibe coding works best when the feedback loop is short. In Sheerpower, there is no reason to make a large batch of changes before checking the result. The compiler is fast enough that every meaningful code change should be followed immediately by validation.
Problem:
AI-generated code can contain small mistakes: a misspelled variable, a
missing terminator, an incomplete statement, or a routine name that does
not match the source. If many changes are made before compiling, these
small mistakes become harder to isolate.
Solution:
Use a tight cycle: make one change, compile, read the result, fix the
first error, and compile again. Do not guess blindly. Always read the
compiler message and the source line it points to.
Efficiency:
Sheerpower compiles very quickly, so frequent validation is not a burden.
It is the normal workflow. One edit followed by one compile keeps the
AI, the programmer, and the source code on the same page.
Takeaway:
For Sheerpower vibe coding, the rule is simple: one edit, one compile,
one test. Keep the loop tight.
After each code change, compile and validate immediately:
Always use PowerShell for this workflow, not bash. Bash can alter or
mangle the line-delimited compiler output. The sp4gl
command is expected to be on the system path, so a full path is not
needed.
Use a build-output filename that matches the program name. For example, if the source file is:
then the build output should be:
This keeps build results easy to find and avoids overwriting results from other programs.
After compiling, check the exit code. If the build failed, read the build output:
The build exit codes are:
0 — clean build1 — compile errorA clean build looks like this:
Compiler error lines follow this format:
The fields are:
When a compile fails, follow this process:
If there are multiple errors, focus on the first one. Later errors are often caused by the first error.
Do not guess at fixes blindly. The error message plus the surrounding source code is usually enough to show what went wrong.
For AI-assisted coding, never invent a fix based only on the error message. First inspect the source line, nearby lines, and the related variable or routine names. Then make the smallest correction that directly explains the compiler error.
| Error message | Typical cause |
|---|---|
| Unknown variable | Typo in a variable name |
| Unexpected end of statement | Missing closing quote, parenthesis, or keyword |
| Expected end if | Mismatched block structure, such as an if without end if |
| Unknown routine | Typo in a routine name, or routine defined after stop |
The following example shows the complete cycle: write, compile, fail, read the error, fix the source, recompile, run, and verify.
Step 1 — write the program:
Step 2 — compile the program:
The exit code is 1. The build output contains:
Step 3 — read the source at line 5:
The problem is that mesage$ is misspelled. The variable
created on line 3 is named message$. The fix is to use
the same variable name in the print statement:
Step 4 — recompile. The build now succeeds:
Step 5 — run and verify:
Then read hello_world_result.txt:
Put the program in the project folder, not in a temporary folder.
Sheerpower refuses to run a program from %TEMP% (a security
guard against dropped scripts) and stops with Cannot run SheerPower
from a TEMPORARY folder — under automation that looks like a
hang, because the message is a dialog waiting to be dismissed.
The output is correct. The cycle is complete.
After a clean build, test web programs in a browser. The compiler verifies code correctness, but only the browser reveals user-interface issues such as wrong layout, missing data, broken buttons, or incorrect error messages.
Most web programs begin by making sure the SPINS web server is running:
To run a web program:
The program will print a URL to its GUI console, typically:
Open that URL in a browser and test the main workflow and edge cases:
The full vibe coding cycle is:
.spsrc file.
/validate.
One edit, one compile, one test. Keep the loop tight.
When an AI is helping to write or modify Sheerpower programs, it needs a simple and reliable development loop:
This loop works best when both compile errors and runtime errors are visible to the AI. The AI should be able to run the program, see what happened, and make the next correction without needing a human to press keys at the console.
Programs that are being tested by an AI should normally use:
With option abort, any uncaught runtime exception causes
the program to exit with a failure status. This lets the AI detect
that the run failed. When the run fails, Sheerpower also writes out an error file
containing the state of the program when it failed.
Without option abort, Sheerpower may stop at the console
prompt so a human developer can debug interactively. That is useful for a
developer, but not useful for an unattended AI test run.
Some console programs are interactive. They use
line input to ask the user for information:
Run normally, this program waits for a person to type a name. That is correct for human use, but it prevents an AI from running the program in a fully automated way.
The /headless option solves this. Under
/headless, line input reads from piped
stdin instead of the keyboard. Each input line answers one
line input, in order.
This means the same ordinary interactive program can be used in two ways:
/headless.No special AI mode is needed. No duplicate test version is needed. The same source code is tested and used.
When a program is run with /headless:
line input reads from piped stdin instead of the
keyboard.
line input consumes one input line.
line input returns EOF as true
instead of blocking.
print writes to capturable stdout.
delay does not pause; it becomes a no-op.
input and key input read from piped
stdin too (one line per prompt; a key is the line's first character),
and every prompt and answer is echoed to stdout as on a screen.
_headless is 1
under /headless and 0 in a window — a true-or-false value,
like _debug and _test. A test that only makes sense
in a window (one that types into a real screen with
set window: typeahead) guards itself with
if _headless then exit routine, so one test file serves both
ways of running.
End-of-input behavior is especially useful for prompts such as:
In a normal console, the user presses ENTER. In a headless test,
there may be no more input. In that case, dummy$
becomes "", and the program continues instead of
blocking forever.
The recommended AI vibe coding cycle using the /Headless Option is:
.spsrc file./validate.line input.
/headless.
Once tests pass, periodically compile with option insights
and review <program>_insights.json for
complexity/unused-code drift. Run this at feature-completion, or
when a routine keeps needing edits -- not every cycle.
The goal is simple:
Source file: greet.spsrc
Compile:
Input file: greet_in.txt
Run headless:
Expected output in greet_out.txt:
The program has now been tested automatically, even though it was written as a normal interactive console program.
If a program has more than one line input, the input
file should contain one line for each prompt.
Input file:
Run:
Expected output:
The first input line is consumed by the first
line input. The second input line is consumed by the
second line input.
When testing with /headless, use print for
ordinary program output:
The AI can read the captured stdout file and verify the result.
Avoid writing temporary result files from inside the program unless creating a file is the actual purpose of the program. For ordinary console tools, stdout is the cleanest test output.
Use this form to run a headless test:
The < redirects the input file into the program. The
> captures stdout into the output file.
Quote paths that contain spaces:
Do not rely on PowerShell pipeline capture for this workflow. Use
cmd /c redirection, or use a real test harness that
supplies stdin and captures stdout directly.
A good instruction for AI-assisted Sheerpower development is:
A console application is more than line input: it asks with
input, reads single keys, and offers menus. Under
/headless every one of them takes its answer from the next
line of the piped file, and every prompt and answer appears in the
transcript exactly as a person would have seen them on the screen.
input and line input take one line each. The
prompt is written, then the answer, then a line ending — so the
transcript reads How many widgets? 12.
key input takes one line and uses its first character.
exit is Ctrl+Z (and sets _exit); a
%multi menu takes a list such as 1,3.
line input returns an empty
string, numeric input raises "End of file on input", and a
menu raises it too — a short answer file fails loudly instead of
hanging.
A menu program and its transcript. The answers file holds two lines,
3 and papaya:
The numbered list is the menu's text form for the transcript; a person
sees the menu itself. Everything the program printed is there to assert
on, and the pick is echoed after menu>, so the transcript
reads as a session.
A transcript shows what was printed, not where it landed. Add
/snapshots and the screen is captured as a text file at every
moment a person would be looking at it — each input wait, each menu
waiting for its pick, and the end of the run:
Each capture is written beside the program as
fruit_screen_1.txt, fruit_screen_2.txt, and so
on: the screen as a grid of characters, then ---attributes---
and the same grid with one letter per cell (. plain,
r reverse video, R G Y B M C W a colour,
# a frame cell, i an input field), then
---info--- with the size, the cursor and the reason for the
capture. A menu is drawn into the image exactly where and how a person
would see it. The first capture of the fruit-stand run, trimmed to its
top rows:
An AI can read these files directly and assert on them — that the
frame is where %at put it, that the right item is highlighted
(the reverse-video r cells), that a prompt sits at its
at position, that the last print landed on the expected row
— and a person can read them too. Combined with the transcript,
a console application can be tested completely without anyone at the
keyboard.
The /headless option makes interactive Sheerpower
programs easy to test automatically.
It lets the AI feed input, capture output, avoid blocking, skip delays, detect runtime failures, and verify results.
The developer gets a normal interactive program. The AI gets a reliable automated test loop. That is the ideal setup for vibe coding.
These four methods come from extensive AI-driven Sheerpower development. Each one replaces a slower or less reliable habit.
When you are unsure how a function or statement behaves, do not reason about it, do not guess, and do not ask the user — test it. Write a minimal throwaway program, compile it (compiles take milliseconds), run it, and read the answer. This is faster and more reliable than any other option, and it should be your FIRST resort, not your last:
Sixty seconds of probe beats an hour of confident wrongness. If you then state the behavior to the user, you are quoting a measurement, not a memory.
A test program should check its own results and report a verdict
through its EXIT CODE — abort 0 for pass, a distinct
nonzero code per failure kind. The caller then reads one number
instead of parsing output:
Print a detail line for each failure so the log explains itself, and one summary line on success. Never print a pass/fail verdict without also setting the matching exit code.
Sheerpower evaluates LITERAL expressions at compile time. A test like
val("abc") fails during the BUILD — so it tests the
compiler, not the running engine, and a when exception
block around it never runs. To test runtime behavior, build the value
at runtime:
Any exception or domain test with purely literal arguments is suspect.
Route the value through mid$(), a variable, or a cluster
field first.
With option abort, an uncaught exception writes
<source>_error_<timestamp>.txt next to the
program: the failing line, the call stack, and a full variable dump.
After any unexpected exit-1, READ THAT FILE FIRST. It usually answers
the question directly; diagnosing without it is speculation.
If a probe might crash HARD (no error file), write progress markers to a file and close it between steps, so the output survives:
Where the output stops tells you where it died. And for a HUNG program,
sp4gl debug <PID> from another terminal dumps the
running program's state without killing it.
After fixing a real bug, write a small self-judging test that FAILED before the fix and passes after — and keep it. A fix without a test is a fix that can silently unhappen. Verifying the test fails on the broken version is the important half: a test that never failed has proven nothing about its own ability to detect the bug.
(Show/Hide Key Takeaways)call keyword. Invoke routines by name with with and returning.end ___. No braces. for loops close with next varname.select case should include case else.retry re-runs the failing statement. continue skips it. Both are exception-handler keywords only.repeat do for do loops; iterate for for for loops.exit do or exit for. Never break.exit routine. Never return.?. String names end with $.for each, access fields as clustername->field — there is no iterator variable.collect cluster before every for each.findrow() for single-row lookup instead of scanning a whole cluster.VIEW target INTO source, PIECE sep, MATCH n — zero-copy string parsing.sprintf$() formats such as %, %p, %m, and %w.f$("... [[expr]] ..."), a sprintf$() inside a slot for formats. Inside a routine the slots see the routine's own names (its with parameters and private variables) as well as module variables; a name nothing assigns raises UNKNOWNVAR.%text blocks: a literal costs nothing to assign or pass.split() (into a dim words$(*) array) and join$() for delimited strings; element$() / elements() for one piece at a time.size() for cluster row counts and str$() for number-to-string conversion.x(i) with parentheses: fill, filter(), sort(), stats$sum(), x * 2, ucase$(x$) and x(x < 0) = 0 replace the index loops; dim x(*) grows with x(*) = v; an array result needs an array target.values(*) passes an array by reference._integer / _real / _string$ carry a built-in's second result -- read them in the next statement.open file ch: name url$, http-post:// with data and headers, ask #ch: status; a reply of 400 or more raises with the body in _string$; set session for a service called many times.print list is padded; a fraction below 1 prints 0.25.pass for operating system commands.cgi://, line input, getsymbol$(), and [[%spscript]] pattern.local routines share the parent's scope. private routines are isolated units with their own parameters.call return throw raise catch except new this null void function def fn break end for endif.|
Hide Description
|
|
|
Enter or modify the code below, and then click on RUN |
|
Looking for the full power of Sheerpower?
Check out the Sheerpower website. Free to download. Free to use. |