Popup YouTube Video
Sheerpower Logo

Variables, Data Types, Declaring Variables, Comments, and Database Access


The Fundamentals of Sheerpower Programming

Welcome to the fundamentals of Sheerpower! In this tutorial, you'll learn how to store information in variables, understand the different types of data you can use, and how to document your code with comments. Along the way, you'll also see how Sheerpower's naming conventions and type system keep programs clear and easy to read.

1. Understanding Variables

Variables are containers for storing data. In Sheerpower, a variable name must begin with a letter and can contain letters, numbers, and underscores. Variable names are case-insensitive and typically use underscores to improve readability, like tax_rate or full_name$.

Throw-away identifiers: Sheerpower also reserves two special throw-away identifiers: _ for numbers and _$ for strings. Assign to them when you want an expression to run and want a visual signal that you are intentionally ignoring its return value. This makes your intent clear at a glance.

For example, the join() function quickly joins strings together and returns the final length. If we don't need the length, we can do this:

a$ = '' _ = join(a$, 'Admin: ', name$) // build string, deliberately discard returned length

2. Sheerpower's Core Data Types

Every variable holds a specific type of data. Sheerpower's core data types are:

STRING

For text.

"Hello!"

a$ = %text
Sally
Tom
Mary
%end text

REAL

For numbers.

123.45

4.123e50 (Scientific Notation)

BOOLEAN

For true/false.

TRUE

DYNAMIC

Inherits the data type of the last value assigned. Rarely used.

INTEGER

Rarely used. Use REAL instead. Sheerpower's REAL type handles whole numbers perfectly, making a separate INTEGER type unnecessary for most applications.

42
Design Rationale: Why use dynamic data types?
  • Adapts to mixed data: Perfect for code that handles user input, file data, or API responses that might change type from one call to the next.
  • Type stability after assignment: Once a value is assigned, the variable behaves as that type until it is assigned something else.
declare dynamic x x = "hello" // now x is a STRING x = 3.14159 // now x is a REAL

3. Two Ways to Create Variables

Variables can be created either automatically (by using type suffixes) or explicitly (with the declare statement). Sheerpower supports both styles, so you can choose the one that fits your code and team conventions.

  • Automatically (with suffixes): Concise and expressive. A suffix indicates the variable's type and often improves readability:
    • $ for a STRING (e.g., name$)
    • ? for a BOOLEAN (e.g., found?) — read it as a question
    • % for an INTEGER (e.g., count%)
    • No suffix for a REAL (the default for numeric values)
    • # for a REAL, stated explicitly (e.g., rate#) — the same type as no suffix, but visible. It matters most on routine parameters: a bare parameter name adapts to whatever the caller passes, while routine area_of with width#, height#, returning area# says every one of them is a real. x, x#, x?, x% and x$ are all different variables.
    Many developers prefer suffixes because they make the type and purpose of a variable obvious at a glance, especially for booleans and strings.
  • Explicitly (with declare): Can improve clarity, organizes declarations, and reduces errors. This is especially helpful in larger programs or shared codebases.
Strict Mode Option:
Add OPTION REQUIRE DECLARE to enforce explicit declarations. The compiler will raise an error if a variable is used without being declared first.

This ensures tighter control and catches typos or undeclared variables early. You can still use suffixes with declare if you like the clarity they provide (for example, declare boolean found?).
// Automatically created variables full_name$ = "Sally Sue" is_active? = true total_cost = 19.95 // Explicitly declared variables declare string shipping_address declare real weight declare boolean item_available shipping_address = "123 Main St" weight = 2.5 item_available? = true

4. A Special Note on REAL and High-Precision

Sheerpower's REAL type is designed for exact decimal arithmetic. Unlike IEEE floating-point types used in most languages, REAL values deliver 16 digits of true decimal precision, avoiding the rounding errors that plague floating-point math. This makes REAL ideal for financial, accounting, and other applications where accuracy is critical.

Each REAL is stored as multiple 64-bit integers: some for the integer part (IP) and one for the fractional part (FP), scaled by 1016. For example, 13.599 is internally represented as:
IP = 13
FP = 5990000000000000

This design eliminates floating-point approximation and enables efficient comparisons and arithmetic. Expressions like (0.1 + 0.2) - 0.3 evaluate to 0 in Sheerpower.

