String Methods GitHub issue

Overview GitHub issue

vibecode
{"vibecode": {
    "section": "overview",
    "type": "string",
    "encoding": "utf8",
    "mutable": false,
    "key_facts": ["all_methods_return_new_string",
        "all_strings_normalized_to_utf8_at_boundary",
        "no_charset_to_query_no_decode_step"],
    "example_universe": "Narnia"
}}

Strings are immutable. A method might return a new string, but none mutate in place.

All strings in Caspian are normalized to UTF-8.


Operators GitHub issue

vibecode
{"vibecode": {
    "section": "operators",
    "operators": ["+", "*", "[]"],
    "notes": ["right_operand_coerced_to_string_for_plus",
        "repeat_requires_nonneg_integer",
        "subscript_supports_offset_and_length",
        "end_anchored_slicing_via_colon_end",
        "indices_are_unicode_codepoints_not_bytes"]
}}
Method Returns Description
+ String Concatenate. Right operand coerced to string. 'Hello, ' + $name
* String Repeat. 'ha' * 3'hahaha'. Right operand must be a non-negative integer.
[] String Subscript. $str[n] returns the character at index n (0-based). $str[$start, $length] returns $length characters starting at $start. Passing :end as the last argument switches to end-anchored slicing (see end below): $str[n, :end] is sugar for $str[n, 1, :end]; $str[n, $length, :end] returns $length characters ending at position n from the end. Indices count Unicode code points, not bytes. Returns empty string if out of range.

Testing GitHub issue

vibecode
{"vibecode": {
    "section": "testing",
    "returns": "Boolean",
    "methods": ["==", "include?", "match?", "starts_with?", "start_with?",
        "ends_with?", "end_with?"],
    "notes": ["start_with_and_end_with_are_aliases"]
}}
Method Returns Description
== Boolean True if both strings are identical
include?($substr) Boolean True if $substr appears anywhere in the string
match?($pattern) Boolean True if the string matches the regex $pattern
starts_with?($prefix) Boolean True if the string begins with $prefix
start_with?($prefix) Boolean Alias for starts_with?
ends_with?($suffix) Boolean True if the string ends with $suffix
end_with?($suffix) Boolean Alias for ends_with?

Case GitHub issue

vibecode
{"vibecode": {
    "section": "case",
    "methods": ["upper_case", "lower_case"],
    "returns": "String"
}}
Method Returns Description
upper_case String All characters uppercased
lower_case String All characters lowercased

Whitespace GitHub issue

vibecode
{"vibecode": {
    "section": "whitespace",
    "methods": ["left_strip", "right_strip", "collapse", "chomp"],
    "returns": "String",
    "notes": ["collapse_strips_and_normalizes_internal_whitespace",
        "chomp_removes_trailing_newlines_and_carriage_returns"]
}}
Method Returns Description
left_strip String Remove leading whitespace
right_strip String Remove trailing whitespace
collapse String Remove leading and trailing whitespace, then collapse every internal run of whitespace to a single space. ' hello world '.collapse'hello world'. Whitespace includes spaces, tabs, and newlines.
chomp String Remove all trailing newline (\n) and carriage return (\r) characters. No-op if the string does not end with either.

Prefix and Suffix GitHub issue

vibecode
{"vibecode": {
    "section": "prefix_and_suffix",
    "methods": ["delete_prefix", "delete_suffix"],
    "returns": "String",
    "notes": ["returns_string_unchanged_if_prefix_or_suffix_not_present"]
}}
Method Returns Description
delete_prefix($prefix) String Remove $prefix from the start of the string if present; return the string unchanged otherwise
delete_suffix($suffix) String Remove $suffix from the end of the string if present; return the string unchanged otherwise

Search and Replace GitHub issue

vibecode
{"vibecode": {
    "section": "search_and_replace",
    "methods": ["match", "replace"],
    "replace_scope_options": [":all", ":first", ":last"],
    "notes": ["match_returns_match_object_or_nil",
        "replace_pattern_may_be_string_or_regex",
        "default_scope_is_all",
        "returns_original_if_no_match"]
}}
Method Returns Description
match($pattern) Match or nil Return the first match object for $pattern, or nil if no match. The match object carries the matched string and any capture groups.
replace($pattern, $replacement, $scope = :all) String Replace occurrences of $pattern with $replacement. $pattern may be a string or regex. $scope controls which matches are replaced: :all (default) replaces every match, :first replaces only the first, :last replaces only the last. Returns the original string unchanged if no match is found.

replace scope options GitHub issue

Scope Behaviour
:all Replace all matches (default)
:first Replace the first match from the start
:last Replace the last match from the end
$foo = 'Lucy Edmund Lucy'

$foo.replace('Lucy', 'Susan')         -> 'Susan Edmund Susan'
$foo.replace('Lucy', 'Susan', :all)   -> 'Susan Edmund Susan'
$foo.replace('Lucy', 'Susan', :first) -> 'Susan Edmund Lucy'
$foo.replace('Lucy', 'Susan', :last)  -> 'Lucy Edmund Susan'
$foo.replace('Aslan', 'Susan')        -> 'Lucy Edmund Lucy'

Formatting GitHub issue

