1
0
Fork 0
forked from fun/fun

Added some crypto 100% implemented in Fun. (0.11.0)

This commit is contained in:
Johannes Findeisen 2025-09-29 02:36:57 +02:00
commit 12a5646d56
25 changed files with 1926 additions and 12 deletions

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.16)
project(fun VERSION 0.10.0 LANGUAGES C)
project(fun VERSION 0.11.0 LANGUAGES C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)

32
examples/crc32_demo.fun Normal file
View file

@ -0,0 +1,32 @@
#!/usr/bin/env fun
/*
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the ISC license.
* https://opensource.org/license/isc-license-txt
*
* Added: 2025-09-29
*/
// Include the pure Fun CRC32 library
include "lib/crypt/crc32.fun"
// Create a Class-typed instance to prevent accidental shadowing
Class c32 = CRC32()
print(typeof(c32)) // should be "Class"
print("=== CRC32 demo (hex input) ===")
// "" (empty) -> 00000000
print(c32.crc32_hex(""))
// "abc" -> 352441c2
print(c32.crc32_hex("616263"))
// "123456789" -> cbf43926
print(c32.crc32_hex("313233343536373839"))
print("=== Done ===")

34
examples/md5_demo.fun Normal file
View file

@ -0,0 +1,34 @@
#!/usr/bin/env fun
/*
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the ISC license.
* https://opensource.org/license/isc-license-txt
*
* Added: 2025-09-29
*/
// Include the pure Fun MD5 library
include "lib/crypt/md5.fun"
// create a hasher instance
md5 = MD5()
print("=== MD5 demo (hex input) ===")
// "abc" => 0x61 0x62 0x63
print(md5.md5_hex("616263")) // -> 900150983cd24fb0d6963f7d28e17f72
// empty string "" => hex ""
print(md5.md5_hex("")) // -> d41d8cd98f00b204e9800998ecf8427e
// "message digest"
print(md5.md5_hex("6d65737361676520646967657374")) // -> f96b697d7cb7938d525a2f31aaf161d0
// "abcdefghijklmnopqrstuvwxyz"
print(md5.md5_hex("6162636465666768696a6b6c6d6e6f707172737475767778797a")) // -> c3fcd3d76192e4007dfb496cca67e13b
print("=== Done ===")

33
examples/sha256_demo.fun Normal file
View file

@ -0,0 +1,33 @@
#!/usr/bin/env fun
/*
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the ISC license.
* https://opensource.org/license/isc-license-txt
*
* Added: 2025-09-29
*/
// Include the pure Fun SHA-256 library
include "lib/crypt/sha256.fun"
sha = SHA256()
print("=== SHA-256 demo (hex input) ===")
// "" (empty)
print(sha.sha256_hex("")) // -> e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
// "abc"
print(sha.sha256_hex("616263")) // -> ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
// "message digest"
print(sha.sha256_hex("6d65737361676520646967657374")) // -> f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650
// "abcdefghijklmnopqrstuvwxyz"
print(sha.sha256_hex("6162636465666768696a6b6c6d6e6f707172737475767778797a")) // -> 71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73
print("=== Done ===")

35
examples/sha512_demo.fun Normal file
View file

@ -0,0 +1,35 @@
#!/usr/bin/env fun
/*
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the ISC license.
* https://opensource.org/license/isc-license-txt
*
* Added: 2025-09-29
*/
include "lib/crypt/sha512.fun"
sha = SHA512()
print("=== SHA-512 demo (hex input) ===")
// "" (empty)
print(sha.sha512_hex("")) // -> cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce
// 47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e
// "abc"
print(sha.sha512_hex("616263")) // -> ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a
// 2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f
// "message digest"
print(sha.sha512_hex("6d65737361676520646967657374"))
// -> 107dbf389d9e9f71a3a95f6c055b9251
// bc5268c2be16d6c13492ea45b0199f33
// 09e16455ab1e96118e8a905d5597b721
// 3f36cc64dab6c0c5
print("=== Done ===")

View file

@ -0,0 +1,34 @@
#!/usr/bin/env fun
/*
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the ISC license.
* https://opensource.org/license/isc-license-txt
*
* Added: 2025-09-29
*/
// Demo: SHA-512 of raw strings (no hex decoding)
include "lib/crypt/sha512.fun"
sha = SHA512()
print("=== SHA-512 demo (raw string input via sha512_str) ===")
// "" (empty string)
print(sha.sha512_str("")) // -> cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce
// 47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e
// "abc"
print(sha.sha512_str("abc")) // -> ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a
// 2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f
// "616263" (the literal characters '6','1','6','2','6','3')
print(sha.sha512_str("616263")) // -> ee21095236a9c037f705f41ffa3cf60869891fcdb461c5c8fe50e5b1711a27b
// fc02de2e387228651bdd034113cc59af777fdb9c915b70fdbed0eeacf7113296b
print("=== Note ===")
print("sha512_str(\"616263\") hashes the text 616263; sha512_hex(\"616263\") hashes bytes 0x61 0x62 0x63 (abc).")

187
lib/crypt/crc32.fun Normal file
View file

@ -0,0 +1,187 @@
/*
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the ISC license.
* https://opensource.org/license/isc-license-txt
*
* Added: 2025-09-29
*/
// lib/crypt/crc32.fun
// Pure Fun CRC32 (IEEE 802.3, polynomial 0xEDB88320, reflected) operating on hex-string input.
//
// Public API (class):
// crc = CRC32()
// crc.crc32_hex(hexStr) -> digest hex string (lowercase, 8 hex chars)
//
// Example:
// print(CRC32().crc32_hex("616263")) // "abc" => 352441c2
class CRC32()
// hex helpers
fun hex_val(this, ch)
if (ch == "0")
return 0
else if (ch == "1")
return 1
else if (ch == "2")
return 2
else if (ch == "3")
return 3
else if (ch == "4")
return 4
else if (ch == "5")
return 5
else if (ch == "6")
return 6
else if (ch == "7")
return 7
else if (ch == "8")
return 8
else if (ch == "9")
return 9
else if (ch == "a" || ch == "A")
return 10
else if (ch == "b" || ch == "B")
return 11
else if (ch == "c" || ch == "C")
return 12
else if (ch == "d" || ch == "D")
return 13
else if (ch == "e" || ch == "E")
return 14
else if (ch == "f" || ch == "F")
return 15
else
return 0
fun byte_from_hex_pair(this, hh)
hi = this.hex_val(substr(hh, 0, 1))
lo = this.hex_val(substr(hh, 1, 1))
return hi * 16 + lo
fun from_hex(this, hex)
// ensure we operate on a string
hex = to_string(hex)
arr = []
number i = 0
while true
ch1 = substr(hex, i, 1)
if (typeof(ch1) != "String")
break
if (ch1 == "")
break
ch2 = substr(hex, i + 1, 1)
if (typeof(ch2) != "String")
break
if (ch2 == "")
break
pair = join([ch1, ch2], "")
number b = this.byte_from_hex_pair(pair)
push(arr, b)
i = i + 2
return arr
fun two_hex(this, n)
n = n % 256
hexd = "0123456789abcdef"
number hi = n / 16
number lo = n % 16
ch1 = substr(hexd, hi, 1)
ch2 = substr(hexd, lo, 1)
return join([ch1, ch2], "")
fun u32(this, x)
m = 4294967296
while x < 0
x = x + m
while x >= m
x = x - m
return x
// Core per-byte CRC (bit-by-bit, reflected), polynomial 0xEDB88320
fun crc32_bytes(this, bytes)
crc = 4294967295 // 0xFFFFFFFF initial
poly = 3988292384 // 0xEDB88320
number i = 0
L = len(bytes)
while i < L
// crc ^= byte
crc = bxor(crc, bytes[i])
// 8 times
j = 0
while j < 8
if (band(crc, 1) == 1)
// crc = (crc >> 1) ^ poly
crc = bxor(shr(crc, 1), poly)
else
// crc >>= 1
crc = shr(crc, 1)
j = j + 1
i = i + 1
// final xor
crc = bxor(crc, 4294967295)
return this.u32(crc)
fun crc32_hex(this, hexStr)
// Process hex input directly with only core built-ins; no method calls on 'this'
hex = to_string(hexStr)
hexd = "0123456789abcdef"
// CRC state
number crc = 4294967295 // 0xFFFFFFFF
number poly = 3988292384 // 0xEDB88320
// Iterate two hex nibbles at a time
number i = 0
while true
ch1 = substr(hex, i, 1)
if (typeof(ch1) != "String" || ch1 == "")
break
ch2 = substr(hex, i + 1, 1)
if (typeof(ch2) != "String" || ch2 == "")
break
// Convert two hex chars to a byte without helper calls
// find returns index of substring or -1; ensure lowercase
h1 = find(hexd, to_string(ch1))
h2 = find(hexd, to_string(ch2))
if (h1 < 0 || h2 < 0)
// invalid hex -> stop
break
number b = h1 * 16 + h2
// crc ^= b
crc = bxor(crc, b)
// 8 rounds
number j = 0
while j < 8
if (band(crc, 1) == 1)
crc = bxor(shr(crc, 1), poly)
else
crc = shr(crc, 1)
j = j + 1
i = i + 2
// final xor
crc = bxor(crc, 4294967295)
// Format result as 8 hex chars big-endian without calling this.two_hex
number b3 = (crc / 16777216) % 256
number b2 = (crc / 65536) % 256
number b1 = (crc / 256) % 256
number b0 = crc % 256
// encode a single byte to two hex chars using hexd and substr
fun b2hex(byte)
number hi = (byte / 16) % 16
number lo = (byte % 16)
c1 = substr(hexd, hi, 1)
c2 = substr(hexd, lo, 1)
return join([c1, c2], "")
parts = [ b2hex(b3), b2hex(b2), b2hex(b1), b2hex(b0) ]
return join(parts, "")

