Popup YouTube Video
Sheerpower Logo

Routines and Variable Scoping


Sheerpower: Routines and Variable Scoping

As projects grow, it becomes essential to break code into smaller, reusable building blocks. In Sheerpower, these are called routines. They not only organize your code, but also control how variables are shared, isolated, or reset between calls.

Routine names: Routine names may contain only letters, numbers, and underscores (A—Z, 0—9, _). They must begin with a letter and include at least one underscore. For example, do_taxes is valid, but taxes is not. Routine names are case-insensitive. By design, Sheerpower never uses underscores in its own keywords, so your routine names will never collide with language features.

Types of Routines in Sheerpower

Global Routines: The default type. All global variables are accessible inside them.

Private Routines: Use their own variable namespace. By default, variables declared inside are local to that routine. If you need to access a global variable, prefix it with main$.

Accessing Globals and Functional Style

Problem: In larger applications, routines may need access to shared data. However, unrestricted access to global variables introduces hidden dependencies and reduces predictability.

Solution: The main$ prefix provides an explicit way for private, scoped, or local routines to access global variables when necessary. At the same time, private routines maintain an isolated namespace, making them well-suited for a functional-style approach based on inputs, computation, and explicit returned results.

Efficiency: Isolated routines reduce unintended side effects and eliminate unnecessary data sharing. Explicit inputs and outputs improve readability, simplify reasoning, and make code easier to test and maintain.

Takeaway: Private routines support a functional-style design, but do not enforce it. For best results:

  • Use private routines for input — computation — output
  • Treat main$ as an explicit escape from isolation
  • Avoid hidden state, I/O, and static variables unless required

Scoped Routines: Similar to private routines, but all non-static variables are cleared on entry and again on exit. Prefix any variable with static to preserve it across calls.

Local Routines: Logically associated with a parent routine, local routines share the parent’s scope and can only be called from within that routine. This allows complex logic to be broken into smaller, named steps without cluttering the parent routine, without parameter passing, and without runtime overhead from copying data. Local routines are called using the local prefix before their name and are normally declared immediately after the end routine of their parent.

Note: Local routines can only be called from within private or scoped parent routines. The compiler enforces this restriction, preventing accidental coupling or misuse. Requiring the local prefix at the call site also makes it immediately clear that a local routine is being invoked, improving readability and avoiding ambiguity.

Keeping Large Routines Readable

Why Local Routines Exist: As routines grow, it is natural to want to split them into smaller, clearer steps. In many environments, doing so requires stopping to decide which values should be passed, how those values should be organized, and what scope each helper should have. This extra decision-making can interrupt development flow at the very moment when clarity is most needed.

Local routines address this by allowing a routine to be split into named steps without redesigning data flow. They solve the problem by letting you organize logic into named steps while staying entirely within the same variable scope. This keeps your attention focused on the problem being solved.

Takeaway: Local routines turn large routines into smaller, easier-to-read and easier-to-maintain routines, without the cognitive or runtime overhead common in nested code.

process_order with qty 10, price 1.50, returning total print total // Parent routine stays short and readable private routine process_order with qty, price, returning total total = 0 is_vip? = true local calculate_totals local apply_discounts end routine // Local routines (share scope, parent-only) local routine calculate_totals total = qty * price end routine local routine apply_discounts if is_vip? then total = total * 0.9 end routine

ONCE — Run a Routine Exactly One Time

Some routines are intended to execute only once during the lifetime of a program. These are typically initialization or registration routines.

Rather than manually managing flags such as initialized?, Sheerpower provides a declaration-level solution:

routine routine_name once routine routine_name once, with a, b, returning x, y private routine routine_name once scoped routine routine_name once local routine routine_name once

When a routine is declared once, its body executes only on the first call. All subsequent calls return immediately at entry, before any body statements execute. The routine is never re-entered after its first execution, and any side effects occur only once.

Note: Returning values are cached from the first execution and reused on all later calls.

Problem: Initialization logic is often protected by ad-hoc flags scattered through code—cluttering it.