By contrast, the same expression in IEEE math yields:
0.00000000000000005551115123125783

Range & Scientific Notation:

For statistics and large-scale data, Sheerpower supports scientific notation (e.g., 4.123e50). The REAL type provides a high-precision decimal numeric type supporting up to 54 digits before and 16 digits after the decimal point. Within this range, decimal values are represented exactly, so calculations are predictable and do not introduce binary floating-point rounding error.

When greater dynamic range or additional decimal precision is required, REAL automatically uses scientific notation while retaining up to 64 significant decimal digits. The exponent can have a magnitude of up to 1,000,000,000 (a one followed by a billion zeros), allowing REAL values to represent extremely large or extremely small numbers.

Fractions, Complex Numbers, and the Two Specials:

The same REAL holds three more kinds of value, with no declaration and no second type. A divide of two whole numbers whose quotient does not terminate keeps the exact fraction: a = 1 / 3 is one third, not a rounded decimal, so a * 3 is exactly 1, and it prints as the decimal you expect (.3333333333333333) until you ask for the fraction with fraction$(a). See Exact Fractions: the RATIONAL Mode.

A complex number is written with the i suffix: z = 3 + 4i. Arithmetic, abs(z) (which is 5 here), sqr(), exp(), log() and the trig functions all take one, and a negative square root that raises an error on a plain real works once you say you mean the complex plane: sqr(-4 + 0i) is 0+2i. See Processing Complex Numbers.

Finally, a REAL can hold NaN and Inf. They are never produced by ordinary arithmetic on finite values (1 / 0 still raises), but they come in through data — a val("NaN"), a CSV cell, a float field of a table — and propagate the IEEE way: x = _inf then x - x is NaN, and isnan(), isinf() and isfinite() find them.

a = 1 / 3
print a * 3                ! 1
print fraction$(a)         ! 1/3
z = 3 + 4i
print abs(z)               ! 5
print sqr(-4 + 0i)         ! 0+2i
print typeof$(a)           ! Name:A, Dtype:Real, *Rational*
print typeof$(z)           ! Name:Z, Dtype:Real, *Complex*

typeof$() names the mode a value is in (*Narrow*, *Wide*, *Sci*, *Rational*, *Complex*, *NaN*, *Inf*); the engine moves between them by value, and programs never have to.

See Internals: REAL and String Data Types.
(0.1 + 0.2) - 0.3
Sheerpower: 0
Other Languages: 0.00000000...555

Sheerpower is strongly typed

Sheerpower does not convert between types on your behalf. A STRING value cannot be assigned to a REAL variable, even when it looks numeric—"123.45" is still text, not a number. Assigning it directly is a compile-time error; use VAL() to convert it explicitly. DYNAMIC variables (documented above) are the one exception.

5. Constants Custom Data Types

Sheerpower lets you define your own constants and custom data types. These features make code more readable, expressive, and secure.

Constants

// A constant's value cannot be changed after definition. const everything = 42

Freezable Variables — Runtime Immutability

A const is fixed at compile time. A variable can change forever. Configuration data lives in the gap between them: it is loaded at runtime — from a file, a logical, a table — and from that moment on it should never change again.

Freezable variables close that gap. Load the value normally, then freeze it. From then on, any attempt to change it raises a clear error instead of silently corrupting your configuration.

declare freezable timeout, api_base$ // ... load the configuration file, assign the values ... timeout = val(config_line$, 1) api_base$ = element$(config_line$, 2) freeze timeout, api_base$ // From here on, these cannot be changed -- from anywhere.

Problem: Configuration values are loaded once and trusted everywhere. But nothing stops a stray assignment — a scratch variable that happens to share the name, a routine writing where it shouldn't — from silently changing them mid-run. The symptom appears far from the cause and is miserable to track down.

Solution: Declare the variable freezable, load it, then freeze it. Any later write raises a catchable error naming the variable, and the value is untouched.

Efficiency: The frozen check rides the engine's existing assignment path — reads cost nothing, and writes cost one flag test. Freezing is virtually free insurance.

Takeaway: If a value should never change after loading, freeze it. The bug you would have spent an afternoon hunting becomes a one-line error message at the exact statement that tried it.

Declaring a Freezable Variable