519
lib/crypt/md5.fun Normal file
View file

@ -0,0 +1,519 @@
/*
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the ISC license.
* https://opensource.org/license/isc-license-txt
*
* Added: 2025-09-29
*/
// lib/crypt/md5.fun
// Pure Fun implementation of MD5 operating on hex-string input.
//
// Public API:
// md5_hex(hexStr) -> digest hex string (lowercase)
//
// Example:
// print(md5_hex("616263")) // "abc" => 900150983cd24fb0d6963f7d28e17f72
/* Legacy global API and helpers are commented out below.
Please use the MD5 class (see at the end of this file).
fun u32(x)
// bring x into [0 .. 2^32-1] by modular reduction
m = 4294967296
while x < 0
x = x + m
while x >= m
x = x - m
return x
fun shl32(x, s)
return shl(u32(x), s)
fun shr32(x, s)
return shr(u32(x), s)
fun rol32(x, s)
return rol(u32(x), s)
fun and32(a, b)
return band(u32(a), u32(b))
fun or32(a, b)
return bor(u32(a), u32(b))
fun xor32(a, b)
return bxor(u32(a), u32(b))
fun not32(x)
return bnot(u32(x))
fun add32(a, b)
return u32(a + b)
fun add32_4(a, b, c, d)
return u32(u32(u32(a + b) + c) + d)
fun hex_val(ch)
if (ch == "0")
return 0
else if (ch == "1")
return 1
else if (ch == "2")
return 2
else if (ch == "3")
return 3
else if (ch == "4")
return 4
else if (ch == "5")
return 5
else if (ch == "6")
return 6
else if (ch == "7")
return 7
else if (ch == "8")
return 8
else if (ch == "9")
return 9
else if (ch == "a" || ch == "A")
return 10
else if (ch == "b" || ch == "B")
return 11
else if (ch == "c" || ch == "C")
return 12
else if (ch == "d" || ch == "D")
return 13
else if (ch == "e" || ch == "E")
return 14
else if (ch == "f" || ch == "F")
return 15
else
return 0
fun byte_from_hex_pair(hh)
// hh is 2-char string
hi = hex_val(substr(hh, 0, 1))
lo = hex_val(substr(hh, 1, 1))
return hi * 16 + lo
fun from_hex(hex)
// parse even-length hex string to array of bytes (numbers 0..255)
arr = []
i = 0
n = len(hex)
while i + 1 < n
b = byte_from_hex_pair(substr(hex, i, 2))
push(arr, b)
i = i + 2
return arr
fun two_hex(n)
n = n % 256
d = ["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"]
hi = n / 16
lo = n % 16
// build with join
parts = [d[hi], d[lo]]
return join(parts, "")
fun bytes_to_hex(arr)
i = 0
out = []
while i < len(arr)
push(out, two_hex(arr[i]))
i = i + 1
return join(out, "")
// MD5 auxiliary functions
fun F(x, y, z)
return or32(and32(x, y), and32(not32(x), z))
fun G(x, y, z)
return or32(and32(x, z), and32(y, not32(z)))
fun H(x, y, z)
return xor32(xor32(x, y), z)
fun I(x, y, z)
return xor32(y, or32(x, not32(z)))
// T constants (K) = floor(abs(sin(i+1)) * 2^32), i=0..63
K = [
3614090360, 3905402710, 606105819, 3250441966, 4118548399, 1200080426, 2821735955, 4249261313,
1770035416, 2336552879, 4294925233, 2304563134, 1804603682, 4254626195, 2792965006, 1236535329,
4129170786, 3225465664, 643717713, 3921069994, 3593408605, 38016083, 3634488961, 3889429448,
568446438, 3275163606, 4107603335, 1163531501, 2850285829, 4243563512, 1735328473, 2368359562,
4294588738, 2272392833, 1839030562, 4259657740, 2763975236, 1272893353, 4139469664, 3200236656,
681279174, 3936430074, 3572445317, 76029189, 3654602809, 3873151461, 530742520, 3299628645,
4096336452, 1126891415, 2878612391, 4237533241, 1700485571, 2399980690, 4293915773, 2240044497,
1873313359, 4264355552, 2734768916, 1309151649, 4149444226, 3174756917, 718787259, 3951481745
]
// per-round rotation amounts
S = [
7,12,17,22, 7,12,17,22, 7,12,17,22, 7,12,17,22,
5, 9,14,20, 5, 9,14,20, 5, 9,14,20, 5, 9,14,20,
4,11,16,23, 4,11,16,23, 4,11,16,23, 4,11,16,23,
6,10,15,21, 6,10,15,21, 6,10,15,21, 6,10,15,21
]
fun pad_bytes(bytes)
// returns a new array with MD5 padding
L = len(bytes)
out = []
// copy original bytes
i = 0
while i < L
push(out, bytes[i])
i = i + 1
// append 0x80
push(out, 128)
// pad zeros until length % 64 == 56
while (len(out) % 64) != 56
push(out, 0)
// append length (bits) as 64-bit little-endian
len_bits = L * 8
j = 0
while j < 8
b = (len_bits / pow(2, 8 * j)) % 256
push(out, b)
j = j + 1
return out
fun word32_le(b0, b1, b2, b3)
return u32(b0 + b1 * 256 + b2 * 65536 + b3 * 16777216)
fun process_block(state, block)
// state: [a0,b0,c0,d0], block: 64 bytes array
a0 = state[0]
b0 = state[1]
c0 = state[2]
d0 = state[3]
// M[16] little-endian words
M = []
i = 0
while i < 16
j = i * 4
w = word32_le(block[j], block[j+1], block[j+2], block[j+3])
push(M, w)
i = i + 1
A = a0
B = b0
C = c0
D = d0
k = 0
while k < 64
if (k < 16)
f = F(B, C, D)
g = k
else if (k < 32)
f = G(B, C, D)
g = (5 * k + 1) % 16
else if (k < 48)
f = H(B, C, D)
g = (3 * k + 5) % 16
else
f = I(B, C, D)
g = (7 * k) % 16
tmp = D
sum = add32_4(A, f, K[k], M[g])
D = C
C = B
B = add32(B, rol32(sum, S[k]))
A = tmp
k = k + 1
state[0] = add32(state[0], A)
state[1] = add32(state[1], B)
state[2] = add32(state[2], C)
state[3] = add32(state[3], D)
return state
fun md5_bytes(bytes)
// returns array of 16 bytes of digest
data = pad_bytes(bytes)
// initial state
a0 = 1732584193 // 0x67452301
b0 = 4023233417 // 0xEFCDAB89
c0 = 2562383102 // 0x98BADCFE
d0 = 271733878 // 0x10325476
state = [a0, b0, c0, d0]
off = 0
N = len(data)
while off < N
block = []
i = 0
while i < 64
push(block, data[off + i])
i = i + 1
state = process_block(state, block)
off = off + 64
// output digest as little-endian bytes of A,B,C,D
out = []
j = 0
while j < 4
v = state[j]
// 4 bytes little-endian
push(out, (v ) % 256)
push(out, (v / 256) % 256)
push(out, (v / 65536) % 256)
push(out, (v / 16777216) % 256)
j = j + 1
return out
fun md5_hex(hexStr)
bytes = from_hex(hexStr)
digest = md5_bytes(bytes)
return bytes_to_hex(digest)
// Class wrapper to avoid global name collisions and show class usage
*/
class MD5()
// MD5 constants (as fields)
K = [
3614090360, 3905402710, 606105819, 3250441966, 4118548399, 1200080426, 2821735955, 4249261313,
1770035416, 2336552879, 4294925233, 2304563134, 1804603682, 4254626195, 2792965006, 1236535329,
4129170786, 3225465664, 643717713, 3921069994, 3593408605, 38016083, 3634488961, 3889429448,
568446438, 3275163606, 4107603335, 1163531501, 2850285829, 4243563512, 1735328473, 2368359562,
4294588738, 2272392833, 1839030562, 4259657740, 2763975236, 1272893353, 4139469664, 3200236656,
681279174, 3936430074, 3572445317, 76029189, 3654602809, 3873151461, 530742520, 3299628645,
4096336452, 1126891415, 2878612391, 4237533241, 1700485571, 2399980690, 4293915773, 2240044497,
1873313359, 4264355552, 2734768916, 1309151649, 4149444226, 3174756917, 718787259, 3951481745
]
S = [
7,12,17,22, 7,12,17,22, 7,12,17,22, 7,12,17,22,
5, 9,14,20, 5, 9,14,20, 5, 9,14,20, 5, 9,14,20,
4,11,16,23, 4,11,16,23, 4,11,16,23, 4,11,16,23,
6,10,15,21, 6,10,15,21, 6,10,15,21, 6,10,15,21
]
// 32-bit helpers
fun u32(this, x)
m = 4294967296
while x < 0
x = x + m
while x >= m
x = x - m
return x
fun shl32(this, x, s)
return shl(this.u32(x), s)
fun shr32(this, x, s)
return shr(this.u32(x), s)
fun rol32(this, x, s)
return rol(this.u32(x), s)
fun and32(this, a, b)
return band(this.u32(a), this.u32(b))
fun or32(this, a, b)
return bor(this.u32(a), this.u32(b))
fun xor32(this, a, b)
return bxor(this.u32(a), this.u32(b))
fun not32(this, x)
return bnot(this.u32(x))
// tiny adders
fun add32(this, a, b)
return this.u32(a + b)
fun add32_4(this, a, b, c, d)
return this.u32(this.u32(this.u32(a + b) + c) + d)
// hex helpers
fun hex_val(this, ch)
if (ch == "0")
return 0
else if (ch == "1")
return 1
else if (ch == "2")
return 2
else if (ch == "3")
return 3
else if (ch == "4")
return 4
else if (ch == "5")
return 5
else if (ch == "6")
return 6
else if (ch == "7")
return 7
else if (ch == "8")
return 8
else if (ch == "9")
return 9
else if (ch == "a" || ch == "A")
return 10
else if (ch == "b" || ch == "B")
return 11
else if (ch == "c" || ch == "C")
return 12
else if (ch == "d" || ch == "D")
return 13
else if (ch == "e" || ch == "E")
return 14
else if (ch == "f" || ch == "F")
return 15
else
return 0
fun byte_from_hex_pair(this, hh)
hi = this.hex_val(substr(hh, 0, 1))
lo = this.hex_val(substr(hh, 1, 1))
return hi * 16 + lo
fun from_hex(this, hex)
arr = []
i = 0
n = len(hex)
while i + 1 < n
b = this.byte_from_hex_pair(substr(hex, i, 2))
push(arr, b)
i = i + 2
return arr
fun two_hex(this, n)
n = n % 256
d = ["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"]
hi = n / 16
lo = n % 16
parts = [d[hi], d[lo]]
return join(parts, "")
fun bytes_to_hex(this, arr)
i = 0
out = []
while i < len(arr)
push(out, this.two_hex(arr[i]))
i = i + 1
return join(out, "")
// MD5 aux
fun F(this, x, y, z)
return this.or32(this.and32(x, y), this.and32(this.not32(x), z))
fun G(this, x, y, z)
return this.or32(this.and32(x, z), this.and32(y, this.not32(z)))
fun H(this, x, y, z)
return this.xor32(this.xor32(x, y), z)
fun I(this, x, y, z)
return this.xor32(y, this.or32(x, this.not32(z)))
fun pad_bytes(this, bytes)
L = len(bytes)
out = []
i = 0
while i < L
push(out, bytes[i])
i = i + 1
push(out, 128)
while (len(out) % 64) != 56
push(out, 0)
len_bits = L * 8
j = 0
while j < 8
b = (len_bits / pow(2, 8 * j)) % 256
push(out, b)
j = j + 1
return out
fun word32_le(this, b0, b1, b2, b3)
return this.u32(b0 + b1 * 256 + b2 * 65536 + b3 * 16777216)
fun process_block(this, state, block)
a0 = state[0]
b0 = state[1]
c0 = state[2]
d0 = state[3]
M = []
i = 0
while i < 16
j = i * 4
w = this.word32_le(block[j], block[j+1], block[j+2], block[j+3])
push(M, w)
i = i + 1
A = a0
B = b0
C = c0
D = d0
k = 0
while k < 64
if (k < 16)
f = this.F(B, C, D)
g = k
else if (k < 32)
f = this.G(B, C, D)
g = (5 * k + 1) % 16
else if (k < 48)
f = this.H(B, C, D)
g = (3 * k + 5) % 16
else
f = this.I(B, C, D)
g = (7 * k) % 16
tmp = D
sum = this.add32_4(A, f, this.K[k], M[g])
D = C
C = B
B = this.add32(B, this.rol32(sum, this.S[k]))
A = tmp
k = k + 1
state[0] = this.add32(state[0], A)
state[1] = this.add32(state[1], B)
state[2] = this.add32(state[2], C)
state[3] = this.add32(state[3], D)
return state
fun md5_bytes(this, bytes)
data = this.pad_bytes(bytes)
a0 = 1732584193
b0 = 4023233417
c0 = 2562383102
d0 = 271733878
state = [a0, b0, c0, d0]
off = 0
N = len(data)
while off < N
block = []
i = 0
while i < 64
push(block, data[off + i])
i = i + 1
state = this.process_block(state, block)
off = off + 64
out = []
j = 0
while j < 4
v = state[j]
push(out, (v) % 256)
push(out, (v / 256) % 256)
push(out, (v / 65536) % 256)
push(out, (v / 16777216) % 256)
j = j + 1
return out
fun md5_hex(this, hexStr)
bytes = this.from_hex(hexStr)
digest = this.md5_bytes(bytes)
return this.bytes_to_hex(digest)

