summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-20 21:53:23 +0200
committerPaul Buetow <paul@buetow.org>2026-02-20 21:53:23 +0200
commit43614694706ac61b2cebef486b7fa9368c78fe6a (patch)
tree06fc10249b5f5e02449d93cf5f52bf80590f1bea
parent03b874818315e7dc9fb2ccf26716a0fb65242a57 (diff)
Add README.md and array slicing support
-rw-r--r--README.md235
-rw-r--r--docs/help.txt2
-rw-r--r--docs/stats.txt6
-rw-r--r--docs/version.txt2
-rw-r--r--examples/all-examples.txt360
-rw-r--r--examples/slices.fy33
-rw-r--r--examples/uber.fy386
-rw-r--r--src/build.h2
-rw-r--r--src/core/functions.c10
-rw-r--r--src/core/interpret.c122
-rw-r--r--src/core/interpret.h4
-rw-r--r--tags1
12 files changed, 1145 insertions, 18 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..64b0012
--- /dev/null
+++ b/README.md
@@ -0,0 +1,235 @@
+# Fype
+
+**F**or **Y**our **P**rogram **E**xecution — a lightweight scripting language.
+
+## Synopsis
+
+```sh
+fype script.fy # Run a .fy file
+fype -e "say 1 + 2;" # Run inline code
+fype -h # Show help
+fype -v # Show version
+```
+
+## About
+
+Fype is a single-pass, simultaneous-parse-and-interpret scripting language. There is no AST; the scanner produces a flat token list which the interpreter consumes directly.
+
+Fype is developed under the BSD license.
+
+## Building
+
+```sh
+make # Build the fype binary
+make install # Install to /usr/local/bin
+make clean # Remove build artifacts
+```
+
+## Data Types
+
+Fype uses automatic type conversion between these types:
+
+- **integer** — whole numbers
+- **double** — floating-point numbers
+- **string** — text strings
+
+Explicit conversion: `integer`, `double`, `string`.
+
+## Syntax
+
+### Comments
+
+```fy
+# Single-line comment
+
+#* Block comment *#
+
+#* Multi-line
+ block comment *#
+
+say 1 #* inline *# + 1;
+```
+
+### Variables
+
+```fy
+my foo = 1 + 2; # declare and assign
+my bar = 4, baz = 5; # multiple declarations
+my bay; # defaults to 0
+```
+
+### Arithmetic
+
+```fy
+say 10 + 3; # 13
+say 10 - 3; # 7
+say 10 * 3; # 30
+say 10 / 3; # 3 (integer division)
+say neg 5; # -5
+incr x; # x = x + 1
+decr x; # x = x - 1
+```
+
+### Comparison
+
+```fy
+5 == 5 # equal
+5 != 4 # not equal
+5 < 10 # less than
+5 <= 5 # less or equal
+5 > 3 # greater than
+5 >= 5 # greater or equal
+not 0 # logical not (returns 1)
+```
+
+### Bitwise
+
+```fy
+5 and 3 # bitwise AND
+5 or 2 # bitwise OR
+5 xor 3 # bitwise XOR
+2 :< 2 # left shift (2 << 2 = 8)
+8 :> 2 # right shift (8 >> 2 = 2)
+```
+
+### Conditionals
+
+```fy
+if 1 { say "true"; }
+ifnot 0 { say "false"; }
+```
+
+### Loops
+
+```fy
+while x < 10 { incr x; }
+until x == 10 { incr x; }
+loop {
+ if x == 5 { break; }
+ incr x;
+}
+do { incr x; } while x < 10;
+do { incr x; } until x == 10;
+```
+
+`break` exits the loop; `next` skips to the next iteration.
+
+### Procedures
+
+Procedures share the caller's scope:
+
+```fy
+proc greet {
+ say "Hello";
+ my local_var = 42; # visible to caller
+}
+greet;
+```
+
+### Functions
+
+Functions have local scope and support parameters and return values:
+
+```fy
+# Zero-arg function
+fun answer() { ret 42; }
+say answer();
+
+# With parameters
+fun add(a, b) { ret a + b; }
+say add(3, 5);
+
+# Multiple return values
+fun minmax(a, b) {
+ if a < b { ret a, b; }
+ ret b, a;
+}
+say minmax(3, 7); # prints 3 then 7
+```
+
+### Arrays
+
+```fy
+my nums = [10, 20, 30, 40, 50];
+say len nums; # 5
+say nums[0]; # 10
+say nums[1 + 1]; # 30 (expression index)
+nums[2] = 99; # element assignment
+
+# Slices (half-open ranges)
+my sub = nums[1:4]; # [20, 30, 40]
+my head = nums[:2]; # [10, 20]
+my tail = nums[3:]; # [40, 50]
+my copy = nums[:]; # full shallow copy
+```
+
+### Scoping
+
+```fy
+my outer = 1;
+{
+ my inner = 2;
+ say defined inner; # 1
+}
+say defined inner; # 0
+```
+
+### Synonyms (Aliases)
+
+```fy
+my foo = 42;
+my bar = \foo; # bar is an alias for foo
+foo = 99;
+say bar; # 99 (reflects change)
+say syms foo; # 2 (two symbols point to same value)
+undef foo;
+say defined bar; # 1 (alias keeps value alive)
+```
+
+### Built-in Functions
+
+| Function | Description |
+|----------|-------------|
+| `put x` | Print without newline |
+| `say x` | Print with newline |
+| `ln` | Print newline |
+| `len arr` | Array length |
+| `defined id` | 1 if defined, else 0 |
+| `undef id` | Undefine a symbol |
+| `syms id` | Count synonyms |
+| `assert cond` | Assert condition is true |
+| `exit n` | Exit with code n |
+| `fork` | Fork a subprocess |
+| `gc` | Run garbage collector |
+| `scope` | Print visible symbols |
+
+## Examples
+
+See the `examples/` directory for complete demonstrations:
+
+- `variables.fy` — variable declarations
+- `expressions.fy` — arithmetic expressions
+- `conditionals.fy` — if/ifnot
+- `control.fy` — while/until loops
+- `loop_do.fy` — loop, break, next, do-while, do-until
+- `functions.fy` — functions and nesting
+- `func_args_ret.fy` — function arguments and return values
+- `procedures.fy` — procedures
+- `slices.fy` — array slicing
+- `synonyms.fy` — aliases
+- `scopeing.fy` — lexical scoping
+- `bitwise.fy` — bitwise operators
+- `types.fy` — type conversion
+- `io.fy` — I/O operations
+- `fork.fy` — process forking
+- `comments.fy` — comment syntax
+- `break_next.fy` — loop control
+- `uber.fy` — comprehensive example exercising all features
+
+## Author
+
+Paul C. Buetow (https://buetow.org)
+
+## Website
+
+https://codeberg.org/snonux/fype
diff --git a/docs/help.txt b/docs/help.txt
index 481c057..06b1017 100644
--- a/docs/help.txt
+++ b/docs/help.txt
@@ -1,4 +1,4 @@
-Fype Superalpha Build 9676
+Fype Superalpha Build 9686
(c) Paul C. Buetow (2005 - 2008) <fype@dev.buetow.org>
-e Executes given code string (see synopses)
-h Prints this help
diff --git a/docs/stats.txt b/docs/stats.txt
index 087de31..190942c 100644
--- a/docs/stats.txt
+++ b/docs/stats.txt
@@ -1,6 +1,6 @@
make[1]: Entering directory '/home/paul/git/fype'
===> Num of C source files : 46
-===> Num of C source lines : 8490
-===> Num of Fype source examples : 17
-===> Num of Fype source lines : 464
+===> Num of C source lines : 8602
+===> Num of Fype source examples : 19
+===> Num of Fype source lines : 822
make[1]: Leaving directory '/home/paul/git/fype'
diff --git a/docs/version.txt b/docs/version.txt
index aa5c59c..0087354 100644
--- a/docs/version.txt
+++ b/docs/version.txt
@@ -1 +1 @@
-Fype Superalpha Build 9676
+Fype Superalpha Build 9686
diff --git a/examples/all-examples.txt b/examples/all-examples.txt
index f1a81b8..5349073 100644
--- a/examples/all-examples.txt
+++ b/examples/all-examples.txt
@@ -405,6 +405,40 @@ assert 0 == (say defined bar);
+# Test array index expressions and slice notation
+
+my a = [10, 20, 30, 40, 50];
+
+# Expression-based index
+my i = 2;
+assert 30 == say a[i];
+assert 40 == say a[i + 1];
+
+# Array element assignment
+a[1] = 99;
+assert 99 == say a[1];
+
+# Basic slice (half-open range: end index is exclusive)
+my sub = a[1:4];
+assert 3 == say len sub;
+assert 99 == say sub[0];
+assert 30 == say sub[1];
+assert 40 == say sub[2];
+
+# Slice from start (low bound omitted)
+my head = a[:2];
+assert 2 == say len head;
+assert 10 == say head[0];
+
+# Slice to end (high bound omitted)
+my tail = a[3:];
+assert 2 == say len tail;
+assert 40 == say tail[0];
+
+# Full copy
+my copy = a[:];
+assert 5 == say len copy;
+
#*
* Examples of how to use synonyms
*#
@@ -459,6 +493,332 @@ assert say integer double string put say neg 12; # Nonsense but working :)
#*
+ * Fype uber-example: exercises every language feature.
+ * Every assertion must pass for the script to exit cleanly.
+ *#
+
+# ─── 1. COMMENTS ─────────────────────────────────────────────────────────────
+
+# Single-line comment — ignored completely.
+
+#*
+ * Block comment: also ignored.
+ *#
+
+# Inline block comment inside an expression:
+my commentsok = 1 #* this is ignored *# + 1;
+assert 2 == say commentsok;
+
+# ─── 2. VARIABLES AND BASIC ARITHMETIC ───────────────────────────────────────
+
+# Multiple declarations on one line; bay gets the default value 0
+my x = 10, y = 3, bay;
+assert 0 == say bay;
+assert 13 == say x + y;
+assert 7 == say x - y;
+assert 30 == say x * y;
+assert 3 == say x / y; # integer division truncates toward zero
+assert 10 == say 2 * (4 + 2) - 2;
+assert 10 == say (8 / 2) + 2 * 3;
+
+x = x + 1;
+assert 11 == say x;
+
+# ─── 3. NEGATION AND NOT ──────────────────────────────────────────────────────
+
+assert 5 == say neg neg 5;
+assert 0 == say neg neg 0;
+assert (neg 1) == (say neg not 0); # not 0 => 1, neg 1 => -1
+
+# ─── 4. INCREMENT / DECREMENT ────────────────────────────────────────────────
+
+my counter = 5;
+incr counter;
+assert 6 == say counter;
+decr counter;
+decr counter;
+assert 4 == say counter;
+
+# ─── 5. TYPE CONVERSION ───────────────────────────────────────────────────────
+
+assert 2 == say integer 2.8; # truncates toward zero
+assert 1 == say double 1; # 1.0 compared as 1
+assert 14 == say 1 + string 13; # "13" coerced to integer 13
+assert 5 == say "10 bla" / 2; # leading digits extracted
+
+# ─── 6. STRING ARITHMETIC ────────────────────────────────────────────────────
+
+assert 46 == say "12" + "34"; # "12"->12 + "34"->34 = 46
+assert 1231 == say "1234" - "3"; # 1234 - 3 = 1231
+assert 5 == say "10 bla" / 2;
+
+# ─── 7. COMPARISON OPERATORS ─────────────────────────────────────────────────
+
+assert 1 == (put 5 > 3); assert 0 == (say 5 > 5);
+assert 1 == (put 5 >= 5); assert 0 == (say 4 >= 5);
+assert 1 == (put 3 < 5); assert 0 == (say 5 < 5);
+assert 1 == (put 3 <= 5); assert 0 == (say 6 <= 5);
+assert 1 == (put 5 == 5); assert 0 == (say 4 == 5);
+assert 1 == (put 5 != 4); assert 0 == (say 5 != 5);
+
+# ─── 8. BITWISE OPERATORS ────────────────────────────────────────────────────
+
+assert 1 == say (5 and 3); # 0b101 & 0b011 = 0b001 = 1
+assert 7 == say (5 or 2); # 0b101 | 0b010 = 0b111 = 7
+assert 6 == say (5 xor 3); # 0b101 ^ 0b011 = 0b110 = 6
+assert 8 == say (2 :< 2); # 2 << 2 = 8
+assert 2 == say (8 :> 2); # 8 >> 2 = 2
+assert 0 == say (1 and 0);
+assert 1 == say (0 or 1);
+assert 0 == say (1 xor 1);
+
+# ─── 9. CONDITIONALS ─────────────────────────────────────────────────────────
+
+my flag = 0;
+if 1 { flag = 1; }
+assert 1 == say flag;
+
+ifnot 0 { flag = 2; }
+assert 2 == say flag;
+
+if 0 { flag = 99; } # must not execute
+ifnot 1 { flag = 99; } # must not execute
+assert 2 == say flag;
+
+# ─── 10. WHILE AND UNTIL ─────────────────────────────────────────────────────
+
+my cnt = 0;
+while cnt < 5 { cnt = cnt + 1; }
+assert 5 == say cnt;
+
+cnt = 0;
+until cnt >= 4 { cnt = cnt + 1; }
+assert 4 == say cnt;
+
+# ─── 11. LOOP WITH BREAK AND NEXT ────────────────────────────────────────────
+
+my n = 0;
+loop {
+ n = n + 1;
+ if n == 7 { break; }
+}
+assert 7 == say n;
+
+# Sum 1..10 skipping multiples of 3: 1+2+4+5+7+8+10 = 37
+my total = 0, step = 0;
+loop {
+ step = step + 1;
+ if step > 10 { break; }
+ if step / 3 * 3 == step { next; } # divisible by 3
+ total = total + step;
+}
+assert 37 == say total;
+
+# ─── 12. WHILE WITH BREAK AND NEXT ───────────────────────────────────────────
+
+# Sum 1..10 skipping 5, stopping after 8: 1+2+3+4 + 6+7+8 = 31
+my wsum = 0, wi = 0;
+while wi < 10 {
+ wi = wi + 1;
+ if wi == 5 { next; }
+ if wi > 8 { break; }
+ wsum = wsum + wi;
+}
+assert 31 == say wsum;
+
+# ─── 13. DO-WHILE / DO-UNTIL ─────────────────────────────────────────────────
+
+# Body runs once even though the condition is already false
+my dw = 10;
+do { dw = dw + 1; } while dw < 10;
+assert 11 == say dw;
+
+my du = 0;
+do { du = du + 1; } until du == 5;
+assert 5 == say du;
+
+# ─── 14. PROCEDURES ──────────────────────────────────────────────────────────
+
+# Procedures share the caller's scope; mutations are visible to the caller
+my acc = 0;
+proc bump { acc = acc + 10; }
+bump;
+bump;
+assert 20 == say acc;
+
+# Variables declared inside a procedure leak into the caller's scope
+proc initcoords { my cx = 3; my cy = 7; }
+initcoords;
+assert 3 == say cx;
+assert 7 == say cy;
+
+# ─── 15. FUNCTIONS ───────────────────────────────────────────────────────────
+
+# Zero-arg function with explicit return
+fun answer() { ret 42; }
+assert 42 == say answer();
+
+# Single-arg
+fun square(m) { ret m * m; }
+assert 25 == say square(5);
+assert 0 == say square(0);
+
+# Two-arg
+fun add(a, b) { ret a + b; }
+assert 9 == say add(4, 5);
+
+# Conditional return
+fun absval(v) {
+ if v < 0 { ret neg v; }
+ ret v;
+}
+assert 7 == say absval(7);
+assert 7 == say absval(neg 7);
+
+# Iterative function with local variables and decr
+fun factorial(num) {
+ my result = 1;
+ while num > 1 {
+ result = result * num;
+ decr num;
+ }
+ ret result;
+}
+assert 1 == say factorial(0);
+assert 1 == say factorial(1);
+assert 120 == say factorial(5);
+assert 720 == say factorial(6);
+
+# Iterative fibonacci using local variables
+fun fib(num) {
+ if num <= 1 { ret num; }
+ my fa = 0, fb = 1, ftmp = 0, fi = 2;
+ while fi <= num {
+ ftmp = fa + fb;
+ fa = fb;
+ fb = ftmp;
+ fi = fi + 1;
+ }
+ ret fb;
+}
+assert 0 == say fib(0);
+assert 1 == say fib(1);
+assert 1 == say fib(2);
+assert 8 == say fib(6);
+assert 55 == say fib(10);
+
+# Old-style zero-arg function (no parentheses)
+fun greet { say "hello from greet"; }
+greet;
+
+# Multiple return values both land on the caller's stack (printed here)
+fun minmax(a, b) {
+ if a < b { ret a, b; }
+ ret b, a;
+}
+say minmax(3, 7); # prints 3 then 7
+
+# ─── 16. SCOPING ─────────────────────────────────────────────────────────────
+
+my outer = 100;
+{
+ my inner = 200;
+ outer = outer + inner;
+ assert 1 == defined inner;
+}
+assert 300 == say outer;
+assert 0 == defined inner;
+
+# Variables defined in nested blocks vanish when the block closes
+my depth = 0;
+{
+ depth = depth + 1;
+ {
+ depth = depth + 1;
+ { depth = depth + 1; }
+ assert 3 == say depth;
+ }
+}
+assert 3 == say depth;
+
+# ─── 17. SYNONYMS / ALIASES ──────────────────────────────────────────────────
+
+my orig = 42;
+my alias = \orig;
+assert 42 == say alias; # alias starts with orig's value
+
+orig = 99;
+assert 99 == say alias; # alias reflects the change
+
+assert 2 == syms orig; # two symbols point to the same value
+undef orig;
+assert 1 == syms alias;
+assert 0 == defined orig;
+assert 1 == defined alias;
+undef alias;
+
+# Procedure synonym
+proc grp { say "grp called"; }
+my grp2 = \grp;
+assert 2 == syms grp;
+grp2; # still callable through the alias
+undef grp;
+grp2; # alias keeps it alive
+assert 0 == defined grp;
+undef grp2;
+
+# ─── 18. DEFINED AND UNDEF ───────────────────────────────────────────────────
+
+my tmpvar = 5;
+assert 1 == defined tmpvar;
+assert 0 == defined nosuchvar;
+undef tmpvar;
+assert 0 == defined tmpvar;
+
+# ─── 19. ARRAYS — LITERALS, INDEXING, ASSIGNMENT ────────────────────────────
+
+my nums = [10, 20, 30, 40, 50];
+assert 5 == say len nums;
+assert 10 == say nums[0];
+assert 30 == say nums[2];
+assert 50 == say nums[4];
+
+# Expression-based index
+my idx = 1;
+assert 20 == say nums[idx];
+assert 30 == say nums[idx + 1];
+
+# Element assignment
+nums[2] = 99;
+assert 99 == say nums[2];
+
+# ─── 20. ARRAY SLICES ────────────────────────────────────────────────────────
+
+# nums is now [10, 20, 99, 40, 50]
+my sl = nums[1:4]; # half-open: indices 1, 2, 3
+assert 3 == say len sl;
+assert 20 == say sl[0];
+assert 99 == say sl[1];
+assert 40 == say sl[2];
+
+my head = nums[:2]; # first two elements
+assert 2 == say len head;
+assert 10 == say head[0];
+assert 20 == say head[1];
+
+my tail = nums[3:]; # from index 3 to end
+assert 2 == say len tail;
+assert 40 == say tail[0];
+assert 50 == say tail[1];
+
+my cpy = nums[:]; # full shallow copy
+assert 5 == say len cpy;
+assert 10 == say cpy[0];
+assert 50 == say cpy[4];
+
+say "All assertions passed.";
+
+#*
* Examples of how to define variables
*#
diff --git a/examples/slices.fy b/examples/slices.fy
new file mode 100644
index 0000000..942f4bc
--- /dev/null
+++ b/examples/slices.fy
@@ -0,0 +1,33 @@
+# Test array index expressions and slice notation
+
+my a = [10, 20, 30, 40, 50];
+
+# Expression-based index
+my i = 2;
+assert 30 == say a[i];
+assert 40 == say a[i + 1];
+
+# Array element assignment
+a[1] = 99;
+assert 99 == say a[1];
+
+# Basic slice (half-open range: end index is exclusive)
+my sub = a[1:4];
+assert 3 == say len sub;
+assert 99 == say sub[0];
+assert 30 == say sub[1];
+assert 40 == say sub[2];
+
+# Slice from start (low bound omitted)
+my head = a[:2];
+assert 2 == say len head;
+assert 10 == say head[0];
+
+# Slice to end (high bound omitted)
+my tail = a[3:];
+assert 2 == say len tail;
+assert 40 == say tail[0];
+
+# Full copy
+my copy = a[:];
+assert 5 == say len copy;
diff --git a/examples/uber.fy b/examples/uber.fy
new file mode 100644
index 0000000..7c3ccdc
--- /dev/null
+++ b/examples/uber.fy
@@ -0,0 +1,386 @@
+#*
+ * Student Score Report — Fype uber-example
+ *
+ * Analyses a class of ten exam scores (0-100) and produces a brief
+ * statistical summary. Every Fype language feature is exercised in
+ * service of the analysis; all assertions must pass for the script
+ * to exit cleanly.
+ *#
+
+# ─── DATASET ───────────────────────────────────────────────────────────────
+# Ten student exam scores. We will compute sum, average, band counts, etc.
+my scores = [72, 45, 89, 91, 55, 78, 63, 100, 48, 82];
+my N = 10; # number of students
+my PASS = 60; # minimum score for a pass
+my DIST = 80; # minimum score for a distinction
+
+# ─── 1. COMMENTS ───────────────────────────────────────────────────────────
+
+# Single-line comment — ignored.
+#* Block comment — also ignored. *#
+
+# Inline block comment inside an expression — the #* *# part is discarded:
+my ok = 1 #* invisible *# + 0;
+assert 1 == ok;
+
+# ─── 2. BASIC ARITHMETIC AND VARIABLES ─────────────────────────────────────
+
+# Multiple declarations on one line; band defaults to 0.
+my a = 10, b = 3, band;
+assert 0 == say band;
+assert 13 == say a + b;
+assert 7 == say a - b;
+assert 30 == say a * b;
+assert 3 == say a / b; # integer division truncates toward zero
+assert 10 == say 2 * (4 + 2) - 2;
+assert 10 == say (8 / 2) + 2 * 3;
+
+# Assignment is also an expression that returns the new value.
+assert 15 == (a = 15);
+assert 15 == say a;
+
+# ─── 3. NEGATION AND NOT ───────────────────────────────────────────────────
+
+assert 5 == say neg neg 5;
+assert 0 == say neg neg 0;
+assert 1 == say not 0;
+assert 0 == say not 1;
+
+# Temporarily penalise the first score by 5, then restore it.
+my s0 = scores[0]; # 72
+s0 = s0 - 5;
+assert 67 == say s0;
+s0 = s0 + 5;
+assert 72 == say s0;
+
+# ─── 4. INCREMENT / DECREMENT ──────────────────────────────────────────────
+
+# Used as statements:
+my tally = 0;
+incr tally; incr tally; incr tally;
+assert 3 == say tally;
+decr tally;
+assert 2 == say tally;
+
+# Used as expressions (return the new value):
+my step = 0;
+assert 1 == say incr step;
+assert 0 == say decr step;
+
+# ─── 5. TYPE CONVERSION ────────────────────────────────────────────────────
+
+assert 7 == say integer 7.9; # truncates toward zero
+assert 80 == say double 80; # 80.0 compares equal to 80
+assert 14 == say 1 + string 13; # "13" coerced to integer 13
+assert 5 == say "10 pts" / 2; # leading digits extracted first
+
+# ─── 6. STRING ARITHMETIC ──────────────────────────────────────────────────
+
+assert 46 == say "12" + "34"; # "12"->12 + "34"->34 = 46
+assert 1231 == say "1234" - "3"; # 1234 - 3 = 1231
+
+# ─── 7. COMPARISON OPERATORS ───────────────────────────────────────────────
+
+assert 1 == (put scores[0] > PASS); ln; # 72 > 60 => 1
+assert 0 == (put scores[1] > PASS); ln; # 45 > 60 => 0
+assert 1 == (put scores[3] >= DIST); ln; # 91 >= 80 => 1
+assert 1 == (put PASS != DIST); ln;
+assert 0 == (say PASS == DIST);
+assert 1 == (put 3 < 5); assert 0 == (say 5 < 5); ln;
+assert 1 == (put 5 <= 5); assert 0 == (say 6 <= 5); ln;
+
+# ─── 8. BITWISE GRADE FLAGS ────────────────────────────────────────────────
+# Encode quality: bit 0 = passed (>=PASS), bit 1 = distinction (>=DIST).
+
+# Score 91 => passed AND distinction => 0b11 = 3
+my g91 = 0;
+if scores[3] >= PASS { g91 = g91 or 1; }
+if scores[3] >= DIST { g91 = g91 or 2; }
+assert 3 == say g91;
+
+# Score 45 => failed => 0b00 = 0
+my g45 = 0;
+if scores[1] >= PASS { g45 = g45 or 1; }
+assert 0 == say g45;
+
+assert 1 == say (g91 and 1); # extract pass bit from g91
+assert 0 == say (g45 and 1); # extract pass bit from g45
+assert 6 == say (5 xor 3); # 0b101 ^ 0b011 = 0b110 = 6
+assert 4 == say (1 :< 2); # 1 << 2 = 4
+assert 2 == say (8 :> 2); # 8 >> 2 = 2
+
+# ─── 9. CONDITIONALS ───────────────────────────────────────────────────────
+
+# 91 is a distinction
+if scores[3] >= DIST { band = 2; }
+assert 2 == say band;
+
+# 45 is a fail
+ifnot scores[1] >= PASS { band = 0; }
+assert 0 == say band;
+
+if 0 { band = 99; } # must not execute
+ifnot 1 { band = 99; } # must not execute
+assert 0 == say band;
+
+# ─── 10. WHILE + UNTIL: SUM ALL SCORES ─────────────────────────────────────
+# 72+45+89+91+55+78+63+100+48+82 = 723
+
+my total = 0, wi = 0;
+while wi < N {
+ total = total + scores[wi];
+ incr wi;
+}
+assert 723 == say total;
+
+my total2 = 0, ui = 0;
+until ui >= N {
+ total2 = total2 + scores[ui];
+ incr ui;
+}
+assert 723 == say total2;
+
+# ─── 11. LOOP + BREAK + NEXT: COUNT PASSING SCORES ─────────────────────────
+# Scores >= 60: 72,89,91,78,63,100,82 => 7 students
+
+my passing = 0, li = 0;
+loop {
+ if li >= N { break; }
+ if scores[li] < PASS { li = li + 1; next; }
+ incr passing;
+ incr li;
+}
+assert 7 == say passing;
+
+# ─── 12. WHILE + BREAK + NEXT: COUNT DISTINCTIONS ──────────────────────────
+# Scores >= 80: 89,91,100,82 => 4 students
+
+my dists = 0, di = 0;
+while di < N {
+ incr di;
+ if scores[di - 1] < DIST { next; }
+ incr dists;
+}
+assert 4 == say dists;
+
+# ─── 13. DO-WHILE / DO-UNTIL ───────────────────────────────────────────────
+
+# Body runs once even though the condition is already false.
+my dw = 10;
+do { dw = dw + 1; } while dw < 10;
+assert 11 == say dw;
+
+# Accumulate scores until the running total exceeds 500 (runs at least once).
+# After 8 iterations: 72+45+89+91+55+78+63+100 = 593.
+my rsum = 0, ri = 0;
+do {
+ rsum = rsum + scores[ri];
+ incr ri;
+} while rsum < 500;
+assert 593 == say rsum;
+assert 8 == say ri;
+
+# do-until: count down from 3 to 0.
+my cd = 3;
+do { decr cd; } until cd == 0;
+assert 0 == say cd;
+
+# ─── 14. FUNCTIONS ─────────────────────────────────────────────────────────
+
+# Sum the first n elements of an array.
+fun arr_sum(arr, n) {
+ my s = 0, i = 0;
+ while i < n {
+ s = s + arr[i];
+ incr i;
+ }
+ ret s;
+}
+assert 723 == say arr_sum(scores, N);
+
+# Integer average (floor division).
+fun avg(arr, n) {
+ ret arr_sum(arr, n) / n;
+}
+assert 72 == say avg(scores, N);
+
+# Conditional return: grade band (0=fail, 1=pass, 2=distinction).
+fun grade_band(score) {
+ if score >= DIST { ret 2; }
+ if score >= PASS { ret 1; }
+ ret 0;
+}
+assert 2 == say grade_band(91);
+assert 1 == say grade_band(72);
+assert 0 == say grade_band(45);
+
+# Multiple return values: minimum and maximum of an array.
+fun arr_minmax(arr, n) {
+ my mn = arr[0], mx = arr[0], i = 1;
+ while i < n {
+ if arr[i] < mn { mn = arr[i]; }
+ if arr[i] > mx { mx = arr[i]; }
+ incr i;
+ }
+ ret mn, mx; # prints 45 then 100
+}
+say arr_minmax(scores, N);
+
+# Nested function: range check (returns 1 if 0 <= score <= 100).
+fun valid_score(score) {
+ fun in_range(v) {
+ if v >= 0 { if v <= 100 { ret 1; } }
+ ret 0;
+ }
+ ret in_range(score);
+}
+assert 1 == say valid_score(72);
+assert 0 == say valid_score(neg 1);
+assert 0 == defined in_range; # nested function gone after the call
+
+# Old-style zero-arg function (no parentheses).
+fun banner { say "=== Student Score Report ==="; }
+banner;
+
+# Self-undefining one-shot function.
+fun init_once { say "Initialising report..."; undef init_once; }
+init_once;
+assert 0 == defined init_once;
+
+# ─── 15. PROCEDURES ────────────────────────────────────────────────────────
+# Procedures share the caller's scope; mutations are visible to the caller.
+
+my accum = total; # start from the already-computed sum
+proc apply_bonus { accum = accum + 10; }
+apply_bonus; apply_bonus;
+assert 743 == say accum; # 723 + 20
+
+# Variables declared inside a procedure leak into the caller's scope.
+proc make_summary {
+ my summary_pass = passing;
+ my summary_dist = dists;
+}
+make_summary;
+assert 7 == say summary_pass;
+assert 4 == say summary_dist;
+
+# ─── 16. SCOPING ───────────────────────────────────────────────────────────
+
+my outer = 100;
+{
+ my inner = 50;
+ outer = outer + inner;
+ assert 1 == defined inner;
+ scope; # prints all symbols visible here
+}
+assert 150 == say outer;
+assert 0 == defined inner; # inner vanished when the block closed
+
+# Deep nesting: mutate a counter from three levels deep.
+my depth = 0;
+{
+ incr depth;
+ { incr depth; { incr depth; } }
+}
+assert 3 == say depth;
+
+# ─── 17. SYNONYMS / ALIASES ────────────────────────────────────────────────
+
+my best = scores[3]; # 91
+my top = \best; # top is a synonym for best
+assert 91 == say top;
+
+best = 95; # update through the original name
+assert 95 == say top; # synonym reflects the change
+
+assert 2 == syms best;
+undef best;
+assert 1 == syms top;
+assert 0 == defined best;
+assert 1 == defined top;
+undef top;
+
+# Procedure synonym keeps the procedure alive after the original is undefined.
+proc show_band { put "band="; say grade_band(91); }
+my show_band2 = \show_band;
+assert 2 == syms show_band;
+show_band2;
+undef show_band;
+show_band2; # alias still callable
+assert 0 == defined show_band;
+undef show_band2;
+
+# ─── 18. DEFINED / UNDEF ───────────────────────────────────────────────────
+
+my tmp = 42;
+assert 1 == defined tmp;
+assert 0 == defined nosuchvar;
+undef tmp;
+assert 0 == defined tmp;
+
+# ─── 19. ARRAYS — LITERALS, INDEXING, ASSIGNMENT ────────────────────────────
+
+my extra = [5, 10, 15, 20, 25];
+assert 5 == say len extra;
+assert 5 == say extra[0];
+assert 15 == say extra[2];
+
+# Element assignment.
+extra[2] = 99;
+assert 99 == say extra[2];
+
+# Expression-based index.
+my ei = 1;
+assert 10 == say extra[ei];
+assert 99 == say extra[ei + 1];
+
+# ─── 20. ARRAY SLICES ──────────────────────────────────────────────────────
+# scores is [72, 45, 89, 91, 55, 78, 63, 100, 48, 82]
+
+# Half-open slice: indices 2, 3, 4 => [89, 91, 55]
+my mid = scores[2:5];
+assert 3 == say len mid;
+assert 89 == say mid[0];
+assert 55 == say mid[2];
+
+# Slice from start (low bound omitted): first three => [72, 45, 89]
+my first3 = scores[:3];
+assert 3 == say len first3;
+assert 72 == say first3[0];
+assert 89 == say first3[2];
+
+# Slice to end (high bound omitted): from index 7 => [100, 48, 82]
+my tail = scores[7:];
+assert 3 == say len tail;
+assert 100 == say tail[0];
+assert 82 == say tail[2];
+
+# Full shallow copy.
+my copy = scores[:];
+assert 10 == say len copy;
+assert 72 == say copy[0];
+assert 82 == say copy[9];
+
+# ─── 21. I/O ───────────────────────────────────────────────────────────────
+
+put "Total: "; put total; ln;
+put "Average: "; put avg(scores, N); ln;
+put "Passing: "; put passing; ln;
+put "Distincts: "; say dists;
+
+# ─── 22. FORK ──────────────────────────────────────────────────────────────
+# Child process computes the same sum independently; parent waits.
+
+my pid = fork;
+
+if pid {
+ put "Parent: child pid = "; say pid;
+}
+
+ifnot pid {
+ put "Child: score sum = "; say arr_sum(scores, N);
+ exit 0;
+}
+
+# ─── DONE ──────────────────────────────────────────────────────────────────
+say "All assertions passed.";
diff --git a/src/build.h b/src/build.h
index 5029024..161c8f2 100644
--- a/src/build.h
+++ b/src/build.h
@@ -36,7 +36,7 @@
#ifndef BUILD_H
#define BUILD_H
-#define BUILDNR 9682
+#define BUILDNR 9689
#define OS_LINUX
#endif
diff --git a/src/core/functions.c b/src/core/functions.c
index 76b4eed..a448f84 100644
--- a/src/core/functions.c
+++ b/src/core/functions.c
@@ -148,6 +148,16 @@ _process(Interpret *p_interpret, Token *p_token_store, Token *p_token_op,
switch (tt_op) {
case TT_ASSIGN:
{
+ /* Array element assignment: arr[i] = val */
+ if (p_interpret->p_token_array_lhs != NULL) {
+ array_set(p_interpret->p_token_array_lhs->p_array,
+ p_interpret->i_array_lhs_index,
+ p_token_store);
+ p_interpret->p_token_array_lhs = NULL;
+ return;
+ }
+
+ /* Regular variable assignment */
Token *p_token_assign = p_interpret->p_token_temp;
TokenType tt_assign = token_get_tt(p_token_assign);
diff --git a/src/core/interpret.c b/src/core/interpret.c
index 0911ef7..f61aa5c 100644
--- a/src/core/interpret.c
+++ b/src/core/interpret.c
@@ -74,6 +74,8 @@ int _program(Interpret *p_interpret);
int _statement(Interpret *p_interpret);
int _sum(Interpret *p_interpret);
int _term(Interpret *p_interpret);
+int _term_array_access(Interpret *p_interpret,
+ Token *p_token_array, Array *p_array);
int _var_assign(Interpret *p_interpret);
int _var_decl(Interpret *p_interpret);
int _var_list(Interpret *p_interpret);
@@ -99,6 +101,8 @@ interpret_new(List *p_list_token, Hash *p_hash_syms) {
p_interpret->tt_prev = TT_NONE;
p_interpret->p_token_prev = NULL;
p_interpret->p_token_temp = NULL;
+ p_interpret->p_token_array_lhs = NULL;
+ p_interpret->i_array_lhs_index = 0;
p_interpret->ct = CONTROL_NONE;
return (p_interpret);
@@ -860,6 +864,13 @@ _sum(Interpret *p_interpret) {
switch (p_interpret->tt) {
case TT_DDOT:
+ /* ':' is a shift-operator prefix only when followed by
+ * '<' or '>'; otherwise it is a slice separator so stop.
+ */
+ if (_NEXT_TT != TT_LT && _NEXT_TT != TT_GT) {
+ b_flag = false;
+ break;
+ }
p_token_tmp = p_interpret->p_token;
_NEXT
case TT_ADD:
@@ -941,6 +952,9 @@ int
_product2(Interpret *p_interpret) {
_CHECK TRACK
+ /* Clear any stale array LHS left from a preceding expression */
+ p_interpret->p_token_array_lhs = NULL;
+
if (_term(p_interpret)) {
_Bool b_flag = true;
@@ -949,11 +963,17 @@ _product2(Interpret *p_interpret) {
&& IS_NOT_OPERATOR(_NEXT_TT)) {
Token *p_token = p_interpret->p_token;
Token *p_token_temp = p_interpret->p_token_prev;
+ /* Save array LHS set by _term; RHS evaluation clears it */
+ Token *p_lhs = p_interpret->p_token_array_lhs;
+ int i_lhs_idx = p_interpret->i_array_lhs_index;
_NEXT
if (!_expression_(p_interpret))
_INTERPRET_ERROR("Expected expression", p_token);
+ /* Restore array LHS so function_process can assign it */
+ p_interpret->p_token_array_lhs = p_lhs;
+ p_interpret->i_array_lhs_index = i_lhs_idx;
p_interpret->p_token_temp = p_token_temp;
function_process(p_interpret, p_token, NULL,
p_interpret->p_stack, 2);
@@ -971,6 +991,86 @@ _product2(Interpret *p_interpret) {
return (0);
}
+/* Evaluate an array index or slice expression and push the result.
+ *
+ * On entry the current token is the array identifier. The function
+ * advances past `arr[...]` and pushes the result onto the stack.
+ *
+ * Supported forms (half-open range, end is exclusive):
+ * arr[i] single element at index i
+ * arr[i:j] sub-array of elements [i, j)
+ * arr[i:] elements from i to end
+ * arr[:j] elements from start up to j
+ * arr[:] full copy
+ *
+ * For single-element access, sets p_token_array_lhs / i_array_lhs_index
+ * so that _product2 can dispatch arr[i] = val to the right handler.
+ */
+int
+_term_array_access(Interpret *p_interpret,
+ Token *p_token_array, Array *p_array) {
+ _NEXT /* advance past identifier, now at '[' */
+ _NEXT /* advance past '[', now at index start */
+
+ /* Evaluate the low bound. _expression_ stops before ':' because
+ * _sum now only treats ':' as a shift prefix when the next token
+ * is '<' or '>'. Returns 0 and pushes nothing for arr[:j]. */
+ int i_low = 0;
+ if (_expression_(p_interpret))
+ i_low = convert_to_integer_get(
+ stack_pop(p_interpret->p_stack));
+
+ /* Skip the auto-semicolon the scanner inserts before ']' */
+ if (p_interpret->tt == TT_SEMICOLON)
+ _NEXT
+
+ if (p_interpret->tt == TT_DDOT) { /* slice: arr[i:j] etc. */
+ _NEXT /* advance past ':' */
+
+ /* Default: slice extends to the end of the array */
+ int i_high = array_get_used(p_array);
+
+ /* Evaluate explicit high bound if not immediately at ']' */
+ if (p_interpret->tt != TT_PARANT_AR
+ && p_interpret->tt != TT_SEMICOLON) {
+ if (!_expression_(p_interpret))
+ _INTERPRET_ERROR("Expected high-bound expression",
+ p_interpret->p_token);
+ i_high = convert_to_integer_get(
+ stack_pop(p_interpret->p_stack));
+ }
+
+ /* Skip auto-semicolon before ']' */
+ if (p_interpret->tt == TT_SEMICOLON)
+ _NEXT
+
+ /* Build sub-array [i_low, i_high) — shallow copy of pointers.
+ * Elements are shared with the original array; do not ref-up
+ * because the GC tracks all tokens at ref_count 0. */
+ int i_len = i_high - i_low;
+ Token *p_sub = token_new_array(i_len > 0 ? i_len : 1);
+ for (int i = i_low; i < i_high; ++i) {
+ Token *p_elem = array_get(p_array, i);
+ if (p_elem != NULL)
+ array_unshift(p_sub->p_array, p_elem);
+ }
+ stack_push(p_interpret->p_stack, p_sub);
+
+ } else { /* single index: arr[i] */
+ /* Record LHS info so _product2 can handle arr[i] = val */
+ p_interpret->p_token_array_lhs = p_token_array;
+ p_interpret->i_array_lhs_index = i_low;
+ stack_push(p_interpret->p_stack, array_get(p_array, i_low));
+ }
+
+ /* Consume ']' */
+ if (p_interpret->tt != TT_PARANT_AR)
+ _INTERPRET_ERROR("Expected ']'", p_interpret->p_token);
+ _NEXT /* advance past ']' */
+
+ return (1);
+}
+
int
_term(Interpret *p_interpret) {
_CHECK TRACK
@@ -993,24 +1093,22 @@ _term(Interpret *p_interpret) {
{
if (_NEXT_TT != TT_ASSIGN) {
if (_NEXT_TT == TT_PARANT_AL) {
- Token *p_token_var = p_interpret->p_token;
- char *c_name = token_get_val(p_token_var);
- Symbol *p_symbol = scope_get(p_interpret->p_scope, c_name);
+ /* Delegate to the array-access / slice helper */
+ char *c_name = token_get_val(p_interpret->p_token);
+ Symbol *p_symbol = scope_get(
+ p_interpret->p_scope, c_name);
if (p_symbol == NULL)
- _INTERPRET_ERROR("No such symbol", p_token_var);
+ _INTERPRET_ERROR("No such symbol",
+ p_interpret->p_token);
Token *p_token_array = symbol_get_val(p_symbol);
Array *p_array = TOKEN_GET_ARRAY(p_token_array);
if (p_array == NULL)
- _INTERPRET_ERROR("Expected an array", p_interpret->p_token);
-
- _NEXT2
- Token *p_token_val = array_get(p_array,
- convert_to_integer_get(p_interpret->p_token));
- stack_push(p_interpret->p_stack, p_token_val);
- _NEXT
+ _INTERPRET_ERROR("Expected an array",
+ p_interpret->p_token);
- return (1);
+ return (_term_array_access(p_interpret,
+ p_token_array, p_array));
} else if (function_is_buildin(p_interpret->p_token)) {
Token *p_token = p_interpret->p_token;
diff --git a/src/core/interpret.h b/src/core/interpret.h
index 972c49d..7c2eba8 100644
--- a/src/core/interpret.h
+++ b/src/core/interpret.h
@@ -66,6 +66,10 @@ typedef struct {
Token *p_token_prev;
TokenType tt_prev;
Token *p_token_temp;
+ /* LHS array token set by _term_array_access for arr[i] = val */
+ Token *p_token_array_lhs;
+ /* Array index set alongside p_token_array_lhs */
+ int i_array_lhs_index;
} Interpret;
Interpret* interpret_new(List *p_list_token, Hash *p_hash_syms);
diff --git a/tags b/tags
index d29941d..b7644a0 100644
--- a/tags
+++ b/tags
@@ -84,6 +84,7 @@ _scope_print_cb ./src/core/scope.c /^_scope_print_cb(void *p_void, int i_level)
_statement ./src/core/interpret.c /^_statement(Interpret *p_interpret) {$/;" f typeref:typename:int
_sum ./src/core/interpret.c /^_sum(Interpret *p_interpret) {$/;" f typeref:typename:int
_term ./src/core/interpret.c /^_term(Interpret *p_interpret) {$/;" f typeref:typename:int
+_term_array_access ./src/core/interpret.c /^_term_array_access(Interpret *p_interpret,$/;" f typeref:typename:int
_tree_print ./src/data/tree.c /^_tree_print(TreeNode *p_treenode, int i_indent) {$/;" f typeref:typename:void
_tree_print_cb ./src/data/tree.c /^_tree_print_cb(void *p_void, void *p_indent) {$/;" f typeref:typename:void
_tree_print_cb2 ./src/data/tree.c /^_tree_print_cb2(void *p_void, void *p_indent) {$/;" f typeref:typename:void