Only variables declared freezable can be frozen. This is the gate: the variable's author decides whether freezing is part of its contract, and no other code can freeze a variable that was never meant to be frozen.

declare freezable timeout // a freezable REAL declare freezable api_base$ // a freezable string freeze some_other_var // COMPILE ERROR: // "This is not declared as FREEZABLE"

Cluster members may be declared freezable too, so individual fields of a configuration cluster can be protected.

FREEZE and UNFREEZE

freeze and unfreeze are runtime statements — a variable is frozen from the moment the freeze statement executes, not from where it appears in the source. A variable may be assigned freely before freezing, including many times during a loading loop.

declare freezable retries retries = 3 freeze retries retries = 5
?? Cannot assign into this variable>> RETRIES is frozen; it can't be assigned a value.

The error is catchable with when exception, and the frozen value is untouched. Every way of writing a variable is covered — plain assignment, retries++, join() into a frozen string, lset, substring assignment (frozen$[1:2] = "ab"), a routine's returning value, and even writing through a view into a frozen string.

typeof$() reports the state at any time:

declare freezable f f = 10 freeze f print typeof$(f)
Name:F, Dtype:Real, *Freezable*, *Frozen*

The Unfreeze Window — Reloading Configuration

unfreeze re-opens a frozen variable for writing. Use it to create a small, deliberate window for changes — such as reloading a configuration file — and refreeze immediately:

routine reload_config unfreeze timeout, api_base$ // ... re-read the configuration file, assign the values ... freeze timeout, api_base$ end routine

Keep the unfreeze and the matching freeze in the same routine, and keep the window small. A useful property of this habit: searching your source for unfreeze finds every place a frozen value can legitimately change. Anything else raises an exception.

PERMAFREEZE — The One-Way Ratchet

permafreeze freezes a variable permanently for the rest of the run. It cannot be thawed — unfreeze on a permafrozen variable raises an error rather than quietly reopening it.

declare freezable license_key$ license_key$ = load_key$ permafreeze license_key$ unfreeze license_key$ // raises an exception-- the ratchet does not reverse

The distinction between the two is worth stating plainly: freeze protects against accident — any code could still unfreeze it deliberately. permafreeze protects against intent — once set, nothing in the program can change the value for the rest of the run. Use freeze for values that may legitimately reload, and permafreeze for load-once values that must survive the whole run untouched.

Freezing Whole Arrays and Clusters

The things most often loaded at runtime and never meant to change are not single values but tables: a list of tax bands, the day names, a lookup cluster read from a file at startup. Those can be frozen as a whole. Declare the array or cluster freezable after the dim or cluster statement, and freeze it once it is loaded.

dim rates(3) fill rates with 0.1, 0.2, 0.3 declare freezable rates freeze rates rates(2) = 9 ! raises: RATES is a frozen array cluster cities: city$, population add cluster cities cities->city$ = 'Cebu' cities->population = 1_000_000 end add ! ...or cluster input from a file declare freezable cities freeze cities add cluster cities ! raises: CITIES is a frozen cluster cities->city$ = 'Davao' end add

A frozen array is frozen in contents and shape: an element write, an append with (*), any fill, redim, or an array-expression assignment raises the same exception a frozen variable does, and the array is untouched. Reading it, size(), the statistics functions, and passing it to a routine as a with values(*) parameter are all free. The freeze travels with the data: a routine that receives the array as a returning parameter raises on its own write.

A frozen cluster is frozen in contents and row set: a field write on any row, add cluster, delete from cluster, reset cluster, cluster input and copy cluster into it all raise. Everything that only looks stays free — collect with its include and sort by, for each, findrow, set cluster: row n. One rule worth stating: a store inside a collect block, such as words->count = _extracted, is a field write and raises — that is exactly the quiet mutation freezing exists to catch. A cluster handed to a routine raises in the routine too. Freezing a member and freezing the cluster are independent: unfreezing the cluster leaves a member you froze separately still frozen. permafreeze works on both, with the same one-way rule. typeof$ reports all of it: typeof$(rates) gives Name:RATES, Dtype:Real, *Array*, Dims:1, Bounds:1:3, Size:3 , *Fixed*, *Freezable*, *Frozen*, and typeof$(cities) gives Name:CITIES, *Cluster*, Rows:2, *Freezable*, *Frozen*.

