further opts

This commit is contained in:
Luxferre
2026-06-13 20:11:12 +03:00
parent 201e8ec88d
commit 7134522353
3 changed files with 96 additions and 108 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ If you want to create your own implementation of LTTB, see the [Implementation N
Here are all currently known implementations of LTTB: Here are all currently known implementations of LTTB:
* [Jim Tcl port](./lttb.tcl) (296 SLOC) * [Jim Tcl port](./lttb.tcl) (285 SLOC)
## FAQ ## FAQ
+46 -47
View File
@@ -60,7 +60,6 @@ Accepts the statement string `STMT` as the input line argument.
7. Trim leading/trailing whitespace from `BODY`. 7. Trim leading/trailing whitespace from `BODY`.
8. If `KW` is equal to `GO` and the **third** non-whitespace character of `STMT` is equal to `S`, execute core language routine named `GOS` with `BODY` as a parameter and return. 8. If `KW` is equal to `GO` and the **third** non-whitespace character of `STMT` is equal to `S`, execute core language routine named `GOS` with `BODY` as a parameter and return.
9. Execute core language routine with the name equal to the value of `KW` with `BODY` as a parameter and return. 9. Execute core language routine with the name equal to the value of `KW` with `BODY` as a parameter and return.
## Number normalization function (`NORM`) ## Number normalization function (`NORM`)
Accepts any number-like value `V` as the argument. Accepts any number-like value `V` as the argument.
@@ -68,7 +67,7 @@ Accepts any number-like value `V` as the argument.
1. If `V` is an empty string, return 0 immediately. 1. If `V` is an empty string, return 0 immediately.
2. Convert `V` into an integer (by truncating the decimal part if necessary). 2. Convert `V` into an integer (by truncating the decimal part if necessary).
3. If `V` is inside the allowed range (-32768 to 32767), return `V`. 3. If `V` is inside the allowed range (-32768 to 32767), return `V`.
4. Set `V = V mod 65536`. 4. Set `V = V % 65536`.
5. If `V` exceeds 32767, set `V = V - 65536`. 5. If `V` exceeds 32767, set `V = V - 65536`.
6. Return `V`. 6. Return `V`.
@@ -76,56 +75,56 @@ Accepts any number-like value `V` as the argument.
Simple two-value evaluator. Accepts two stacks, value stack `VALSTACK` and operator stack `OPSTACK`. Simple two-value evaluator. Accepts two stacks, value stack `VALSTACK` and operator stack `OPSTACK`.
1. Set the integer result `RES` to 0. 1. Pop operation `OP` from `OPSTACK`.
2. Pop operation `OP` from `OPSTACK`. 2. Pop operand `V2` from `VALSTACK`.
3. Pop operand `V2` from `VALSTACK`. 3. Pop operand `V1` from `VALSTACK`.
4. Pop operand `V1` from `VALSTACK`. 4. If `OP` is equal to `$`, set `RES` to a random value between 0 and `(V2 + 32768) % 32768`.
5. If `OP` is equal to `$`, set `RES` to a random value between 0 and `(V2 + 32768) % 32768`. 5. If `OP` is equal to `+`, set `RES = V1 + V2`.
6. If `OP` is equal to `+`, set `RES = V1 + V2`. 6. If `OP` is equal to `-`, set `RES = V1 - V2`.
7. If `OP` is equal to `-`, set `RES = V1 - V2`. 7. If `OP` is equal to `*`, set `RES = V1 * V2`.
8. If `OP` is equal to `*`, set `RES = V1 * V2`. 8. If `OP` is equal to `/` and `V2` is not 0, set `RES = V1 / V2`.
9. If `OP` is equal to `/` and `V2` is not 0, set `RES = V1 / V2`. 9. Push the value of `NORM(RES)` to `VALSTACK`.
10. Push the value of `NORM(RES)` to `VALSTACK`. 10. Return `VALSTACK` and `OPSTACK`.
11. Return `VALSTACK` and `OPSTACK`.
## Expression evaluation function (`EXPREVAL`) ## Expression evaluation function (`EXPREVAL`)
Mathematical expression and intrinsic function application engine. Accepts a single `EXPR` string as a parameter. Mathematical expression and intrinsic function application engine. Accepts a single `EXPR` string as a parameter.
1. Trim any leading/trailing whitespace from `EXPR`. 1. Trim leading/trailing whitespace from `EXPR`.
2. If `EXPR` is empty, return 0 as a result. 2. If `EXPR` is empty, return 0.
3. If `EXPR` only consists of digits, return `NORM(EXPR)` as a result. 3. Initialize `VALSTACK` and `OPSTACK` as empty stacks.
4. Substitute all occurrences of the string `RND` with the string `0$` inside `EXPR`. 4. Initialize `VALBUF` and `PREVC` as empty strings.
5. [Phase 1 start] Initialize a `PRE_EXPR` (prepared expression) string buffer with three opening parens `(((`. 5. Set index `I = 0`.
6. Initialize a previous character holder `PREVC` with an empty string. 6. [Main Loop] Fetch character `C = EXPR[I]`. If out of bounds, go to step 16.
7. Fetch the next character `C` in `EXPR`. Go to step 15 if there are no more characters. 7. If `C` is whitespace, increment `I` and go to step 6.
8. If `C` is a digit or `$` character, append it to `PRE_EXPR` and go to step 14. 8. If `C` is a digit:
9. If `C` is a variable name `A`-`Z`, append the value `VARS[C]` to `PRE_EXPR` and go to step 14. - Append `C` to `VALBUF`.
10. If `C` is an opening parenthesis `(`, append `(((` to `PRE_EXPR` and go to step 14. - Set `PREVC = C`, increment `I`, go to step 6.
11. If `C` is a closing parenthesis `)`, append `)))` to `PRE_EXPR` and go to step 14. 9. If `VALBUF is not empty, push NORM(VALBUF) to VALSTACK and clear `VALBUF`.
12. If `C` is either `*` or `/`, append `)`, then the value of `C`, then `(` to `PRE_EXPR` and go to step 14. 10. If EXPR[I onwards] starts with `RND`:
13. If `C` is either `+` or `-`, check for the `PREVC` value. If `PREVC` is either an empty string or belongs to the `( + - * /` set, then append `0` and the value of `C` to `PRE_EXPR`, otherwise append `))`, then the value of `C`, then `((` to `PRE_EXPR`. - Push 0 to `VALSTACK`.
14. Save the current value of `C` into `PREVC` and go to step 7. - Set `C` to `$`.
15. Append three closing parens `)))` to the end of `PRE_EXPR`. - Advance `I` by 3, go to step 14 (process `$` as operator).
16. [Phase 2 start] Initialize a value buffer `VALBUF` as an empty string, and two empty stacks: `VALSTACK` and `OPSTACK`. 11. If `C` is a variable `A`-`Z`:
17. Initialize precedence map `PRECMAP` with string keys and integer values. The map can be represented in JSON as follows: `{"+": 1, "-": 1, "*": 2, "/": 2, "$": 3}`. - Push `NORM(VARS[C])` to `VALSTACK`.
18. Fetch the next non-whitespace character `C` in `PRE_EXPR`. Go to step 33 if there are no more characters. - Set `PREVC = C`, increment `I`, go to step 6.
19. If `C` is a digit, append it to `VALBUF` and go to step 18. 12. If `C` is `(`:
20. If `VALBUF` is not empty, push its value onto `VALSTACK` and clear the `VALBUF` buffer. - Push `(` to OPSTACK.
21. If `C` is an opening parenthesis `(`, push it onto `OPSTACK` and go to step 18. - Set `PREVC = C`, increment `I`, go to step 6.
22. If `C` is a closing parenthesis `)`, go to step 23, otherwise go to step 25. 13. If `C` is `)`:
23. While `OPSTACK` is not empty and the top of `OPSTACK` is not `(`, execute `EVALHELPER(VALSTACK,OPSTACK)`. - While `OPSTACK` top is not `(`: Execute `EVALHELPER(VALSTACK, OPSTACK)`.
24. If the top of `OPSTACK` is `(`, pop it from there. Go to step 18. - Pop `(` from `OPSTACK`.
25. If `C` is one of these `+ - * / $`, go to step 26, otherwise go to step 18. - Set `PREVC = C`, increment `I`, go to step 6.
26. Set the current precedence value `PREC = PRECMAP[C]`. 14. If `C` is an operator (`+ - * / $`):
27. If `OPSTACK` is empty, go to step 32. - If C is `+` or `-` AND (`PREVC` is empty OR belongs to `( + - * / $`):
28. Get the top operation on the `OPSTACK` without popping it. Assign it to `TOP_OP`. - Push 0 to `VALSTACK`.
29. If `TOP_OP` is `(`, go to step 32. - While `OPSTACK` is not empty AND `PRECMAP[OPSTACK top] >= PRECMAP[C]`:
30. If `PRECMAP[TOP_OP] >= PREC`, execute `EVALHELPER(VALSTACK,OPSTACK)`, otherwise go to step 32. - Execute `EVALHELPER(VALSTACK, OPSTACK)`.
31. Go to step 27. - Push `C` to `OPSTACK`.
32. Push `C` into `OPSTACK` and go to step 18. - Set `PREVC = C`, increment `I`, go to step 6.
33. While `OPSTACK` is not empty, execute `EVALHELPER(VALSTACK,OPSTACK)`. 16. [Finalization] If `VALBUF` is not empty, push `NORM(VALBUF)` to `VALSTACK`.
34. Pop the value `RES` from `VALSTACK` and return `NORM(RES)` as the expression result. 17. While `OPSTACK` is not empty: Execute `EVALHELPER(VALSTACK, OPSTACK)`.
18. Return pop from `VALSTACK`.
## Core language routines ## Core language routines
+49 -60
View File
@@ -45,6 +45,7 @@ proc lttb_exec {pline} {
# Integer number normalizer (for TB-valid signed 16-bit range) # Integer number normalizer (for TB-valid signed 16-bit range)
proc lttb_norm {num} { proc lttb_norm {num} {
if {$num eq {}} {return 0}
set num $(int($num)) set num $(int($num))
if {($num > 32767) || ($num < -32768)} { if {($num > 32767) || ($num < -32768)} {
set num $($num % 65536) set num $($num % 65536)
@@ -65,85 +66,73 @@ proc _lpop {list_var} {
return $val return $val
} }
# Tiny BASIC expression evaluator (two-pass) # Simple evaluation helper
proc eval_helper {vsvar osvar} {
upvar $vsvar valstack
upvar $osvar opstack
set res 0
set op [_lpop opstack]
set v2 [_lpop valstack]
set v1 [_lpop valstack]
switch -exact -- $op {
{$} { set res [rand 0 $(($v2 + 32768) % 32768)] }
{+} { set res $($v1 + $v2) }
{-} { set res $($v1 - $v2) }
{*} { set res $($v1 * $v2) }
{/} { if {$v2 != 0} {set res $(int($v1 / $v2))} }
default { error "Unknown operator: $op" }
}
lappend valstack [lttb_norm $res]
}
# Tiny BASIC expression evaluator (single-pass)
proc lttb_eval {lttb_ex} { proc lttb_eval {lttb_ex} {
global vars
# pre-checks # pre-checks
set lttb_ex [string trim $lttb_ex] set lttb_ex [string trim $lttb_ex]
if {$lttb_ex eq {}} {return 0} if {$lttb_ex eq {}} {return 0}
if {[string is digit $lttb_ex]} {return [lttb_norm $(int($lttb_ex))]} if {[string is digit $lttb_ex]} {return [lttb_norm $(int($lttb_ex))]}
# pass 1: variable and parentheses substitution lassign {{( 0 + 1 - 1 * 2 / 2 $ 3} {} {} {} {} 0} precmap valbuf prevc valstack opstack i
global vars set l [string length $lttb_ex]
regsub -all -nocase -- {RND} $lttb_ex {0$} lttb_ex loop i 0 $l {
lassign {{(((} {}} pre_ex prevc set c [string index $lttb_ex $i]
foreach c [split $lttb_ex {}] { if [string is space $c] {continue}
set outc {} if [string is digit $c] {
if {[string is digit $c] || $c eq {$}} {set outc $c}
if {[string is upper $c]} {set outc $vars($c)}
if {$c eq "("} {set outc {(((}}
if {$c eq ")"} {set outc {)))}}
if {$c eq "*" || $c eq "/"} {set outc [string cat {)} $c {(}]}
if {$c eq "+" || $c eq "-"} {
if {$prevc in {{} ( + -}} {
set outc [string cat 0 $c]
} else {set outc [string cat {))} $c {((}] }
}
append pre_ex $outc
set prevc $c
}
append pre_ex {)))}
# pass 2: two-stack LTR evaluator
local proc eval_helper {vsvar osvar} {
upvar $vsvar valstack
upvar $osvar opstack
set res 0
set op [_lpop opstack]
set v2 [_lpop valstack]
set v1 [_lpop valstack]
switch -exact -- $op {
{$} { set res [rand 0 $(($v2 + 32768) % 32768)] }
{+} { set res $($v1 + $v2) }
{-} { set res $($v1 - $v2) }
{*} { set res $($v1 * $v2) }
{/} { if {$v2 != 0} {set res $(int($v1 / $v2))} }
default { error "Unknown operator: $op" }
}
lappend valstack [lttb_norm $res]
}
lassign {{} {} {}} valbuf valstack opstack
set precmap {+ 1 - 1 * 2 / 2 $ 3}
foreach c [split $pre_ex {}] {
if {[string is space $c]} {continue}
if {[string is digit $c]} {
append valbuf $c append valbuf $c
} else { } else {
if {[string length $valbuf] > 0} { if {$valbuf ne {}} {
lappend valstack $(int($valbuf)) lappend valstack [lttb_norm $valbuf]
set valbuf {} set valbuf {}
} }
if {$c eq {(}} { if {[string range $lttb_ex $i $($i+2)] eq {RND}} {
lappend valstack 0
set c {$}
incr i 2
} elseif {[string is upper $c]} {
lappend valstack [lttb_norm $vars($c)]
} elseif {$c eq {(}} {
lappend opstack $c lappend opstack $c
} elseif {$c eq {)}} { } elseif {$c eq {)}} {
while {[llength $opstack] > 0 && [lindex $opstack end] ne "("} { while {[llength $opstack] > 0 && [lindex $opstack end] ne {(}} {
eval_helper valstack opstack eval_helper valstack opstack
} }
if {[lindex $opstack end] eq "("} {_lpop opstack} if {[lindex $opstack end] eq {(}} {_lpop opstack}
} elseif {$c in {+ - * / $}} { }
set prec $precmap($c) if {$c in {+ - * / $}} {
while {[llength $opstack] > 0} { if {$c in {+ -} && $prevc in {{} ( + - * / $}} {
set top_op [lindex $opstack end] lappend valstack 0
if {$top_op eq "("} { break } }
if {$precmap($top_op) >= $prec} { while {[llength $opstack] > 0 && $precmap([lindex $opstack end]) >= $precmap($c) } {
eval_helper valstack opstack eval_helper valstack opstack
} else { break }
} }
lappend opstack $c lappend opstack $c
} }
} }
set prevc $c
} }
if {$valbuf ne {}} {lappend valstack [lttb_norm $valbuf]}
while {[llength $opstack] > 0} {eval_helper valstack opstack} while {[llength $opstack] > 0} {eval_helper valstack opstack}
set res [lindex $valstack 0] return [lttb_norm [lindex $valstack 0]]
if {$res eq {}} {set res 0}
return $res
} }
# Tiny BASIC routines implementing various commands # Tiny BASIC routines implementing various commands