Solution: Declare the routine with once. The runtime enforces single execution without the need for code-cluttering ad-hoc flags.

Efficiency: After the first call, the routine short-circuits at entry — no body execution, no extra checks inside the routine.

Note: If a guard clause or internal error handling causes an early return on the first call, the routine is still marked as executed and will not run again. Ensure preconditions are met before the first call, not inside it.

Takeaway: Lifecycle intent belongs in the language, not in scattered boolean guards.

Execution Semantics

  • First call: Executes normally from top to end routine.
  • Later calls: Immediately return at entry.
  • Lifetime: The "already called" state persists for the lifetime of the running program.

Internally, the runtime marks the routine as executed in its control structure. Later calls short-circuit before the first statement runs.

Example — One-Time Initialization

private routine build_tax_table once main$tax_rate_by_state$ = "CA=0.0725|NY=0.0400|TX=0.0625" end routine build_tax_table build_tax_table // Returns immediately without executing

No manual initialized? flag is required. The declaration guarantees correctness.

Example — ONCE with Returning Values

routine get_build_info once, returning version$, build_date$ version$ = "Sheerpower 1.0" build_date$ = "2026-02-28" end routine

(Show/Hide Sheerpower ONCE Takeaways)

Invoking Routines with Named Parameters

Routines always use named parameters. Do not prefix the call with call. Instead, write the routine name directly, followed by with (input) and returning (output) parameter assignments. Named parameters eliminate positional errors and make refactoring safer when parameter lists change.

Input parameters in the with clause are read-only — the compiler prevents any modifications. They are passed by reference for efficiency, but cannot be changed within the routine. A routine may accept up to 16 input parameters using the with clause.

Output values are specified using the returning clause. Up to 16 values may be returned, and each returned variable must be explicitly named in the call. Together, the with and returning clauses allow a maximum of 32 parameters per routine.


The 16-parameter limits are intended to keep routine interfaces readable and maintainable. If a routine requires more than 16 inputs or outputs, consider passing a cluster instead (structured collections of related values covered in the Data Structures tutorial).
routine calculate_area with length, width, returning area area = length * width end routine calculate_area with length = 10, width = 5, returning area room_area print "The area is: "; room_area

Output:

The area is: 50

Basic Return Example

routine calculate_area with length, width, returning area area = length * width end routine calculate_area with length = 10, width = 5, returning area room_area print "The area is: "; room_area

Returning Multiple Values

routine split_name with full_name$, returning first$, last$ first$ = element$(full_name$, 1, " ") last$ = element$(full_name$, 2, " ") end routine split_name with full_name$ = "Anna Rivera", returning first$ fname$, last$ lname$ print "First: "; fname$ print "Last: "; lname$

Overall Benefits

Sheerpower routines combine clarity, safety, and flexibility. Strong scoping rules reduce accidental variable misuse, while named parameters make code self-documenting and maintainable.

Routine Parameters in Detail covers parameter passing in much more detail.

Flow Control Within a Routine

Sheerpower gives you three statements for managing execution flow within a routine without relying on exceptions, flags, or deeply nested conditionals.

  • exit routine — Exit the routine immediately, returning control to the caller before reaching the routine’s normal end.
  • repeat routine — Restart execution of the routine from the beginning without resetting parameters or variables.
  • guard condition — Enforce a required condition; if it is not met, the routine exits immediately.

Example 1: exit routine — Early, Intentional Exit

Use exit routine when continuing execution no longer makes sense, but the situation is not exceptional.

routine print_discount with price, returning discounted if price <= 0 then // Invalid input -- nothing to do discounted = 0 exit routine end if discounted = price * 0.9 end routine print_discount with price = -50, returning discounted d print "Discounted price: "; d

Example 2: guard condition — Enforcing Preconditions

Use guard to make required conditions explicit and self-documenting.

routine process_payment with amount guard amount > 0 print "Processing payment of "; amount // ... payment logic ... end routine process_payment with amount = -20

Example 3: repeat routine — Controlled Retry Without Reset