261
lib/crypt/sha256.fun Normal file
View file

@ -0,0 +1,261 @@
/*
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the ISC license.
* https://opensource.org/license/isc-license-txt
*
* Added: 2025-09-29
*/
// lib/crypt/sha256.fun
// Pure Fun SHA-256 implementation operating on hex-string input.
//
// Public API (class):
// sha = SHA256()
// sha.sha256_hex(hexStr) -> digest hex string (lowercase)
//
// Example:
// print(SHA256().sha256_hex("616263")) // "abc" => ba7816bf...
class SHA256()
// 32-bit helpers
fun u32(this, x)
m = 4294967296
while x < 0
x = x + m
while x >= m
x = x - m
return x
fun add32(this, a, b)
return this.u32(a + b)
fun add32_5(this, a, b, c, d, e)
return this.u32(this.u32(this.u32(this.u32(a + b) + c) + d) + e)
// bitwise (use native VM ops)
fun rotr32(this, x, s)
return ror(this.u32(x), s)
fun shr32(this, x, s)
return shr(this.u32(x), s)
fun ch(this, x, y, z)
// (x & y) ^ (~x & z)
return bxor(band(x, y), band(bnot(x), z))
fun maj(this, x, y, z)
// (x & y) ^ (x & z) ^ (y & z)
return bxor(bxor(band(x, y), band(x, z)), band(y, z))
fun bigSigma0(this, x)
// ROTR2 ^ ROTR13 ^ ROTR22
return bxor(bxor(this.rotr32(x, 2), this.rotr32(x, 13)), this.rotr32(x, 22))
fun bigSigma1(this, x)
// ROTR6 ^ ROTR11 ^ ROTR25
return bxor(bxor(this.rotr32(x, 6), this.rotr32(x, 11)), this.rotr32(x, 25))
fun smallSigma0(this, x)
// ROTR7 ^ ROTR18 ^ SHR3
return bxor(bxor(this.rotr32(x, 7), this.rotr32(x, 18)), this.shr32(x, 3))
fun smallSigma1(this, x)
// ROTR17 ^ ROTR19 ^ SHR10
return bxor(bxor(this.rotr32(x, 17), this.rotr32(x, 19)), this.shr32(x, 10))
// constants
IV = [
1779033703, 3144134277, 1013904242, 2773480762,
1359893119, 2600822924, 528734635, 1541459225
]
K = [
1116352408, 1899447441, 3049323471, 3921009573, 961987163, 1508970993, 2453635748, 2870763221,
3624381080, 310598401, 607225278, 1426881987, 1925078388, 2162078206, 2614888103, 3248222580,
3835390401, 4022224774, 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986,
2554220882, 2821834349, 2952996808, 3210313671, 3336571891, 3584528711, 113926993, 338241895,
666307205, 773529912, 1294757372, 1396182291, 1695183700, 1986661051, 2177026350, 2456956037,
2730485921, 2820302411, 3259730800, 3345764771, 3516065817, 3600352804, 4094571909, 275423344,
430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 1537002063, 1747873779,
1955562222, 2024104815, 2227730452, 2361852424, 2428436474, 2756734187, 3204031479, 3329325298
]
// hex helpers
fun hex_val(this, ch)
if (ch == "0")
return 0
else if (ch == "1")
return 1
else if (ch == "2")
return 2
else if (ch == "3")
return 3
else if (ch == "4")
return 4
else if (ch == "5")
return 5
else if (ch == "6")
return 6
else if (ch == "7")
return 7
else if (ch == "8")
return 8
else if (ch == "9")
return 9
else if (ch == "a" || ch == "A")
return 10
else if (ch == "b" || ch == "B")
return 11
else if (ch == "c" || ch == "C")
return 12
else if (ch == "d" || ch == "D")
return 13
else if (ch == "e" || ch == "E")
return 14
else if (ch == "f" || ch == "F")
return 15
else
return 0
fun byte_from_hex_pair(this, hh)
hi = this.hex_val(substr(hh, 0, 1))
lo = this.hex_val(substr(hh, 1, 1))
return hi * 16 + lo
fun from_hex(this, hex)
arr = []
i = 0
n = len(hex)
while i + 1 < n
b = this.byte_from_hex_pair(substr(hex, i, 2))
push(arr, b)
i = i + 2
return arr
fun two_hex(this, n)
n = n % 256
d = ["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"]
hi = n / 16
lo = n % 16
parts = [d[hi], d[lo]]
return join(parts, "")
fun bytes_to_hex(this, arr)
i = 0
out = []
while i < len(arr)
push(out, this.two_hex(arr[i]))
i = i + 1
return join(out, "")
// padding (SHA-256)
fun pad_bytes(this, bytes)
L = len(bytes)
out = []
i = 0
while i < L
push(out, bytes[i])
i = i + 1
// append 0x80
push(out, 128)
// zeros until length % 64 == 56
while (len(out) % 64) != 56
push(out, 0)
// length in bits as 64-bit big-endian
len_bits = L * 8
j = 7
while j >= 0
b = (len_bits / pow(2, 8 * (7 - j))) % 256
push(out, b)
j = j - 1
return out
fun word32_be(this, b0, b1, b2, b3)
// big-endian
return this.u32(b0 * 16777216 + b1 * 65536 + b2 * 256 + b3)
fun process_block(this, state, block)
// prepare message schedule W[64]
W = []
t = 0
while t < 16
j = t * 4
push(W, this.word32_be(block[j], block[j+1], block[j+2], block[j+3]))
t = t + 1
while t < 64
s0 = this.smallSigma0(W[t - 15])
s1 = this.smallSigma1(W[t - 2])
wt = this.add32_5(W[t - 16], s0, W[t - 7], s1, 0)
// reuse array position via push (we can index existing after push)
push(W, wt)
t = t + 1
a = state[0]
b = state[1]
c = state[2]
d = state[3]
e = state[4]
f = state[5]
g = state[6]
h = state[7]
t = 0
while t < 64
T1 = this.add32_5(h, this.bigSigma1(e), this.ch(e, f, g), this.K[t], W[t])
T2 = this.add32(this.bigSigma0(a), this.maj(a, b, c))
h = g
g = f
f = e
e = this.add32(d, T1)
d = c
c = b
b = a
a = this.add32(T1, T2)
t = t + 1
state[0] = this.add32(state[0], a)
state[1] = this.add32(state[1], b)
state[2] = this.add32(state[2], c)
state[3] = this.add32(state[3], d)
state[4] = this.add32(state[4], e)
state[5] = this.add32(state[5], f)
state[6] = this.add32(state[6], g)
state[7] = this.add32(state[7], h)
return state
fun sha256_bytes(this, bytes)
data = this.pad_bytes(bytes)
// initial hash value (copy IV)
H = [ this.IV[0], this.IV[1], this.IV[2], this.IV[3], this.IV[4], this.IV[5], this.IV[6], this.IV[7] ]
off = 0
N = len(data)
while off < N
block = []
i = 0
while i < 64
push(block, data[off + i])
i = i + 1
H = this.process_block(H, block)
off = off + 64
// digest: 32 bytes big-endian
out = []
j = 0
while j < 8
v = H[j]
push(out, (v / 16777216) % 256)
push(out, (v / 65536) % 256)
push(out, (v / 256) % 256)
push(out, (v) % 256)
j = j + 1
return out
fun sha256_hex(this, hexStr)
bytes = this.from_hex(hexStr)
digest = this.sha256_bytes(bytes)
return this.bytes_to_hex(digest)