Details Worth Knowing

  • Freezing a never-assigned freezable is allowed — it freezes at empty ("") or zero. Occasionally useful for "this feature stays off for this run."
  • Reading a frozen variable is completely normal and costs nothing. Freezing only affects writes.
  • Crash dumps and show all report the frozen state, so a "why won't this assign?" question answers itself in the diagnostics.

The professional pattern: combine freezing with private routine style. Globals are accessed explicitly with the main$ prefix, so every access is visible and searchable — and freezing makes those globals immutable after loading. Configuration that is explicit, auditable, and untouchable: load it, freeze it, and trust it for the rest of the run.

(Show/Hide Sheerpower FREEZABLE Takeaways)

Custom Data Types

// Define a new type named 'money' based on REAL type real money // Declare a variable using the new type declare money monthly_salary monthly_salary = 5000.00

This makes code more self-explanatory—using money signals financial data, not just a number. Custom data types are enforced at compile time, as the next example shows.

type real money type real feet declare money my_salary declare feet room_width my_salary = 10 room_width = my_salary // this will generate a compile-time error

The compiler replies:

?? Declared data type mismatch >> ROOM_WIDTH is of type FEET, MY_SALARY is of type MONEY

Secure Custom Types

// Create a secure string type for sensitive data type nodump string secret // Declare a variable using the secure type declare secret password password = "xxyyy"

If the program crashes, password appears as <nodump> in debug output—helping to protect secrets from accidental exposure.

Why It Matters:
Problem: Debug output and crash logs can accidentally expose sensitive data like passwords or tokens.

Solution: The nodump modifier ensures those values are hidden, showing <nodump> instead of the actual contents.

Efficiency: This adds no runtime overhead—security is enforced automatically by the runtime.

Takeaway: Use nodump for secrets or private data to keep applications safe without extra coding effort.

6. Formatting Your Output

// This variable prints with a $, comma, and 2 decimal places. declare format "$%.2m" price price = 12300.456 print "Total price: "; price // Output: Total price: $12,300.46

Format Syntax

  • $ — Literal dollar sign
  • %.2m — Two decimal places in monetary format
declare format "%.1f%%" success_rate success_rate = 98.73 print "Success: "; success_rate // Output: Success: 98.7%

7. A Quick Look at Database Variables

Sheerpower provides a secure way to access table fields using table_name(field_name). This separates code from data and protects against SQL injection.

Payroll Table
id name salary
payroll(salary) = 55000.00

Database access is an advanced feature. To learn more, see the Integrated Database Access Overview tutorial.


8. Documenting Your Code with Comments

Comments are ignored by the compiler and help others understand your code. Use // (preferred) or ! to begin a comment.

// This is a comment. It explains the next line. const tax_rate = 0.08 // 8% sales tax // Good comments explain the "why," not just the "what." // Calculate the final price including tax. final_price = subtotal * (1 + tax_rate)

9. Converting Between Strings and Numbers

Sheerpower enforces variable types. You cannot assign a STRING directly to a REAL or vice versa — use conversion functions.

Converting a String to a Number

declare real price declare string input$ input$ = "12.95" price = val(input$)

For a complete walkthrough, see Strings to Reals -- Extracting Numbers From Text.

Converting a Number to a String

declare string output$ declare real tax = 0.075 output$ = str$(tax)

Conversions like val() and str$() are commonly used when reading user input, importing raw data, or constructing strings for display.

10. Putting It All Together

const sales_tax_rate = 0.08 declare string item_name$ declare real item_price, quantity item_name$ = "Super Gadget" item_price = 29.95 quantity = 2 subtotal = item_price * quantity final_total = subtotal * (1 + sales_tax_rate) print sprintf$("Item: %, Qty: %, Total: $%.2m", item_name$, quantity, final_total)

What You've Learned

  • Variable naming rules and optional type suffixes
  • Core types: STRING, REAL, BOOLEAN, with optional INTEGER
  • Dynamic variables that adapt type from assigned values
  • REAL type with exact 16-digit decimal precision
  • Automatic vs. stated declarations
  • Strict mode with OPTION REQUIRE DECLARE
  • Constants and custom types (including nodump)
  • Formatting with declare format
  • Intro to database variables
  • Comments (! and //)
  • Conversions with val() and str$()
(Show/Hide Variable & Data Type Takeaways)

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.