Use repeat routine when a routine must retry its logic while retaining parameters and variable state.

routine read_until_valid with returning value static attempts attempts = attempts + 1 print "Attempt "; attempts input "Please enter a number between one and ten": value if value < 1 or value > 10 then print "Invalid number:"; value;" Please try again." repeat routine end if end routine read_until_valid returning value n print "Accepted value: "; n

Example 4: Combining guard and exit routine

This example demonstrates how guard enforces required inputs, while exit routine handles valid early-exit cases without error handling or deep nesting.

routine safe_divide with numer, denom, returning result result = 0 guard denom <> 0 if numer = 0 then exit routine end if result = numer / denom end routine safe_divide with numer = 10, denom = 0, returning result r print "Result: "; r

YIELD and CONTINUE — Generator-Style Routines

Sheerpower supports generator-style routines using yield and continue routine_name. This lets a routine suspend execution, return to the caller, and later resume exactly where it last yielded.

This is useful for incremental processing, streaming data, stateful iteration, and cooperative workflows where a routine produces results over time rather than all at once.

How yield Works

  • yield causes the current routine to return immediately to its caller.
  • The routine’s execution state is preserved — including local variables, static variables, and the current instruction position.
  • When resumed, execution continues at the statement immediately following the yield.

continue routine_name

To resume a previously yielded routine, use:

continue routine_name

The routine_name must be the name of a routine that has already executed a yield. Execution resumes from the last yield point inside that routine.

Example: Simple Generator Routines

generate_numbers with starting 1 do print counter continue generate_numbers if not _yield then exit do loop generate_numbers with starting 11 do print counter continue generate_numbers if not _yield then exit do loop read_file do print line$ continue read_file if not _yield then exit do loop stop // When a routine is called, execution starts at the beginning. // When YIELD is encountered, the routine returns and remembers where it // was. When CONTINUE is used, execution resumes from the last yield. routine generate_numbers with starting print "starting the count generator" counter = starting yield counter = counter + 1 yield counter = counter + 1 yield end routine routine read_file print "Opening the file to read" open file in_ch: name "@access.log" do line input #in_ch, eof eof?: line$ if eof? then exit do yield loop close #in_ch end routine

Key Characteristics

  • yield does not reset parameters or local variables.
  • Static variables naturally persist across yields.
  • Multiple yield statements may appear within the same routine.
  • Control flow remains explicit and easy to reason about.

When to Use yield

  • Streaming or incremental data processing
  • Stateful iteration without global variables
  • Breaking long computations into cooperative steps
  • Producing values over time instead of all at once

Unlike exception-based generators or hidden iterator objects in other languages, Sheerpower’s yield and continue statements keep control flow explicit, readable, and deterministic.

Yield Status: _yield

_yield is a built-in boolean status variable that indicates whether the most recent routine call or continue operation exited via a yield.

  • _yield is true if the routine executed a yield and can be continued.
  • _yield is false if the routine completed normally and cannot be continued.

_yield is set when a routine exits and reflects the outcome of the most recent routine call or continue operation.

This allows callers to determine whether a yielded routine is still active and decide whether further continue operations are valid.

continue routine_name is valid only if that routine has previously executed a yield and is currently suspended. If the routine is not in a yielded state, continue raises a runtime error.


Error Status & Routine Name: _error and _routine

_error is always false on routine entry and becomes true when you execute set error on; callers can check _error after the call. Also see Exception Handling.

Inside a routine, _routine provides the routine's name as a string.

// Divide-by-zero guard using _error and exit routine routine safe_divide with numer, denom, returning quotient if denom = 0 then set error on exit routine end if quotient = numer / denom end routine // Caller: just check _error safe_divide with numer = 10, denom = 0, returning quotient q if _error then print "Error: divide by zero" else print "Quotient = "; q end if
// _routine returns the current routine name (string) routine reconcile_orders with date$, returning count print "I am in the routine called "; _routine // ... work ... count = 42 end routine

(Show/Hide Routine Syntax & Scoping 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.