diff --git a/CMakeLists.txt b/CMakeLists.txt index fa6acc6..9b2b554 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(fun VERSION 0.16.3 LANGUAGES C) +project(fun VERSION 0.16.4 LANGUAGES C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/stdlib_showcase.fun b/examples/stdlib_showcase.fun new file mode 100644 index 0000000..61b368e --- /dev/null +++ b/examples/stdlib_showcase.fun @@ -0,0 +1,54 @@ +#!/usr/bin/env fun + +/* + * Standard library showcase example for arrays, strings, math, ranges, and base64. + */ + +#include +#include +#include +#include +#include + +print("=== Arrays ===") +arr = [1,2,2,3,4] +print(join(array_slice(arr, 1, 3), ",")) // "2,2,3" +print(join(array_reverse(arr), ",")) // "4,3,2,2,1" +print(array_index_of(arr, 3)) // 3's index +print(array_contains(arr, 5)) // 0 +print(join(array_unique(arr), ",")) // "1,2,3,4" +print(join(array_flatten1([[1,2],[3],[4,5]]), ",")) // "1,2,3,4,5" + +print("=== Strings ===") +s = " Hello World \n" +print("["+str_trim(s)+"]") // "[Hello World]" +print(str_starts_with("foobar", "foo")) // 1 +print(str_ends_with("foobar", "bar")) // 1 +parts = str_split("a,b,c", ",") +print(join(parts, "|")) // "a|b|c" +print(str_replace_all("banana", "na", "NA")) // "baNANA" +print(str_to_lower("FUN")) // "fun" +print(str_to_upper("Fun")) // "FUN" +print(str_repeat("ha", 3)) // "hahaha" + +print("=== Math ===") +print(abs(-5)) // 5 +print(clamp(42, 0, 10)) // 10 +print(gcd(54, 24)) // 6 +print(lcm(21, 6)) // 42 +print(powi(3, 5)) // 243 +print(array_min([9,2,8,3])) // 2 +print(array_max([9,2,8,3])) // 9 +print(min3(3,1,2)) // 1 +print(max3(3,1,2)) // 3 + +print("=== Range ===") +print(join(range(5), ",")) // "0,1,2,3,4" +print(join(range2(3, 8), ",")) // "3,4,5,6,7" +print(join(range3(10, 0, -3), ",")) // "10,7,4,1" + +print("=== Base64 ===") +bytes = [0x48,0x65,0x6c,0x6c,0x6f] // "Hello" +b64 = b64_encode_bytes(bytes) +print(b64) // "SGVsbG8=" +print(join(b64_decode_to_bytes(b64), ",")) // "72,101,108,108,111" diff --git a/lib/arrays.fun b/lib/arrays.fun new file mode 100644 index 0000000..4755911 --- /dev/null +++ b/lib/arrays.fun @@ -0,0 +1,117 @@ +/* + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-01 + */ + +// Array utilities + +// Return a shallow slice of arr starting at 'start' with 'count' elements (clamped) +fun array_slice(arr, start, count) + number n = len(arr) + number s = start + if (s < 0) + s = 0 + if (s > n) + s = n + number c = count + if (c < 0) + c = 0 + if (s + c > n) + c = n - s + out = [] + number i = 0 + while i < c + push(out, arr[s + i]) + i = i + 1 + return out + +// Return a new array with elements in reverse order +fun array_reverse(arr) + out = [] + number i = len(arr) - 1 + while i >= 0 + push(out, arr[i]) + i = i - 1 + return out + +// Concatenate two arrays and return a new array +fun array_concat(a, b) + out = [] + number i = 0 + while i < len(a) + push(out, a[i]) + i = i + 1 + number j = 0 + while j < len(b) + push(out, b[j]) + j = j + 1 + return out + +// Return the index of the first occurrence of value in arr, or -1 +fun array_index_of(arr, value) + number i = 0 + number n = len(arr) + while i < n + if (arr[i] == value) + return i + i = i + 1 + return -1 + +// Return 1 if arr contains value, else 0 +fun array_contains(arr, value) + return array_index_of(arr, value) >= 0 + +// Return a new array with only the first occurrence of each element (stable) +fun array_unique(arr) + out = [] + number i = 0 + number n = len(arr) + while i < n + v = arr[i] + if (array_index_of(out, v) < 0) + push(out, v) + i = i + 1 + return out + +// Flatten one level: [[1,2],[3],[4,5]] -> [1,2,3,4,5] +fun array_flatten1(arr) + out = [] + number i = 0 + number n = len(arr) + while i < n + item = arr[i] + // Treat nested arrays as flattenable + if (typeof(item) == "Array") + number j = 0 + number m = len(item) + while j < m + push(out, item[j]) + j = j + 1 + else + push(out, item) + i = i + 1 + return out + +// Push value to arr only if not present (mutates arr), returns arr +fun array_push_unique_mut(arr, value) + if (!array_contains(arr, value)) + push(arr, value) + return arr + +// Return a new array that contains all elements of arr and value appended only if not present +fun array_push_unique(arr, value) + out = [] + number i = 0 + number n = len(arr) + while i < n + push(out, arr[i]) + i = i + 1 + if (!array_contains(out, value)) + push(out, value) + return out diff --git a/lib/core/string_to_bytes_ascii.fun b/lib/core/string_to_bytes_ascii.fun index 0523fe4..09de6fd 100644 --- a/lib/core/string_to_bytes_ascii.fun +++ b/lib/core/string_to_bytes_ascii.fun @@ -9,54 +9,4 @@ * Added: 2025-10-01 */ -// ASCII string to bytes (printable ASCII 0x20..0x7E) -fun string_to_bytes_ascii(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 + diff --git a/lib/encoding/base64.fun b/lib/encoding/base64.fun new file mode 100644 index 0000000..2b9071e --- /dev/null +++ b/lib/encoding/base64.fun @@ -0,0 +1,89 @@ +/* + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-01 + */ + +// Base64 encode/decode for byte arrays (RFC 4648, standard alphabet) + +fun b64_encode_bytes(bytes) + table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + out = [] + number i = 0 + number n = len(bytes) + while i + 2 < n + number b0 = bytes[i] + number b1 = bytes[i + 1] + number b2 = bytes[i + 2] + number v = b0 * 65536 + b1 * 256 + b2 + number c0 = (v / 262144) % 64 // bits 18..23 + number c1 = (v / 4096) % 64 // bits 12..17 + number c2 = (v / 64) % 64 // bits 6..11 + number c3 = v % 64 // bits 0..5 + push(out, substr(table, c0, 1)) + push(out, substr(table, c1, 1)) + push(out, substr(table, c2, 1)) + push(out, substr(table, c3, 1)) + i = i + 3 + + number rem = n - i + if (rem == 1) + number b0 = bytes[i] + number v = b0 * 65536 + number c0 = (v / 262144) % 64 + number c1 = (v / 4096) % 64 + push(out, substr(table, c0, 1)) + push(out, substr(table, c1, 1)) + push(out, "=") + push(out, "=") + else if (rem == 2) + number b0 = bytes[i] + number b1 = bytes[i + 1] + number v = b0 * 65536 + b1 * 256 + number c0 = (v / 262144) % 64 + number c1 = (v / 4096) % 64 + number c2 = (v / 64) % 64 + push(out, substr(table, c0, 1)) + push(out, substr(table, c1, 1)) + push(out, substr(table, c2, 1)) + push(out, "=") + + return join(out, "") + +fun b64_decode_to_bytes(s) + table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + str = to_string(s) + out = [] + // build reverse lookup: index in table is value + // Since we don't have a map, use find on each char + number i = 0 + number n = len(str) + while i < n + // read 4 chars + c0 = substr(str, i, 1) + c1 = substr(str, i + 1, 1) + c2 = substr(str, i + 2, 1) + c3 = substr(str, i + 3, 1) + number v0 = find(table, c0) + number v1 = find(table, c1) + number pad2 = (c2 == "=") + number pad3 = (c3 == "=") + number v2 = pad2 ? 0 : find(table, c2) + number v3 = pad3 ? 0 : find(table, c3) + number twentyfour = v0 * 262144 + v1 * 4096 + v2 * 64 + v3 + // bytes + number b0 = (twentyfour / 65536) % 256 + number b1 = (twentyfour / 256) % 256 + number b2 = twentyfour % 256 + push(out, b0) + if (!pad2) + push(out, b1) + if (!pad3) + push(out, b2) + i = i + 4 + return out diff --git a/lib/math.fun b/lib/math.fun new file mode 100644 index 0000000..9ed7763 --- /dev/null +++ b/lib/math.fun @@ -0,0 +1,90 @@ +/* + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-01 + */ + +// Math helpers + +fun abs(x) + if (x < 0) + return -x + return x + +fun clamp(x, lo, hi) + v = x + if (v < lo) v = lo + if (v > hi) v = hi + return v + +fun gcd(a, b) + number x = abs(a) + number y = abs(b) + while y != 0 + t = x % y + x = y + y = t + return x + +fun lcm(a, b) + if (a == 0) || (b == 0) + return 0 + return abs(a * b) / gcd(a, b) + +// integer exponent (exp >= 0) +fun powi(base, exp) + number e = exp + if (e < 0) + // simple fallback for negative: 0 for now (no floats) + return 0 + number result = 1 + number b = base + while e > 0 + if (e % 2 == 1) + result = result * b + b = b * b + e = e / 2 + return result + +// Min/Max over three numbers +fun min3(a, b, c) + m = a + if (b < m) m = b + if (c < m) m = c + return m + +fun max3(a, b, c) + m = a + if (b > m) m = b + if (c > m) m = c + return m + +// Array min/max (for non-empty arrays) +fun array_min(arr) + number n = len(arr) + if (n == 0) + return 0 + m = arr[0] + number i = 1 + while i < n + if (arr[i] < m) + m = arr[i] + i = i + 1 + return m + +fun array_max(arr) + number n = len(arr) + if (n == 0) + return 0 + m = arr[0] + number i = 1 + while i < n + if (arr[i] > m) + m = arr[i] + i = i + 1 + return m diff --git a/lib/strings.fun b/lib/strings.fun index f5dfd56..35439dc 100644 --- a/lib/strings.fun +++ b/lib/strings.fun @@ -2,14 +2,202 @@ * This file is part of the Fun programming language. * https://hanez.org/project/fun/ * - * Copyright 2025 Johannes Findeisen + * Copyright 2025 Johannes Findeisen * Licensed under the terms of the Apache-2.0 license. * https://opensource.org/license/apache-2-0 * * Added: 2025-10-01 */ -// This file should not contain code! It should just include all string related -// functions from core/*.fun. +// Extra string utilities -#include +// Trim whitespace on the left (space, tab, CR, LF) +fun str_ltrim(s) + src = to_string(s) + number i = 0 + ws = " \t\r\n" + while i < len(src) + ch = substr(src, i, 1) + if (find(ws, ch) < 0) + break + i = i + 1 + return substr(src, i, len(src) - i) + +// Trim whitespace on the right +fun str_rtrim(s) + src = to_string(s) + number i = len(src) - 1 + ws = " \t\r\n" + while i >= 0 + ch = substr(src, i, 1) + if (find(ws, ch) < 0) + break + i = i - 1 + return substr(src, 0, i + 1) + +// Trim both sides +fun str_trim(s) + return str_rtrim(str_ltrim(s)) + +// Return 1 if s starts with prefix, else 0 +fun str_starts_with(s, prefix) + a = to_string(s) + p = to_string(prefix) + if (len(p) > len(a)) + return 0 + return substr(a, 0, len(p)) == p + +// Return 1 if s ends with suffix, else 0 +fun str_ends_with(s, suffix) + a = to_string(s) + p = to_string(suffix) + number la = len(a) + number lp = len(p) + if (lp > la) + return 0 + return substr(a, la - lp, lp) == p + +// Split by a single-character delimiter, returns array of strings +fun str_split(s, delim) + src = to_string(s) + d = to_string(delim) + // Use only the first character of delim + if (len(d) == 0) + return [src] + dd = substr(d, 0, 1) + parts = [] + buf = [] + number i = 0 + number n = len(src) + while i < n + ch = substr(src, i, 1) + if (ch == dd) + push(parts, join(buf, "")) + buf = [] + else + push(buf, ch) + i = i + 1 + // tail + push(parts, join(buf, "")) + return parts + +// Replace all occurrences of 'from' with 'to' (naive scan) +fun str_replace_all(s, from, to) + src = to_string(s) + f = to_string(from) + t = to_string(to) + number n = len(src) + number lf = len(f) + if (lf == 0) + return src + out = [] + number i = 0 + while i < n + if (i + lf <= n) && (substr(src, i, lf) == f) + push(out, t) + i = i + lf + else + push(out, substr(src, i, 1)) + i = i + 1 + return join(out, "") + +// Lowercase transform for ASCII A..Z +fun str_to_lower(s) + src = to_string(s) + U = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + L = "abcdefghijklmnopqrstuvwxyz" + out = [] + number i = 0 + number n = len(src) + while i < n + ch = substr(src, i, 1) + idx = find(U, ch) + if (idx >= 0) + push(out, substr(L, idx, 1)) + else + push(out, ch) + i = i + 1 + return join(out, "") + +// Uppercase transform for ASCII a..z +fun str_to_upper(s) + src = to_string(s) + U = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + L = "abcdefghijklmnopqrstuvwxyz" + out = [] + number i = 0 + number n = len(src) + while i < n + ch = substr(src, i, 1) + idx = find(L, ch) + if (idx >= 0) + push(out, substr(U, idx, 1)) + else + push(out, ch) + i = i + 1 + return join(out, "") + +// Repeat string s 'count' times +fun str_repeat(s, count) + src = to_string(s) + number c = count + if (c <= 0) + return "" + parts = [] + number i = 0 + while i < c + push(parts, src) + i = i + 1 + return join(parts, "") + +// ASCII string to bytes (printable ASCII 0x20..0x7E) +fun string_to_bytes_ascii(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 diff --git a/lib/utils/range.fun b/lib/utils/range.fun new file mode 100644 index 0000000..61c882f --- /dev/null +++ b/lib/utils/range.fun @@ -0,0 +1,48 @@ +/* + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-01 + */ + +// Range utilities (end-exclusive) + +// range(n) -> [0, 1, ..., n-1] +fun range(n) + out = [] + number i = 0 + while i < n + push(out, i) + i = i + 1 + return out + +// range2(start, end) -> [start, start+1, ..., end-1] +fun range2(start, end) + out = [] + number i = start + while i < end + push(out, i) + i = i + 1 + return out + +// range3(start, end, step) with positive or negative step (non-zero) +fun range3(start, end, step) + out = [] + number s = step + if (s == 0) + return out + if (s > 0) + number i = start + while i < end + push(out, i) + i = i + s + else + number j = start + while j > end + push(out, j) + j = j + s + return out diff --git a/src/parser.c b/src/parser.c index d637b0d..a7dfd5e 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1639,9 +1639,58 @@ static int emit_or_expr(Bytecode *bc, const char *src, size_t len, size_t *pos) return 1; } +/* conditional operator (ternary) with right associativity: + Parses: logical_or ('?' conditional ':' conditional)? */ +static int emit_conditional(Bytecode *bc, const char *src, size_t len, size_t *pos) { + /* parse condition (logical OR precedence or higher) */ + if (!emit_or_expr(bc, src, len, pos)) return 0; + + for (;;) { + skip_spaces(src, len, pos); + if (!(*pos < len && src[*pos] == '?')) break; + (*pos)++; /* consume '?' */ + + /* If condition is false -> jump to false arm */ + int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + + /* true arm (right-assoc: allow nested ternaries) */ + skip_spaces(src, len, pos); + if (!emit_conditional(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after '?'"); + return 0; + } + + /* After true arm, unconditionally skip false arm */ + int jmp_end = bytecode_add_instruction(bc, OP_JUMP, 0); + + /* false arm label */ + bytecode_set_operand(bc, jmp_false, bc->instr_count); + + /* require ':' */ + skip_spaces(src, len, pos); + if (!(*pos < len && src[*pos] == ':')) { + parser_fail(*pos, "Expected ':' in conditional expression"); + return 0; + } + (*pos)++; /* consume ':' */ + + /* false arm (right-assoc) */ + skip_spaces(src, len, pos); + if (!emit_conditional(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression after ':'"); + return 0; + } + + /* end label */ + bytecode_set_operand(bc, jmp_end, bc->instr_count); + /* loop to allow chaining like a ? b : c ? d : e (right-assoc) */ + } + return 1; +} + /* top-level expression */ static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *pos) { - return emit_or_expr(bc, src, len, pos); + return emit_conditional(bc, src, len, pos); } /* @@ -3336,12 +3385,41 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, int ci = bytecode_add_constant(bc, make_int(0)); bytecode_add_instruction(bc, OP_LOAD_CONST, ci); } - /* end of condition: ignore any trailing until EOL */ - skip_to_eol(src, len, pos); - /* conditional jump over this clause's block */ + /* Decide between inline single-statement and indented block on next line */ + size_t ppeek = *pos; + /* skip spaces after condition */ + while (ppeek < len && src[ppeek] == ' ') ppeek++; + int inline_stmt = 0; + if (ppeek < len) { + if (src[ppeek] == '\r' || src[ppeek] == '\n') { + inline_stmt = 0; /* EOL -> no inline body */ + } else if (ppeek + 1 < len && src[ppeek] == '/' && src[ppeek + 1] == '/') { + inline_stmt = 0; /* line comment -> no inline body */ + } else if (ppeek + 1 < len && src[ppeek] == '/' && src[ppeek + 1] == '*') { + inline_stmt = 0; /* block comment at EOL -> no inline body */ + } else { + inline_stmt = 1; /* there's code after condition on same line */ + } + } + + /* conditional jump over this clause's inline/body */ int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + if (inline_stmt) { + /* Compile a single inline statement on the same line: + if (cond) */ + *pos = ppeek; + parse_simple_statement(bc, src, len, pos); + /* Skip the inline body when condition is false */ + bytecode_set_operand(bc, jmp_false, bc->instr_count); + /* one-liner form has no else/elseif on the same line; end the chain */ + break; + } + + /* No inline statement on this line: consume up to EOL and parse indented block or else-if/else */ + skip_to_eol(src, len, pos); + /* parse nested block if next line is indented */ int next_indent = 0; size_t look_next = *pos;