diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-20 21:53:23 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-20 21:53:23 +0200 |
| commit | 43614694706ac61b2cebef486b7fa9368c78fe6a (patch) | |
| tree | 06fc10249b5f5e02449d93cf5f52bf80590f1bea | |
| parent | 03b874818315e7dc9fb2ccf26716a0fb65242a57 (diff) | |
Add README.md and array slicing support
| -rw-r--r-- | README.md | 235 | ||||
| -rw-r--r-- | docs/help.txt | 2 | ||||
| -rw-r--r-- | docs/stats.txt | 6 | ||||
| -rw-r--r-- | docs/version.txt | 2 | ||||
| -rw-r--r-- | examples/all-examples.txt | 360 | ||||
| -rw-r--r-- | examples/slices.fy | 33 | ||||
| -rw-r--r-- | examples/uber.fy | 386 | ||||
| -rw-r--r-- | src/build.h | 2 | ||||
| -rw-r--r-- | src/core/functions.c | 10 | ||||
| -rw-r--r-- | src/core/interpret.c | 122 | ||||
| -rw-r--r-- | src/core/interpret.h | 4 | ||||
| -rw-r--r-- | tags | 1 |
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); @@ -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 |