419
lib/crypt/sha512.fun Normal file
View file

@ -0,0 +1,419 @@
/*
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the ISC license.
* https://opensource.org/license/isc-license-txt
*
* Added: 2025-09-29
*/
// lib/crypt/sha512.fun
// Pure Fun SHA-512 implementation operating on hex-string input.
// 64-bit words are represented as [hi, lo] (two uint32 parts).
//
// Public API (class):
// s = SHA512()
// s.sha512_hex(hexStr) -> digest hex string (lowercase)
//
// Example:
// print(SHA512().sha512_hex("616263")) // "abc" =>
// ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
// 8: thats for SHA-256; SHA-512 for "abc" is:
// ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a
// 2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f
class SHA512()
// -------- hex helpers --------
fun hex_val(this, ch)
if (ch == "0")
return 0
else if (ch == "1")
return 1
else if (ch == "2")
return 2
else if (ch == "3")
return 3
else if (ch == "4")
return 4
else if (ch == "5")
return 5
else if (ch == "6")
return 6
else if (ch == "7")
return 7
else if (ch == "8")
return 8
else if (ch == "9")
return 9
else if (ch == "a" || ch == "A")
return 10
else if (ch == "b" || ch == "B")
return 11
else if (ch == "c" || ch == "C")
return 12
else if (ch == "d" || ch == "D")
return 13
else if (ch == "e" || ch == "E")
return 14
else if (ch == "f" || ch == "F")
return 15
else
return 0
fun byte_from_hex_pair(this, hh)
number hi = this.hex_val(substr(hh, 0, 1))
number lo = this.hex_val(substr(hh, 1, 1))
return hi * 16 + lo
fun from_hex(this, hex)
hex = to_string(hex)
arr = []
number i = 0
number n = len(hex)
while i + 1 < n
number b = this.byte_from_hex_pair(substr(hex, i, 2))
push(arr, b)
i = i + 2
return arr
fun two_hex(this, n)
n = n % 256
hexd = "0123456789abcdef"
number hi = (n / 16) % 16
number lo = n % 16
c1 = substr(hexd, hi, 1)
c2 = substr(hexd, lo, 1)
return join([c1, c2], "")
fun bytes_to_hex(this, arr)
out = []
number i = 0
number L = len(arr)
while i < L
push(out, this.two_hex(arr[i]))
i = i + 1
return join(out, "")
// -------- 32/64 helpers --------
fun u32(this, x)
m = 4294967296
while x < 0
x = x + m
while x >= m
x = x - m
return x
// 64-bit as [hi, lo] (each uint32)
fun pack64_be(this, b0,b1,b2,b3,b4,b5,b6,b7)
number hi = this.u32(b0 * 16777216 + b1 * 65536 + b2 * 256 + b3)
number lo = this.u32(b4 * 16777216 + b5 * 65536 + b6 * 256 + b7)
return [hi, lo]
fun add64(this, a, b)
number alo = this.u32(a[1])
number ahi = this.u32(a[0])
number blo = this.u32(b[1])
number bhi = this.u32(b[0])
number sum_lo = alo + blo
number carry = sum_lo / 4294967296
sum_lo = sum_lo % 4294967296
number sum_hi = (ahi + bhi + carry) % 4294967296
return [sum_hi, sum_lo]
fun add64_5(this, a, b, c, d, e)
return this.add64(this.add64(this.add64(this.add64(a, b), c), d), e)
fun xor64(this, a, b)
return [ bxor(a[0], b[0]), bxor(a[1], b[1]) ]
fun and64(this, a, b)
return [ band(a[0], b[0]), band(a[1], b[1]) ]
fun or64(this, a, b)
return [ bor(a[0], b[0]), bor(a[1], b[1]) ]
fun not64(this, a)
return [ bnot(a[0]), bnot(a[1]) ]
// logical right shift by n (0..63)
fun shr64(this, a, n)
number s = n % 64
number hi = a[0]
number lo = a[1]
if (s == 0)
return [hi, lo]
else if (s < 32)
number new_lo = bor( shr(lo, s), shl(hi, 32 - s) )
number new_hi = shr(hi, s)
return [ new_hi, new_lo ]
else
number k = s - 32
number new_lo2 = shr(hi, k)
return [ 0, new_lo2 ]
// rotate right by n (0..63)
fun rotr64(this, a, n)
number s = n % 64
number hi = a[0]
number lo = a[1]
if (s == 0)
return [hi, lo]
else if (s < 32)
number new_lo = bor( shr(lo, s), shl(hi, 32 - s) )
number new_hi = bor( shr(hi, s), shl(lo, 32 - s) )
return [ new_hi, new_lo ]
else
number k = s - 32
// swap roles
number new_lo2 = bor( shr(hi, k), shl(lo, 32 - k) )
number new_hi2 = bor( shr(lo, k), shl(hi, 32 - k) )
return [ new_hi2, new_lo2 ]
// SHA-512 logical functions
fun Ch(this, x, y, z)
// (x & y) ^ (~x & z)
return this.xor64(this.and64(x, y), this.and64(this.not64(x), z))
fun Maj(this, x, y, z)
// (x & y) ^ (x & z) ^ (y & z)
return this.xor64(this.xor64(this.and64(x, y), this.and64(x, z)), this.and64(y, z))
fun Sigma0(this, x)
return this.xor64(this.xor64(this.rotr64(x, 28), this.rotr64(x, 34)), this.rotr64(x, 39))
fun Sigma1(this, x)
return this.xor64(this.xor64(this.rotr64(x, 14), this.rotr64(x, 18)), this.rotr64(x, 41))
fun sigma0(this, x)
return this.xor64(this.xor64(this.rotr64(x, 1), this.rotr64(x, 8)), this.shr64(x, 7))
fun sigma1(this, x)
return this.xor64(this.xor64(this.rotr64(x, 19), this.rotr64(x, 61)), this.shr64(x, 6))
// -------- constants --------
IV = [
[0x6a09e667, 0xf3bcc908], [0xbb67ae85, 0x84caa73b], [0x3c6ef372, 0xfe94f82b], [0xa54ff53a, 0x5f1d36f1],
[0x510e527f, 0xade682d1], [0x9b05688c, 0x2b3e6c1f], [0x1f83d9ab, 0xfb41bd6b], [0x5be0cd19, 0x137e2179]
]
K = [
[0x428a2f98,0xd728ae22],[0x71374491,0x23ef65cd],[0xb5c0fbcf,0xec4d3b2f],[0xe9b5dba5,0x8189dbbc],
[0x3956c25b,0xf348b538],[0x59f111f1,0xb605d019],[0x923f82a4,0xaf194f9b],[0xab1c5ed5,0xda6d8118],
[0xd807aa98,0xa3030242],[0x12835b01,0x45706fbe],[0x243185be,0x4ee4b28c],[0x550c7dc3,0xd5ffb4e2],
[0x72be5d74,0xf27b896f],[0x80deb1fe,0x3b1696b1],[0x9bdc06a7,0x25c71235],[0xc19bf174,0xcf692694],
[0xe49b69c1,0x9ef14ad2],[0xefbe4786,0x384f25e3],[0x0fc19dc6,0x8b8cd5b5],[0x240ca1cc,0x77ac9c65],
[0x2de92c6f,0x592b0275],[0x4a7484aa,0x6ea6e483],[0x5cb0a9dc,0xbd41fbd4],[0x76f988da,0x831153b5],
[0x983e5152,0xee66dfab],[0xa831c66d,0x2db43210],[0xb00327c8,0x98fb213f],[0xbf597fc7,0xbeef0ee4],
[0xc6e00bf3,0x3da88fc2],[0xd5a79147,0x930aa725],[0x06ca6351,0xe003826f],[0x14292967,0x0a0e6e70],
[0x27b70a85,0x46d22ffc],[0x2e1b2138,0x5c26c926],[0x4d2c6dfc,0x5ac42aed],[0x53380d13,0x9d95b3df],
[0x650a7354,0x8baf63de],[0x766a0abb,0x3c77b2a8],[0x81c2c92e,0x47edaee6],[0x92722c85,0x1482353b],
[0xa2bfe8a1,0x4cf10364],[0xa81a664b,0xbc423001],[0xc24b8b70,0xd0f89791],[0xc76c51a3,0x0654be30],
[0xd192e819,0xd6ef5218],[0xd6990624,0x5565a910],[0xf40e3585,0x5771202a],[0x106aa070,0x32bbd1b8],
[0x19a4c116,0xb8d2d0c8],[0x1e376c08,0x5141ab53],[0x2748774c,0xdf8eeb99],[0x34b0bcb5,0xe19b48a8],
[0x391c0cb3,0xc5c95a63],[0x4ed8aa4a,0xe3418acb],[0x5b9cca4f,0x7763e373],[0x682e6ff3,0xd6b2b8a3],
[0x748f82ee,0x5defb2fc],[0x78a5636f,0x43172f60],[0x84c87814,0xa1f0ab72],[0x8cc70208,0x1a6439ec],
[0x90befffa,0x23631e28],[0xa4506ceb,0xde82bde9],[0xbef9a3f7,0xb2c67915],[0xc67178f2,0xe372532b],
[0xca273ece,0xea26619c],[0xd186b8c7,0x21c0c207],[0xeada7dd6,0xcde0eb1e],[0xf57d4f7f,0xee6ed178],
[0x06f067aa,0x72176fba],[0x0a637dc5,0xa2c898a6],[0x113f9804,0xbef90dae],[0x1b710b35,0x131c471b],
[0x28db77f5,0x23047d84],[0x32caab7b,0x40c72493],[0x3c9ebe0a,0x15c9bebc],[0x431d67c4,0x9c100d4c],
[0x4cc5d4be,0xcb3e42b6],[0x597f299c,0xfc657e2a],[0x5fcb6fab,0x3ad6faec],[0x6c44198c,0x4a475817]
]
// -------- padding --------
fun pad_bytes(this, bytes)
number L = len(bytes)
out = []
// copy bytes
number i = 0
while i < L
push(out, bytes[i])
i = i + 1
// append 0x80
push(out, 128)
// pad zeros until length % 128 == 112 (i.e., 896 bits)
while (len(out) % 128) != 112
push(out, 0)
// append 128-bit length (we use upper 64 bits zero, lower 64 = L*8)
// upper 8 bytes zero:
number u = 0
number j = 0
while j < 8
push(out, 0)
j = j + 1
// lower 8 bytes big-endian (most significant byte first)
number bits = L * 8
number t = 7
while t >= 0
number b = (bits / pow(2, 8 * t)) % 256
push(out, b)
t = t - 1
return out
// -------- core --------
fun process_block(this, H, block)
// W ring buffer of 16 words ([hi, lo])
W = []
number i = 0
while i < 16
number j = i * 8
push(W, this.pack64_be(block[j],block[j+1],block[j+2],block[j+3],
block[j+4],block[j+5],block[j+6],block[j+7]))
i = i + 1
a = H[0]
b = H[1]
c = H[2]
d = H[3]
e = H[4]
f = H[5]
g = H[6]
h = H[7]
number t = 0
while t < 80
number idx = t % 16
if (t >= 16)
w2 = W[(t - 2) % 16]
w7 = W[(t - 7) % 16]
w15 = W[(t - 15) % 16]
w16 = W[(t - 16) % 16] // current slot value
s1 = this.sigma1(w2)
s0 = this.sigma0(w15)
tmp = this.add64(w16, s0)
tmp = this.add64(tmp, w7)
W[idx] = this.add64(tmp, s1)
Wt = W[idx]
// Precompute hot terms once per round
Se1 = this.Sigma1(e)
ch = this.Ch(e, f, g)
// T1 = h + Se1 + ch + K[t] + Wt (chained to avoid 5-arg helper)
tmp1 = this.add64(h, Se1)
tmp1 = this.add64(tmp1, ch)
tmp1 = this.add64(tmp1, this.K[t])
T1 = this.add64(tmp1, Wt)
// T2 = Sigma0(a) + Maj(a, b, c)
T2 = this.add64(this.Sigma0(a), this.Maj(a, b, c))
// State update
h = g
g = f
f = e
e = this.add64(d, T1)
d = c
c = b
b = a
a = this.add64(T1, T2)
t = t + 1
H[0] = this.add64(H[0], a)
H[1] = this.add64(H[1], b)
H[2] = this.add64(H[2], c)
H[3] = this.add64(H[3], d)
H[4] = this.add64(H[4], e)
H[5] = this.add64(H[5], f)
H[6] = this.add64(H[6], g)
H[7] = this.add64(H[7], h)
return H
fun sha512_bytes(this, bytes)
data = this.pad_bytes(bytes)
// init H (deep copy IV)
H = [ this.IV[0], this.IV[1], this.IV[2], this.IV[3], this.IV[4], this.IV[5], this.IV[6], this.IV[7] ]
number off = 0
number N = len(data)
while off < N
block = []
number i = 0
while i < 128
push(block, data[off + i])
i = i + 1
H = this.process_block(H, block)
off = off + 128
// output 64 bytes big-endian from H[0..7]
out = []
number j = 0
while j < 8
word = H[j]
number hi = word[0]
number lo = word[1]
// hi
push(out, (hi / 16777216) % 256)
push(out, (hi / 65536) % 256)
push(out, (hi / 256) % 256)
push(out, hi % 256)
// lo
push(out, (lo / 16777216) % 256)
push(out, (lo / 65536) % 256)
push(out, (lo / 256) % 256)
push(out, lo % 256)
j = j + 1
return out
fun sha512_hex(this, hexStr)
bytes = this.from_hex(hexStr)
digest = this.sha512_bytes(bytes)
return this.bytes_to_hex(digest)
// Convert a printable ASCII string (0x20..0x7E) to byte array
fun string_to_bytes_ascii(this, s)
str = to_string(s)
out = []
number i = 0
// ASCII printable ranges
P1 = " !\"#$%&'()*+,-./" // 32..47
P2 = "0123456789" // 48..57
P3 = ":;<=>?@" // 58..64
P4 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" // 65..90
P5 = "[\\]^_`" // 91..96
P6 = "abcdefghijklmnopqrstuvwxyz" // 97..122
P7 = "{|}~" // 123..126
while true
ch = substr(str, i, 1)
if (typeof(ch) != "String" || ch == "")
break
number code = -1
idx = find(P1, ch)
if (idx >= 0)
code = 32 + idx
else
idx = find(P2, ch)
if (idx >= 0)
code = 48 + idx
else
idx = find(P3, ch)
if (idx >= 0)
code = 58 + idx
else
idx = find(P4, ch)
if (idx >= 0)
code = 65 + idx
else
idx = find(P5, ch)
if (idx >= 0)
code = 91 + idx
else
idx = find(P6, ch)
if (idx >= 0)
code = 97 + idx
else
idx = find(P7, ch)
if (idx >= 0)
code = 123 + idx
else
// non-printable -> 0
code = 0
push(out, code)
i = i + 1
return out
// Hash raw string bytes (printable ASCII). Example: sha512_str("616263") -> ee2109...
fun sha512_str(this, str)
bytes = this.string_to_bytes_ascii(str)
digest = this.sha512_bytes(bytes)
return this.bytes_to_hex(digest)