vibecode
{"vibecode": {
    "section": "formatting",
    "methods": ["left_justify", "right_justify"],
    "returns": "String",
    "notes": ["pads_with_spaces", "no_op_if_already_at_or_above_width"]
}}
Method Returns Description
left_justify($width) String Pad with spaces on the right to at least $width characters. Returns the string unchanged if already at or above $width.
right_justify($width) String Pad with spaces on the left to at least $width characters. Returns the string unchanged if already at or above $width.

Splitting GitHub issue

vibecode
{"vibecode": {
    "section": "splitting",
    "methods": ["lines"],
    "returns": "Array",
    "notes": ["line_endings_stripped_from_each_element"]
}}
Method Returns Description
lines Array Split into an array of lines. Line endings (\n, \r\n) are stripped from each element.

Size GitHub issue

vibecode
{"vibecode": {
    "section": "size",
    "methods": ["length", "size"],
    "returns": "Number",
    "notes": ["counts_unicode_codepoints_not_bytes", "size_is_alias_for_length"]
}}
Method Returns Description
length Number Number of Unicode code points
size Number Alias for length

Conversion GitHub issue

vibecode
{"vibecode": {
    "section": "conversion",
    "methods": ["to_number", "hex", "reverse"],
    "notes": ["to_number_raises_if_invalid",
        "hex_parses_hexadecimal_integer",
        "reverse_returns_characters_in_reverse_order"]
}}
Method Returns Description
to_number Number Parse as a number. Raises an exception if the string is not a valid number.
hex Number Parse as a hexadecimal integer. 'ff'.hex → 255. Raises an exception if the string is not valid hex.
reverse String Characters in reverse order
incremented String Returns the next string in Perl/Ruby-style alphanumeric sequence. Increments the rightmost alphanumeric character and carries left. 'az'.incremented'ba'. 'zz'.incremented'aaa'. Non-alphanumeric characters are left alone.
decremented String Returns the previous string in alphanumeric sequence — the exact reverse of incremented. 'ba'.decremented'az'. 'aaa'.decremented'zz'. Raises an exception if called on an empty string.

End-Anchored Slicing GitHub issue

vibecode
{"vibecode": {
    "section": "end_anchored_slicing",
    "concept": "end_object_wraps_string_for_right_indexed_access",
    "access": "$str.end[$offset] or $str.end[$offset, $length]",
    "sugar": "$str[n, :end] and $str[n, $length, :end]",
    "implementation": "$str.end[$offset, $length] == $str.reverse[$offset, $length].reverse",
    "parser_note": "end_after_dot_is_method_name_not_block_closing_keyword"
}}

end is a method on String that returns an end object — a thin wrapper that holds a reference to the string and provides end-anchored slicing via its own [] method.

Method Returns Description
end End object Returns the end object for this string.

The end object has a [] method with the same signature as String's [], but indexing from the right:

Method Returns Description
end[]($offset) String The character at position $offset from the end. 0 is the last character.
end[]($offset, $length) String $length characters ending at position $offset from the end.

The end object's [] is defined in terms of existing String primitives:

$str.end[$offset, $length]  =  $str.reverse[$offset, $length].reverse

Examples with $foo = 'abcdef':

$foo.end[0]      -> 'f'
$foo.end[0, 2]   -> 'ef'

The :end keyword in String's [] is sugar for calling the end object:

$foo[0, :end]    -> 'f'    # sugar for $foo.end[0]
$foo[0, 2, :end] -> 'ef'   # sugar for $foo.end[0, 2]

The end object may grow additional methods in later versions. In Caspian, the [] method on the end object can be defined as:

function end[]()
end

Parser note: end after a . is a method name, not the block-closing keyword. The parser must handle this disambiguation.


Contributing roles GitHub issue

Every string carries a roles array — the set of every role whose code participated in producing the string's content. The array is union-tracked across operations: when two strings combine, the result's roles is the union of both inputs' roles.

$foo = 'bar'            # roles: ['user']
$gup = $foo + $blah     # $blah came from bob → $gup.roles is ['user', 'bob']

A string created from a literal in role X's code has roles: ['X']. A string returned from a built-in operation (upcase, strip, slicing, etc.) inherits the input's roles. A string built by concatenation, interpolation, formatting, or replace inherits the union of every contributing input's roles.

This is not the same as the string's owner (per the role model). Owner is a single value; roles is the set of every role that's touched the content. A string can be owned by user (the role that holds the reference) while carrying roles: ['user', 'bob'] because some of its content was built from a string bob's code produced.

Why the roles array exists GitHub issue

Certain secure operations gate on the roles array and refuse to proceed when an untrusted role appears in the list. Typical gates:

Each gate documents which roles it considers safe; the runtime cross-checks the string's roles against that set before performing the operation. Mismatch raises rather than silently sanitizing — explicit refusal is the contract.

Distinct from full string provenance GitHub issue

The roles array is a minimal contributor tag — a set of role names, nothing more. It does not record which operations combined the strings or which source produced each input. A richer "full construction history" idea is sketched at ideas/security/string-provenance.md; that's a possible future opt-in for higher-security scenarios. The roles array is the always-on minimum.


Open Questions GitHub issue


© 2026 Puck.uno