Sheerpower Logo

String Views (Zero-Copy Slices)


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)
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.