String Views — Zero-Copy, Live Slices
of Strings
The VIEW statement creates a dynamic, zero-copy
reference into a string. Unlike MID$(),
BETWEEN$() or PIECE$(), which return static copies, a
VIEW binds directly to the original source
variable. A VIEW is always safe: it never becomes invalid. If the source changes,
the view automatically rebinds on the next reference.
Lazy updating and caching:
Views re-evaluate dynamically, but only when referenced. When the source variable is
assigned a new value, a view does not immediately
recalculate its start or end positions. Instead, these are
evaluated the next time the view is referenced (a
lazy update).
To avoid unnecessary work, a view also caches its resolved
slice. If the source has not changed since the
last evaluation, the cached start/end positions of the
view are reused and no recalculation occurs. This keeps
repeated references fast and allocation-free.
When the source string is modified (whether through direct assignment
or write-through from another view), all cached offsets for every view
derived from that source are invalidated. Each view re-evaluates its
slice the next time it is referenced, ensuring all views always reflect
the current state of the source string.
These rules apply to any change in the source. For example,
if the source is set to the null string, then on the next
reference every view derived from it will also evaluate as
a null string.
data$ = "hello"
VIEW v$ INTO data$, MID 2, 3
// v$ hasn't calculated offsets yet
PRINT v$ // NOW it evaluates to "ell" (and caches it)
PRINT v$ // Reuses cached offsets - no recalculation!
data$ = "world"
PRINT v$ // Re-evaluates to "orl" because source changed
Views of Views — Chained Views
Sheerpower supports chained views — you may
create a VIEW using another view as its source.
This lets you refine a slice in stages without allocating any
intermediate substrings.
Internally, a chained view always resolves to the original base
string. Offsets are composed through the chain and cached, so the
final view still points into the same underlying buffer and access
is very fast.
-
No copies are made — all views ultimately reference one base
string.
-
Views still update lazily — they re-evaluate only when
referenced.
-
Each view keeps its own cached offsets for fast repeated access.
Example: Chained Parsing Without Copies
data$ = "[alpha,beta,gamma]"
// First view isolates the bracketed region
view list$ into data$ between "[", "]"
// Second view refines the first view
view second$ into list$ piece ",", match 2
print second$ // beta
Live Updates Propagate Through the Chain
Because the chain resolves to the base string, reassigning the base
updates all derived views on the next reference.
data$ = "[one,two,three]"
print second$ // two
data$ = "[red,green,blue]"
print second$ // green
Write-Through Behavior
Overlay operations on a chained view write through, via an overlay, to the base string
at the composed offsets.
data$ = "[alpha,beta,gamma]"
print second$ // beta
lset second$ = "BETA"
print data$ // [alpha,BETA,gamma]
Mental model: chained views are a pipeline of lenses. Each lens
narrows what you see, but they all look into the same original string
without making any string copies.
Note: View declarations may be placed outside loops when their
specifications are constant. The views automatically rebind whenever
their source variables change.
VIEW Statement
VIEW name$ INTO data$ BETWEEN "name=", " "
Creates a dynamic view pointer for name$
View Pointer (name$)
Logical view into data$'s Variable
Control Block (VCB)
-
Points to: data$ VCB
-
Cached SID: 12345
-
Start offset: 5
-
End offset: 10
View tracks this specific data$ VCB
(same SID)
VCB for (data$)
Holds the real string storage pointer
-
Current value:
"name=Alice age=30"
-
Buffer pointer →
Actual String Buffer
-
Length: 19
-
SID: 12345
-
Allocation:
24 bytes (inline)
Descriptor's buffer pointer references this memory
Actual String Buffer
n a m e =
A l i c e
a g e = 3 0
Offsets 5—10:
this slice is what name$ "sees" through
its view.
Views can be created using fixed positions and lengths, or
using delimiters. All views support write-through overlayed changes to
the underlying string.
VIEW MID — Position and Length
The VIEW ... MID form creates a live, dynamic
slice of a string variable using an explicit starting
position and length. Unlike MID$(), which returns
a copy, a VIEW does not duplicate data
— it binds the view variable directly to the source
string.
A view automatically tracks the current value of the
source variable. It updates when characters change, or when
the entire source variable is reassigned to a new string.
Syntax
VIEW view_var$ INTO source_var$,
MID position_expr, length_expr
All four parameters are required:
- view_var$ — the view variable.
- source_var$ — the source string.
-
position_expr — starting position (1-based,
like
MID$).
-
length_expr — number of characters in the
view.
Note:
-
A view is read-only except when using overlay
operations (
LSET, OVERLAY(),
etc.), which write through to the underlying source
string.
-
A negative starting position counts backward from the
end of the source.
-
If the starting position is beyond the end of the
source, the view evaluates as an empty string.
-
If the view's requested length extends past the end of
the source, it is automatically truncated to the
remaining characters.
Example
phone$ = "8085551212"
// Create a view into the first 3 characters
VIEW area$ INTO phone$,
MID 1,3
PRINT area$ // Output: 808
// Create a view into the last 3 characters
VIEW area$ INTO phone$,
MID -3,3
PRINT area$ // Output: 212
// Modify the source string
phone$ = "7022229999"
// area$ updates automatically
PRINT area$ // Output: 702
// Overwriting a view updates the source
LSET area$ = "abc"
PRINT phone$ // Output: abc2229999
phone$ = ""
PRINT area$ // Output: (empty string)
The VIEW..MID..MATCH Option
The MATCH option extends the VIEW ... MID
syntax so that the source string is treated as a contiguous
array of fixed-width segments. Instead of selecting a single
absolute start position, you define:
-
Base Position — where the first
segment begins.
-
Record Length — the width of every
segment.
-
Index — which segment (occurrence)
you want to view.
This is ideal for processing flat-file segments, binary
headers, fixed-layout structures, or tightly packed lists of
codes, without doing your own loop arithmetic.
Syntax for the Fixed-Width MATCH Form
VIEW view_var$ INTO source_var$,
MID base_pos, record_len,
MATCH index
-
base_pos — starting position of the
first segment (typically 1).
-
record_len — the fixed width of each
segment.
-
index — the segment number to view
(1-based).
Multiple Views Inside a CLUSTER
You can also store multiple views inside a
CLUSTER, turning a single string into a
lightweight, structured record that automatically tracks the
source variable. Note: A CLUSTER is a named collection
of variables.
cluster parts: area$, exch$
VIEW parts->area$ INTO phone$,
MID 1,3
VIEW parts->exch$ INTO phone$,
MID 5,3
phone$ = "702-997-1212"
PRINT cluster parts // Outputs: 702, 997
phone$ = "213-555-1212"
PRINT cluster parts // Outputs: 213, 555
Key Differences Between MID$() and VIEW
| Feature |
MID$() |
VIEW |
| Mechanism |
Creates a new string (copy) |
Creates a reference (pointer) |
| Memory |
Allocates new memory |
Points to the existing memory of the source |
| Behavior |
Static snapshot |
Dynamic, tracks source changes |
| Use Case |
When you want a fixed value |
When you want to track changes |
VIEW BETWEEN — Using Delimiters
The extended VIEW syntax allows you to create a
dynamic slice of a string variable using start
and end delimiters, just like BETWEEN$()
— except the result is a live view instead of a
copied string.
This makes it the dynamic counterpart to BETWEEN$(),
just as VIEW ... MID is the dynamic counterpart
to MID$().
Syntax
VIEW view_var$ INTO base_var$,
BETWEEN start_delim$, end_delim$,
POSITION position_expr,
MATCH match_expr,
NOTRIM,
BALANCED
How it works:
-
The statement searches
base_var$ for
start_delim$ and end_delim$.
-
If
start_delim$ is empty, the beginning of
base_var$ is assumed.
-
If
end_delim$ is empty, the end of
base_var$ is assumed.
-
Instead of returning a copied substring, the view variable
points directly into the section of
base_var$
between the two delimiters.
-
If the delimiters are not found, the view becomes an
empty string.
Note:
-
This form of
VIEW creates a
read-only view except for overlay operations
(LSET, OVERLAY()), which
modify the corresponding characters in
base_var$.
It treats delimited regions of a string as live
fields. Whenever the base string changes, the view
re-locates its delimited region the next time the
view variable is referenced (or becomes an empty
string if the delimiters are not present). Because the
view points directly into the base string, overlaying
the view updates the underlying text without copying or
rebuilding the string.
-
If
base_var$ is reassigned to a new value,
the view will re-evaluate its delimiters the next
time the view variable is referenced. This ensures
the view always corresponds to the current value of
base_var$.
-
If the characters in
base_var$ shift
— for example, due to editing, concatenation, or
overlay operations — the view will locate the
delimiters again when the view is next used.
This guarantees the view reflects the correct, live
region of the modified string.
-
The
VIEW statement is executable, so any
of its expression arguments can be changed at runtime.
When evaluated, it updates the corresponding
view_var$ when the variable is next
referenced.
VIEW...BETWEEN and VIEW...PIECE Options
-
POSITION — By default, the search
starts at the first character. This option allows the
search to start at an arbitrary location in the base
string.
-
MATCH — By default, the search uses
the first occurrence of the target region. With
MATCH, Sheerpower instead returns the Nth
occurrence. MATCH is highly optimized;
millions of sequential matches per second are typical on
a modern laptop.
-
NOTRIM — By default, the substring
between delimiters is trimmed of leading and trailing
spaces before it is exposed via the view. Use
NOTRIM to preserve the exact characters
between delimiters, including any surrounding whitespace.
-
BALANCED — Only valid with
VIEW ... BETWEEN. With BALANCED,
the engine finds the outer delimited region:
starting at the first start delimiter and locating the
matching closing delimiter that encloses the full region,
including any nested or inner delimiters.
VIEW...BETWEEN Example
data$ = "phone=(702) 555-1212 age=42"
VIEW area$ INTO data$,
BETWEEN "(", ")"
VIEW age$ INTO data$,
BETWEEN "age=", ""
PRINT area$ // Output: 702
PRINT age$ // Output: 42
// Replace the entire base string
data$ = "My new phone number is (858) 555-1212"
// The view updates automatically
PRINT area$ // Output: 858
PRINT age$ // Output: (empty string)
// Overlay through the view modifies the source
LSET area$ = "211"
PRINT data$
// Output: My new phone number is (211) 555-1212
// Using MATCH to get a specific occurrence
data$ = "phone=(702) 555-(1212) age=42"
VIEW area2$ INTO data$,
BETWEEN "(", ")" MATCH 2
PRINT area2$ // Output: 1212
data$ = "phone=555-1212 age=42" // Missing parens
PRINT area2$ // Output: (empty string)
// All VIEW parameters accept runtime expressions
list$ = "[apple][banana][cherry]"
FOR occurrence = 1 TO 10
VIEW item$ INTO list$,
BETWEEN "[", "]" MATCH occurrence
IF item$ = "" THEN EXIT FOR // No more items
PRINT item$
NEXT occurrence
// Output (one per line):
// apple
// banana
// cherry
Example: Using NOTRIM to Preserve Whitespace
// Base string with padded value
data$ = "value=[ 123 ] status=ok"
// Default: trims spaces between delimiters
VIEW num$ INTO data$,
BETWEEN "[", "]"
// NOTRIM: keeps the exact characters between delimiters
VIEW num_raw$ INTO data$,
BETWEEN "[", "]" NOTRIM
PRINT "[" + num$ + "]" // Output: [123]
PRINT "[" + num_raw$ + "]" // Output: [ 123 ]
Example: Using BALANCED for Outer Delimited Region
data$ = "outer[start [inner] tail] end"
// Without BALANCED: first "[" to first "]" after it
VIEW inner$ INTO data$,
BETWEEN "[", "]"
// With BALANCED: first "[" to its matching outer "]"
VIEW outer$ INTO data$,
BETWEEN "[", "]" BALANCED
PRINT inner$ // Output: start [inner
PRINT outer$ // Output: start [inner] tail
VIEW PIECE — Delimiter-Based Fields
The VIEW ... PIECE form splits a string into
pieces using a delimiter, and exposes a zero-copy view
of a chosen piece. It is the dynamic counterpart to the PIECE$() function,
but without allocating new strings.
You can think of VIEW ... PIECE as a live,
zero-copy CSV or line parser. It is ideal for comma-separated
values, pipe-delimited logs, or newline-separated records.
Syntax
VIEW view_var$ INTO base_var$,
PIECE delim$,
POSITION position_expr,
MATCH match_expr,
NOTRIM
-
delim$ — delimiter string (may be
multi-character).
-
POSITION — optional starting position for the
piece scan. If omitted, scanning starts at the first
character of
base_var$.
-
MATCH — which piece (1-based) to view,
counting from the starting position. If omitted,
MATCH 1 is assumed.
-
NOTRIM — preserve leading and trailing spaces
in the piece instead of trimming them.
VIEW...PIECE behavior:
-
Pieces are defined by the delimiter. Consecutive
delimiters produce empty pieces. Leading or trailing
delimiters produce empty first or last pieces.
-
If no delimiter is found, the entire string is treated
as a single piece.
MATCH 1 returns the
whole string; higher matches return an empty string.
-
When
POSITION is supplied, the scan starts
from that character offset inside base_var$.
The piece containing that position is
MATCH 1, the next piece is
MATCH 2, and so on.
-
Like all views, a
VIEW ... PIECE view is
read-only except for overlay operations
(LSET, OVERLAY()), which
write through to the underlying source characters.
-
VIEW ... PIECE supports
POSITION, MATCH, and
NOTRIM, but does not support
BALANCED.
Simple CSV Example
data$ = "one,two,three"
VIEW first$ INTO data$,
PIECE ",", MATCH 1
VIEW second$ INTO data$,
PIECE ",", MATCH 2
VIEW third$ INTO data$,
PIECE ",", MATCH 3
PRINT first$ // Output: one
PRINT second$ // Output: two
PRINT third$ // Output: three
// Out-of-range match yields empty string
VIEW extra$ INTO data$,
PIECE ",", MATCH 4
PRINT "[" + extra$ + "]" // Output: []
Leading, Trailing, and Empty Piece
data$ = ",a,,b,"
VIEW p1$ INTO data$,
PIECE ",", MATCH 1 // Leading empty
VIEW p2$ INTO data$,
PIECE ",", MATCH 2 // "a"
VIEW p3$ INTO data$,
PIECE ",", MATCH 3 // Empty (two commas)
VIEW p4$ INTO data$,
PIECE ",", MATCH 4 // "b"
VIEW p5$ INTO data$,
PIECE ",", MATCH 5 // Trailing empty
PRINT "[" + p1$ + "]" // []
PRINT "[" + p2$ + "]" // [a]
PRINT "[" + p3$ + "]" // []
PRINT "[" + p4$ + "]" // [b]
PRINT "[" + p5$ + "]" // []
Multi-Character Delimiter
data$ = "alpha||beta||gamma"
delim$ = "||"
VIEW a$ INTO data$,
PIECE delim$, MATCH 1
VIEW b$ INTO data$,
PIECE delim$, MATCH 2
VIEW c$ INTO data$,
PIECE delim$, MATCH 3
PRINT a$ // alpha
PRINT b$ // beta
PRINT c$ // gamma
POSITION with PIECE
data$ = "one,two,three,four"
// Position 5 is inside "two"
VIEW mid1$ INTO data$,
PIECE ",", POSITION 5
// From the same starting position,
// MATCH 2 is "three"
VIEW mid2$ INTO data$,
PIECE ",", POSITION 5, MATCH 2
PRINT mid1$ // two
PRINT mid2$ // three
// POSITION beyond the end yields empty piece
VIEW off$ INTO data$,
PIECE ",", POSITION 999, MATCH 1
PRINT "[" + off$ + "]" // []
Write-Through via PIECE
data$ = "one,two,three"
VIEW second$ INTO data$,
PIECE ",", MATCH 2
LSET second$ = "TWO"
PRINT data$ // one,TWO,three
VIEW first$ INTO data$,
PIECE ",", MATCH 1
VIEW third$ INTO data$,
PIECE ",", MATCH 3
LSET first$ = "ONE"
LSET third$ = "THREE"
PRINT data$ // ONE,TWO,THREE
Cluster-Based Parsing with PIECE
cluster csv: col1$, col2$, col3$
VIEW csv->col1$ INTO line$,
PIECE ",", MATCH 1
VIEW csv->col2$ INTO line$,
PIECE ",", MATCH 2
VIEW csv->col3$ INTO line$,
PIECE ",", MATCH 3
line$ = "apple,10,1.99"
PRINT cluster csv
// "apple","10","1.99"
line$ = "banana,25,0.79"
PRINT cluster csv
// "banana","25","0.79"
Behavior Summary
| Feature |
BETWEEN$() |
VIEW...BETWEEN |
VIEW...PIECE |
| Mechanism |
Returns a copy |
Creates a live view |
Creates a live view |
| Delimiter type |
Start/end pair |
Start/end pair |
Single field delimiter |
| Updates with base changes? |
No |
Yes, automatically |
Yes, automatically |
| Memory |
Allocates new string |
No allocation; points to base |
No allocation; points to base |
| Write-through |
No |
Yes, via LSET/OVERLAY |
Yes, via LSET/OVERLAY |
| Options |
POSITION, MATCH, NOTRIM |
POSITION, MATCH, NOTRIM,
BALANCED
|
POSITION, MATCH, NOTRIM |
(Show/Hide Sheerpower String Views Takeaways)
Sheerpower String Views Takeaways
-
VIEW creates a zero-copy, live slice that
points directly into the source string instead of
allocating a new one.
-
Views are lazily updated and cached: they re-evaluate
only when the source has changed since the last use,
keeping repeated references fast.
-
VIEW ... MID behaves like a dynamic
MID$(): it tracks edits and
reassignments of the source, including negative
positions, truncation, and out-of-range handling.
-
Views are read-only except for overlay operations
(
LSET, OVERLAY(), etc.),
which write through to the underlying source
characters.
-
The fixed-width
MID ... MATCH form treats
the source as segments, letting you address the Nth
segment without manual offset arithmetic.
-
Delimiter-based
VIEW ... BETWEEN acts as a
dynamic BETWEEN$(), relocating its slice
whenever the base string changes.
-
VIEW ... PIECE is a dynamic, zero-copy
split on a delimiter, ideal for CSV rows, logs, and
line-based data.
-
POSITION, MATCH, and
NOTRIM are shared across both
BETWEEN and PIECE. The
BALANCED option is only available with
VIEW ... BETWEEN.
-
Missing delimiters, missing piece, or out-of-range
matches evaluate as the empty string, providing a
safe default for parsing and loops.
-
Views can live inside a
CLUSTER, turning
one base string into a lightweight record whose
fields stay in sync with the source automatically.
-
All
VIEW arguments can be runtime
expressions, so you can drive zero-copy parsing and
field extraction entirely from computed positions and
delimiters.