View file

@ -112,7 +112,17 @@ typedef enum {
OP_WRITE_FILE, // pops data string, path string; pushes 1/0
// OS
OP_ENV // pops name string; pushes value string (or "")
OP_ENV, // pops name string; pushes value string (or "")
// Bitwise (32-bit) and shifts/rotates
OP_BAND, // pops b, a; pushes (uint32_t)(a & b)
OP_BOR, // pops b, a; pushes (uint32_t)(a | b)
OP_BXOR, // pops b, a; pushes (uint32_t)(a ^ b)
OP_BNOT, // pops a; pushes (uint32_t)(~a)
OP_SHL, // pops s, a; pushes (uint32_t)(a << (s&31))
OP_SHR, // pops s, a; pushes (uint32_t)(a >> (s&31)) logical
OP_ROTL, // pops s, a; pushes rotl32(a, s)
OP_ROTR // pops s, a; pushes rotr32(a, s)
} OpCode;
typedef struct {

View file

@ -1073,6 +1073,72 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
return 1;
}
/* bitwise ops (32-bit) */
if (strcmp(name, "band") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "band expects 2 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "band expects 2 args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_BAND, 0);
free(name);
return 1;
}
if (strcmp(name, "bor") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "bor expects 2 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "bor expects 2 args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_BOR, 0);
free(name);
return 1;
}
if (strcmp(name, "bxor") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "bxor expects 2 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "bxor expects 2 args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_BXOR, 0);
free(name);
return 1;
}
if (strcmp(name, "bnot") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "bnot expects 1 arg"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "bnot expects 1 arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_BNOT, 0);
free(name);
return 1;
}
if (strcmp(name, "shl") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "shl expects 2 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "shl expects 2 args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_SHL, 0);
free(name);
return 1;
}
if (strcmp(name, "shr") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "shr expects 2 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "shr expects 2 args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_SHR, 0);
free(name);
return 1;
}
if (strcmp(name, "rol") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "rol expects 2 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "rol expects 2 args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_ROTL, 0);
free(name);
return 1;
}
if (strcmp(name, "ror") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "ror expects 2 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "ror expects 2 args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_ROTR, 0);
free(name);
return 1;
}
/* push function value first */
if (local_idx >= 0) {
bytecode_add_instruction(bc, OP_LOAD_LOCAL, local_idx);
@ -3308,7 +3374,48 @@ Bytecode *parse_file_to_bytecode(const char *path) {
calc_line_col(compile_src, compile_len, g_err_pos, &line, &col);
g_err_line = line;
g_err_col = col;
fprintf(stderr, "Parse error %s:%d:%d: %s\n", path ? path : "<input>", line, col, g_err_msg);
/* Try to locate include context marker preceding error */
const char *marker = "// __include_begin__: ";
size_t mlen = strlen(marker);
int inner_line = -1;
char inc_path[512]; inc_path[0] = '\0';
/* scan backward to find last marker line */
size_t scan = g_err_pos;
while (scan > 0) {
/* find start of current line */
size_t ls = scan;
while (ls > 0 && compile_src[ls - 1] != '\n') ls--;
/* check if this line starts with marker */
if (ls + mlen <= compile_len && strncmp(compile_src + ls, marker, mlen) == 0) {
/* extract path until end-of-line */
size_t p = ls + mlen;
size_t pe = p;
while (pe < compile_len && compile_src[pe] != '\n' && (pe - p) < sizeof(inc_path) - 1) pe++;
memcpy(inc_path, compile_src + p, pe - p);
inc_path[pe - p] = '\0';
/* compute inner line as number of newlines from (pe+1) to error position */
int count = 1;
size_t q = (pe < compile_len && compile_src[pe] == '\n') ? (pe + 1) : pe;
while (q < g_err_pos) {
if (compile_src[q] == '\n') count++;
q++;
}
inner_line = count;
break;
}
/* move to previous line */
if (ls == 0) break;
scan = ls - 1;
}
if (inner_line > 0 && inc_path[0] != '\0') {
fprintf(stderr, "Parse error %s:%d:%d: %s (in %s:%d)\n",
path ? path : "<input>", line, col, g_err_msg, inc_path, inner_line);
} else {
fprintf(stderr, "Parse error %s:%d:%d: %s\n", path ? path : "<input>", line, col, g_err_msg);
}
if (bc) bytecode_free(bc);
if (prep) free(prep);
free(src);

View file

@ -325,6 +325,10 @@ static char *preprocess_includes_internal(const char *src, int depth) {
char *exp = preprocess_includes_internal(inc, depth + 1);
free(inc);
if (exp) {
/* mark file origin for better error messages */
sb_append(&out, "// __include_begin__: ");
sb_append(&out, resolved);
sb_append(&out, "\n");
sb_append(&out, exp);
/* ensure included chunk ends with newline to preserve line structure */
if (out.len == 0 || out.buf[out.len - 1] != '\n') sb_append_ch(&out, '\n');

View file

@ -25,11 +25,21 @@ static VM *g_active_vm = NULL;
static int fun_vm_vfprintf(FILE *stream, const char *fmt, va_list ap) {
int written = vfprintf(stream, fmt, ap);
if (stream == stderr && g_active_vm && g_active_vm->current_line > 0) {
/* If original message didn't end with newline, add a space first */
if (written > 0) {
/* best-effort: do not attempt to inspect fmt string fully; just append line info */
/* Append more context: line number, opcode name, and ip */
const char *opname = "unknown";
int ip = -1;
if (g_active_vm->fp >= 0) {
Frame *f = &g_active_vm->frames[g_active_vm->fp];
ip = f->ip - 1;
if (f->fn && ip >= 0 && ip < f->fn->instr_count) {
int op = f->fn->instructions[ip].op;
if (op >= 0 && op < (int)(sizeof(opcode_names)/sizeof(opcode_names[0]))) {
opname = opcode_names[op];
}
}
}
fprintf(stream == stderr ? stderr : stream, " (line %d)\n", g_active_vm->current_line);
fprintf(stream == stderr ? stderr : stream, " (line %d, op %s @ip %d)\n",
g_active_vm->current_line, opname, ip);
}
return written;
}
@ -249,6 +259,16 @@ void vm_run(VM *vm, Bytecode *entry) {
#include "vm/arrays/slice.c"
#include "vm/arrays/zip.c"
/* Bitwise and shifts/rotates */
#include "vm/bitwise/band.c"
#include "vm/bitwise/bor.c"
#include "vm/bitwise/bxor.c"
#include "vm/bitwise/bnot.c"
#include "vm/bitwise/shl.c"
#include "vm/bitwise/shr.c"
#include "vm/bitwise/rol.c"
#include "vm/bitwise/ror.c"
#include "vm/core/call.c"
#include "vm/core/dup.c"
#include "vm/core/halt.c"

View file

@ -33,7 +33,8 @@ static const char *opcode_names[] = {
"ENUMERATE","ZIP",
"MIN","MAX","CLAMP","ABS","POW","RANDOM_SEED","RANDOM_INT",
"MAKE_MAP","KEYS","VALUES","HAS_KEY",
"READ_FILE","WRITE_FILE","ENV"
"READ_FILE","WRITE_FILE","ENV",
"BAND","BOR","BXOR","BNOT","SHL","SHR","ROTL","ROTR"
};
typedef struct {
@ -76,7 +77,7 @@ void vm_dump_globals(VM *vm);
void vm_run(VM *vm, Bytecode *entry);
static inline int opcode_is_valid(int op) {
return op >= OP_NOP && op <= OP_ENV; // all current opcodes
return op >= OP_NOP && op <= OP_ROTR; // all current opcodes
}
#endif

View file

@ -59,7 +59,8 @@ case OP_INDEX_GET: {
free_value(idx);
push_value(vm, out);
} else {
fprintf(stderr, "Runtime type error: INDEX_GET expects array or map\n");
fprintf(stderr, "Runtime type error: INDEX_GET expects array or map (got container=%s, index=%s)\n",
value_type_name(container.type), value_type_name(idx.type));
exit(1);
}
break;

27
src/vm/bitwise/band.c Normal file
View file

@ -0,0 +1,27 @@
/**
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the ISC license.
* https://opensource.org/license/isc-license-txt
*
* Added: 2025-09-29
*/
/**
* OP_BAND: bitwise AND (uint32 32-bit)
* pops: b, a
* pushes: (uint32_t)(a & b)
*/
case OP_BAND: {
Value vb = pop_value(vm);
Value va = pop_value(vm);
uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u;
uint32_t b = (vb.type == VAL_INT) ? (uint32_t)vb.i : 0u;
uint32_t r = a & b;
free_value(vb);
free_value(va);
push_value(vm, make_int((int64_t)(uint64_t)r));
break;
}

24
src/vm/bitwise/bnot.c Normal file
View file

@ -0,0 +1,24 @@
/**
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the ISC license.
* https://opensource.org/license/isc-license-txt
*
* Added: 2025-09-29
*/
/**
* OP_BNOT: bitwise NOT (uint32 32-bit)
* pops: a
* pushes: (uint32_t)(~a)
*/
case OP_BNOT: {
Value va = pop_value(vm);
uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u;
uint32_t r = ~a;
free_value(va);
push_value(vm, make_int((int64_t)(uint64_t)r));
break;
}

27
src/vm/bitwise/bor.c Normal file
View file

@ -0,0 +1,27 @@
/**
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the ISC license.
* https://opensource.org/license/isc-license-txt
*
* Added: 2025-09-29
*/
/**
* OP_BOR: bitwise OR (uint32 32-bit)
* pops: b, a
* pushes: (uint32_t)(a | b)
*/
case OP_BOR: {
Value vb = pop_value(vm);
Value va = pop_value(vm);
uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u;
uint32_t b = (vb.type == VAL_INT) ? (uint32_t)vb.i : 0u;
uint32_t r = a | b;
free_value(vb);
free_value(va);
push_value(vm, make_int((int64_t)(uint64_t)r));
break;
}

27
src/vm/bitwise/bxor.c Normal file
View file

@ -0,0 +1,27 @@
/**
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the ISC license.
* https://opensource.org/license/isc-license-txt
*
* Added: 2025-09-29
*/
/**
* OP_BXOR: bitwise XOR (uint32 32-bit)
* pops: b, a
* pushes: (uint32_t)(a ^ b)
*/
case OP_BXOR: {
Value vb = pop_value(vm);
Value va = pop_value(vm);
uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u;
uint32_t b = (vb.type == VAL_INT) ? (uint32_t)vb.i : 0u;
uint32_t r = a ^ b;
free_value(vb);
free_value(va);
push_value(vm, make_int((int64_t)(uint64_t)r));
break;
}

28
src/vm/bitwise/rol.c Normal file
View file

@ -0,0 +1,28 @@
/**
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the ISC license.
* https://opensource.org/license/isc-license-txt
*
* Added: 2025-09-29
*/
/**
* OP_ROTL: rotate left (uint32)
* pops: s, a
* pushes: rotl32(a, s)
*/
case OP_ROTL: {
Value vs = pop_value(vm);
Value va = pop_value(vm);
uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u;
uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u;
s &= 31u;
uint32_t r = (s == 0u) ? a : ((a << s) | (a >> (32u - s)));
free_value(vs);
free_value(va);
push_value(vm, make_int((int64_t)(uint64_t)r));
break;
}

28
src/vm/bitwise/ror.c Normal file
View file

@ -0,0 +1,28 @@
/**
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the ISC license.
* https://opensource.org/license/isc-license-txt
*
* Added: 2025-09-29
*/
/**
* OP_ROTR: rotate right (uint32)
* pops: s, a
* pushes: rotr32(a, s)
*/
case OP_ROTR: {
Value vs = pop_value(vm);
Value va = pop_value(vm);
uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u;
uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u;
s &= 31u;
uint32_t r = (s == 0u) ? a : ((a >> s) | (a << (32u - s)));
free_value(vs);
free_value(va);
push_value(vm, make_int((int64_t)(uint64_t)r));
break;
}

28
src/vm/bitwise/shl.c Normal file
View file

@ -0,0 +1,28 @@
/**
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the ISC license.
* https://opensource.org/license/isc-license-txt
*
* Added: 2025-09-29
*/
/**
* OP_SHL: logical left shift (uint32)
* pops: s, a
* pushes: (uint32_t)(a << (s&31))
*/
case OP_SHL: {
Value vs = pop_value(vm);
Value va = pop_value(vm);
uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u;
uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u;
s &= 31u;
uint32_t r = (s == 0u) ? a : (a << s);
free_value(vs);
free_value(va);
push_value(vm, make_int((int64_t)(uint64_t)r));
break;
}

28
src/vm/bitwise/shr.c Normal file
View file

@ -0,0 +1,28 @@
/**
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the ISC license.
* https://opensource.org/license/isc-license-txt
*
* Added: 2025-09-29
*/
/**
* OP_SHR: logical right shift (uint32)
* pops: s, a
* pushes: (uint32_t)(a >> (s&31)) using logical shift
*/
case OP_SHR: {
Value vs = pop_value(vm);
Value va = pop_value(vm);
uint32_t a = (va.type == VAL_INT) ? (uint32_t)va.i : 0u;
uint32_t s = (vs.type == VAL_INT) ? (uint32_t)vs.i : 0u;
s &= 31u;
uint32_t r = (s == 0u) ? a : (a >> s);
free_value(vs);
free_value(va);
push_value(vm, make_int((int64_t)(uint64_t)r));
break;
}

View file

@ -40,8 +40,8 @@ case OP_LEN: {
len = array_length(&a);
if (len < 0) len = 0;
} else {
fprintf(stderr, "Runtime type error: LEN expects array or string\n");
exit(1);
/* Be lenient: for non-array/non-string, treat length as 0 */
push_value(vm, make_int(0));
}
free_value(a);
push_value(vm, make_int(len));