Files
l2/stdlib/utils.sl

383 lines
12 KiB
Plaintext
Raw Normal View History

#strcmp [*, addr, len, addr | len] -> [*, addr, len, addr, len | bool]
word strcmp
3 pick 2 pick @ swap @ ==
end
#strconcat [*, addr, len, addr | len] -> [*, addr | len]
word strconcat
0 pick 3 pick +
dup
>r >r >r >r >r >r
5 rpick
alloc
r> r>
dup >r
memcpy
swap
r> dup -rot +
r> r>
memcpy
swap
3 pick
-
swap
drop
swap
0 rpick
nip
rot
drop
2026-02-17 12:59:34 +01:00
rdrop rdrop
end
#strlen [* | addr] -> [* | len]
# for null terminated strings
2026-02-03 09:41:11 +01:00
word strlen
0 swap # len addr
while dup c@ 0 != do
1 + # addr++
swap 1 + swap # len++
end
drop # drop addr, leave len
end
#digitsN>num [*, d_{n-1}, d0 | n] -> [* | value]
word digitsN>num # digits bottom=MSD, top=LSD, length on top (MSD-most significant digit, LSD-least significant digit)
0 swap # place accumulator below length
for # loop n times using the length on top
r@ pick # fetch next digit starting from MSD (uses loop counter as index)
swap # acc on top
10 * # acc *= 10
+ # acc += digit
end
end
#toint [*, addr | len] -> [* | int]
# converts a string to an int
word toint
swap
over 0 swap
dup >r
for
over over +
c@ 48 -
swap rot
swap
1 +
end
2drop
r>
dup >r
digitsN>num
r> 1 +
for
swap drop
end
rdrop rdrop
end
#count_digits [* | int] -> [* | int]
# returns the amount of digits of an int
2026-01-12 14:54:48 +01:00
word count_digits
0
swap
while dup 0 > do
10 / swap 1 + swap
end
drop
end
#tostr [* | int] -> [*, addr | len]
# the function allocates a buffer, remember to free it
2026-01-12 14:54:48 +01:00
word tostr
dup
count_digits
2dup >r alloc
swap rot swap
2026-01-12 14:54:48 +01:00
for
dup 10 % swap 10 /
end
drop
r>
1 swap dup
for
dup
2 + pick
2 pick
2 + pick
3 pick rot +
2026-01-12 18:36:34 +01:00
swap 48 + swap 1 - swap c!
2026-01-12 14:54:48 +01:00
swap
1 +
swap
end
swap 0 +
pick 1 +
over for
rot drop
end drop
end
2026-02-17 12:59:34 +01:00
# ---------------------------------------------------------------------------
# String search helpers
# ---------------------------------------------------------------------------
#indexof [*, addr, len | char] -> [* | index]
# Finds the index of the first occurrence of char in the string.
# Returns -1 if not found. Consumes addr, len, and char.
word indexof
>r # char -> rstack
-1 0 # result=-1, i=0
while dup 3 pick < 2 pick -1 == band do # while i < len && result == -1
dup 4 pick + c@ # byte = addr[i]
r@ == if # byte == char?
swap drop dup # result = i
end
1 + # i++
end
drop nip nip # drop i, addr, len -> leave result
rdrop # clean char from rstack
end
#count_char_in_str [*, addr, len | char] -> [*, addr, len | count]
# Counts the number of occurrences of char in the string.
# Preserves addr and len on stack; pushes count on top.
word count_char_in_str
>r # char -> rstack
0 # count = 0
1 pick # push len (for loop count)
for
# for counter r@ goes len..1; char is at 1 rpick
# byte index = len - r@ (0 .. len-1)
1 pick r@ - 3 pick + c@ # byte at addr[len - r@]
1 rpick == # byte == char?
if 1 + end # increment count
end
rdrop # clean char from rstack
end
# ---------------------------------------------------------------------------
# Single-substitution helpers
# ---------------------------------------------------------------------------
#format1s [*, arg_addr, arg_len, fmt_addr | fmt_len] -> [*, result_addr | result_len]
# Replaces the first '%' in fmt with the arg string.
# Allocates a new result string (caller should free it).
# If no '%' is found, returns fmt as-is (NOT newly allocated).
word format1s
2dup 37 indexof # find first '%'
dup -1 == if
# no '%' — drop pos, drop arg, return fmt unchanged
drop 2swap 2drop
else
# pos is on TOS; save pos, fmt_addr, fmt_len to rstack
>r # rstack: [pos]
over >r # rstack: [pos, fmt_addr]
dup >r # rstack: [pos, fmt_addr, fmt_len]
# rpick 0=fmt_len 1=fmt_addr 2=pos
# stack: arg_addr, arg_len, fmt_addr, fmt_len
drop # drop fmt_len (we have it on rstack)
2 rpick # push pos
# stack: arg_addr, arg_len, fmt_addr, pos (= prefix pair)
2swap # stack: fmt_addr, pos, arg_addr, arg_len
strconcat # tmp = prefix + arg
# save tmp for later freeing
2dup >r >r
# rpick 0=tmp_addr 1=tmp_len 2=fmt_len 3=fmt_addr 4=pos
# build suffix: (fmt_addr + pos + 1, fmt_len - pos - 1)
3 rpick 4 rpick + 1 + # suffix_addr
2 rpick 4 rpick - 1 - # suffix_len
strconcat # result = tmp + suffix
# free tmp
r> r> free
# clean saved pos, fmt_addr, fmt_len
rdrop rdrop rdrop
end
end
#format1i [*, int_arg, fmt_addr | fmt_len] -> [*, result_addr | result_len]
# Replaces the first '%' in fmt with the decimal representation of int_arg.
# Allocates a new result string (caller should free it).
word format1i
rot tostr # convert int to string (allocates)
2dup >r >r # save tostr result for freeing
2swap # bring fmt on top
format1s # substitute
r> r> free # free the tostr buffer
end
# ---------------------------------------------------------------------------
# Multi-substitution format words
# ---------------------------------------------------------------------------
#formats [*, sN_addr, sN_len, ..., s1_addr, s1_len, fmt_addr | fmt_len]
# -> [*, result_addr | result_len]
# Replaces each '%' in fmt with the corresponding string argument.
# s1 (just below fmt) maps to the first '%', s2 to the second, etc.
# The number of string-pair args must equal the number of '%' markers.
# Returns a newly allocated string, or fmt as-is when there are no '%'.
word formats
2dup 37 count_char_in_str nip nip # count '%' -> n
dup 0 == if
drop # no substitutions needed
else
1 - >r # remaining = n - 1
format1s # first substitution
while r@ 0 > do
2dup >r >r # save current result for freeing
format1s # next substitution
r> r> free # free previous result
r> 1 - >r # remaining--
end
rdrop # clean counter
end
end
#formati [*, iN, ..., i1, fmt_addr | fmt_len]
# -> [*, result_addr | result_len]
# Replaces each '%' in fmt with the decimal string of the corresponding
# integer argument. i1 (just below fmt) maps to the first '%', etc.
# The number of int args must equal the number of '%' markers.
# Returns a newly allocated string, or fmt as-is when there are no '%'.
word formati
2dup 37 count_char_in_str nip nip # count '%' -> n
dup 0 == if
drop # no substitutions needed
else
1 - >r # remaining = n - 1
format1i # first substitution
while r@ 0 > do
2dup >r >r # save current result for freeing
format1i # next substitution
r> r> free # free previous result
r> 1 - >r # remaining--
end
rdrop # clean counter
end
end
# ---------------------------------------------------------------------------
# Mixed-type printf-style format (with %i and %s specifiers)
# ---------------------------------------------------------------------------
#find_fmt [*, addr | len] -> [* | pos, type]
# Scans for the first %i or %s placeholder in the string.
# Returns position of '%' and the type byte (105='i' or 115='s').
# Returns -1, 0 if no placeholder is found.
word find_fmt
over >r dup >r # rstack: 0=len, 1=addr
-1 0 0 # pos=-1, type=0, i=0
while dup 0 rpick 1 - < 2 pick 0 == band do
1 rpick over + c@ # addr[i]
37 == if # '%'
1 rpick over + 1 + c@ # addr[i+1]
dup 105 == over 115 == bor if
rot drop rot drop over # pos=i, type=char, keep i
else
drop
end
end
1 +
end
drop # drop i
rdrop rdrop
2swap 2drop # remove addr, len copies; leave pos, type
end
#count_fmt [*, addr | len] -> [*, addr, len | count]
# Counts the number of %i and %s placeholders in a string.
word count_fmt
over >r dup >r # rstack: 0=len, 1=addr
0 0 # count=0, i=0
while dup 0 rpick 1 - < do
1 rpick over + c@
37 == if
1 rpick over + 1 + c@
dup 105 == over 115 == bor if
drop
swap 1 + swap
1 +
else
drop
end
end
1 +
end
drop # drop i
rdrop rdrop
end
#fmt_splice [*, repl_addr, repl_len, fmt_addr, fmt_len | pos] -> [*, result_addr | result_len]
# Replaces the 2-char placeholder at pos in fmt with repl.
# Allocates a new result string (caller should free it).
word fmt_splice
>r >r >r # rstack: [pos, fmt_len, fmt_addr]
# rpick: 0=fmt_addr, 1=fmt_len, 2=pos
0 rpick 2 rpick # push fmt_addr, pos (= prefix pair)
2swap # stack: fmt_addr, pos, repl_addr, repl_len
strconcat # tmp = prefix + repl
2dup >r >r # save tmp for freeing
# rpick: 0=tmp_addr, 1=tmp_len, 2=fmt_addr, 3=fmt_len, 4=pos
2 rpick 4 rpick + 2 + # suffix_addr = fmt_addr + pos + 2
3 rpick 4 rpick - 2 - # suffix_len = fmt_len - pos - 2
strconcat # result = tmp + suffix
r> r> free # free tmp
rdrop rdrop rdrop # clean fmt_addr, fmt_len, pos
end
#format1 [*, arg(s), fmt_addr | fmt_len] -> [*, result_addr | result_len]
# Replaces the first %i or %s placeholder in fmt.
# For %i: consumes one int from below fmt and converts via tostr.
# For %s: consumes one (addr, len) string pair from below fmt.
# Allocates a new result string (caller should free it).
word format1
2dup find_fmt # stack: ..., fmt_addr, fmt_len, pos, type
dup 105 == if
# %i
drop # drop type
>r >r >r # save pos, fmt_len, fmt_addr
tostr # convert int arg to string
2dup >r >r # save istr for freeing
r> r> r> r> r> # restore: istr_addr, istr_len, fmt_addr, fmt_len, pos
fmt_splice
2swap free # free tostr buffer
else 115 == if
# %s: stack already has repl_addr, repl_len, fmt_addr, fmt_len, pos
fmt_splice
else
drop # no placeholder, drop pos
end
end
#format [*, args..., fmt_addr | fmt_len] -> [*, result_addr | result_len]
# Printf-style formatting with %i (integer) and %s (string) placeholders.
# Arguments are consumed left-to-right: the closest arg to the format
# string maps to the first placeholder.
#
# Example: "bar" 123 "foo" "%s: %i and %s" format
# -> "foo: 123 and bar"
#
# Returns a newly allocated string (caller should free it), or the
# original format string unchanged if there are no placeholders.
word format
2dup count_fmt nip nip # n = placeholder count
dup 0 == if
drop
else
>r
format1 # first substitution
r> 1 -
while dup 0 > do
>r
2dup >r >r # save current result for freeing
format1 # next substitution
r> r> free # free previous result
r> 1 -
end
drop # drop counter (0)
end
end