further opts
This commit is contained in:
@@ -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:
|
||||
|
||||
* [Jim Tcl port](./lttb.tcl) (296 SLOC)
|
||||
* [Jim Tcl port](./lttb.tcl) (285 SLOC)
|
||||
|
||||
## FAQ
|
||||
|
||||
|
||||
+46
-47
@@ -60,7 +60,6 @@ Accepts the statement string `STMT` as the input line argument.
|
||||
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.
|
||||
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`)
|
||||
|
||||
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.
|
||||
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`.
|
||||
4. Set `V = V mod 65536`.
|
||||
4. Set `V = V % 65536`.
|
||||
5. If `V` exceeds 32767, set `V = V - 65536`.
|
||||
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`.
|
||||
|
||||
1. Set the integer result `RES` to 0.
|
||||
2. Pop operation `OP` from `OPSTACK`.
|
||||
3. Pop operand `V2` from `VALSTACK`.
|
||||
4. Pop operand `V1` from `VALSTACK`.
|
||||
5. If `OP` is equal to `$`, set `RES` to a random value between 0 and `(V2 + 32768) % 32768`.
|
||||
6. 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`.
|
||||
9. If `OP` is equal to `/` and `V2` is not 0, set `RES = V1 / V2`.
|
||||
10. Push the value of `NORM(RES)` to `VALSTACK`.
|
||||
11. Return `VALSTACK` and `OPSTACK`.
|
||||
1. Pop operation `OP` from `OPSTACK`.
|
||||
2. Pop operand `V2` from `VALSTACK`.
|
||||
3. 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 = V1 + V2`.
|
||||
6. 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 `/` and `V2` is not 0, set `RES = V1 / V2`.
|
||||
9. Push the value of `NORM(RES)` to `VALSTACK`.
|
||||
10. Return `VALSTACK` and `OPSTACK`.
|
||||
|
||||
## Expression evaluation function (`EXPREVAL`)
|
||||
|
||||
Mathematical expression and intrinsic function application engine. Accepts a single `EXPR` string as a parameter.
|
||||
|
||||
1. Trim any leading/trailing whitespace from `EXPR`.
|
||||
2. If `EXPR` is empty, return 0 as a result.
|
||||
3. If `EXPR` only consists of digits, return `NORM(EXPR)` as a result.
|
||||
4. Substitute all occurrences of the string `RND` with the string `0$` inside `EXPR`.
|
||||
5. [Phase 1 start] Initialize a `PRE_EXPR` (prepared expression) string buffer with three opening parens `(((`.
|
||||
6. Initialize a previous character holder `PREVC` with an empty string.
|
||||
7. Fetch the next character `C` in `EXPR`. Go to step 15 if there are no more characters.
|
||||
8. If `C` is a digit or `$` character, append it to `PRE_EXPR` and go to step 14.
|
||||
9. If `C` is a variable name `A`-`Z`, append the value `VARS[C]` to `PRE_EXPR` and go to step 14.
|
||||
10. If `C` is an opening parenthesis `(`, append `(((` to `PRE_EXPR` and go to step 14.
|
||||
11. If `C` is a closing parenthesis `)`, append `)))` to `PRE_EXPR` and go to step 14.
|
||||
12. If `C` is either `*` or `/`, append `)`, then the value of `C`, then `(` to `PRE_EXPR` and go to step 14.
|
||||
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`.
|
||||
14. Save the current value of `C` into `PREVC` and go to step 7.
|
||||
15. Append three closing parens `)))` to the end of `PRE_EXPR`.
|
||||
16. [Phase 2 start] Initialize a value buffer `VALBUF` as an empty string, and two empty stacks: `VALSTACK` and `OPSTACK`.
|
||||
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}`.
|
||||
18. Fetch the next non-whitespace character `C` in `PRE_EXPR`. Go to step 33 if there are no more characters.
|
||||
19. If `C` is a digit, append it to `VALBUF` and go to step 18.
|
||||
20. If `VALBUF` is not empty, push its value onto `VALSTACK` and clear the `VALBUF` buffer.
|
||||
21. If `C` is an opening parenthesis `(`, push it onto `OPSTACK` and go to step 18.
|
||||
22. If `C` is a closing parenthesis `)`, go to step 23, otherwise go to step 25.
|
||||
23. While `OPSTACK` is not empty and the top of `OPSTACK` is not `(`, execute `EVALHELPER(VALSTACK,OPSTACK)`.
|
||||
24. If the top of `OPSTACK` is `(`, pop it from there. Go to step 18.
|
||||
25. If `C` is one of these `+ - * / $`, go to step 26, otherwise go to step 18.
|
||||
26. Set the current precedence value `PREC = PRECMAP[C]`.
|
||||
27. If `OPSTACK` is empty, go to step 32.
|
||||
28. Get the top operation on the `OPSTACK` without popping it. Assign it to `TOP_OP`.
|
||||
29. If `TOP_OP` is `(`, go to step 32.
|
||||
30. If `PRECMAP[TOP_OP] >= PREC`, execute `EVALHELPER(VALSTACK,OPSTACK)`, otherwise go to step 32.
|
||||
31. Go to step 27.
|
||||
32. Push `C` into `OPSTACK` and go to step 18.
|
||||
33. While `OPSTACK` is not empty, execute `EVALHELPER(VALSTACK,OPSTACK)`.
|
||||
34. Pop the value `RES` from `VALSTACK` and return `NORM(RES)` as the expression result.
|
||||
1. Trim leading/trailing whitespace from `EXPR`.
|
||||
2. If `EXPR` is empty, return 0.
|
||||
3. Initialize `VALSTACK` and `OPSTACK` as empty stacks.
|
||||
4. Initialize `VALBUF` and `PREVC` as empty strings.
|
||||
5. Set index `I = 0`.
|
||||
6. [Main Loop] Fetch character `C = EXPR[I]`. If out of bounds, go to step 16.
|
||||
7. If `C` is whitespace, increment `I` and go to step 6.
|
||||
8. If `C` is a digit:
|
||||
- Append `C` to `VALBUF`.
|
||||
- Set `PREVC = C`, increment `I`, go to step 6.
|
||||
9. If `VALBUF is not empty, push NORM(VALBUF) to VALSTACK and clear `VALBUF`.
|
||||
10. If EXPR[I onwards] starts with `RND`:
|
||||
- Push 0 to `VALSTACK`.
|
||||
- Set `C` to `$`.
|
||||
- Advance `I` by 3, go to step 14 (process `$` as operator).
|
||||
11. If `C` is a variable `A`-`Z`:
|
||||
- Push `NORM(VARS[C])` to `VALSTACK`.
|
||||
- Set `PREVC = C`, increment `I`, go to step 6.
|
||||
12. If `C` is `(`:
|
||||
- Push `(` to OPSTACK.
|
||||
- Set `PREVC = C`, increment `I`, go to step 6.
|
||||
13. If `C` is `)`:
|
||||
- While `OPSTACK` top is not `(`: Execute `EVALHELPER(VALSTACK, OPSTACK)`.
|
||||
- Pop `(` from `OPSTACK`.
|
||||
- Set `PREVC = C`, increment `I`, go to step 6.
|
||||
14. If `C` is an operator (`+ - * / $`):
|
||||
- If C is `+` or `-` AND (`PREVC` is empty OR belongs to `( + - * / $`):
|
||||
- Push 0 to `VALSTACK`.
|
||||
- While `OPSTACK` is not empty AND `PRECMAP[OPSTACK top] >= PRECMAP[C]`:
|
||||
- Execute `EVALHELPER(VALSTACK, OPSTACK)`.
|
||||
- Push `C` to `OPSTACK`.
|
||||
- Set `PREVC = C`, increment `I`, go to step 6.
|
||||
16. [Finalization] If `VALBUF` is not empty, push `NORM(VALBUF)` to `VALSTACK`.
|
||||
17. While `OPSTACK` is not empty: Execute `EVALHELPER(VALSTACK, OPSTACK)`.
|
||||
18. Return pop from `VALSTACK`.
|
||||
|
||||
## Core language routines
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ proc lttb_exec {pline} {
|
||||
|
||||
# Integer number normalizer (for TB-valid signed 16-bit range)
|
||||
proc lttb_norm {num} {
|
||||
if {$num eq {}} {return 0}
|
||||
set num $(int($num))
|
||||
if {($num > 32767) || ($num < -32768)} {
|
||||
set num $($num % 65536)
|
||||
@@ -65,34 +66,8 @@ proc _lpop {list_var} {
|
||||
return $val
|
||||
}
|
||||
|
||||
# Tiny BASIC expression evaluator (two-pass)
|
||||
proc lttb_eval {lttb_ex} {
|
||||
# pre-checks
|
||||
set lttb_ex [string trim $lttb_ex]
|
||||
if {$lttb_ex eq {}} {return 0}
|
||||
if {[string is digit $lttb_ex]} {return [lttb_norm $(int($lttb_ex))]}
|
||||
# pass 1: variable and parentheses substitution
|
||||
global vars
|
||||
regsub -all -nocase -- {RND} $lttb_ex {0$} lttb_ex
|
||||
lassign {{(((} {}} pre_ex prevc
|
||||
foreach c [split $lttb_ex {}] {
|
||||
set outc {}
|
||||
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} {
|
||||
# Simple evaluation helper
|
||||
proc eval_helper {vsvar osvar} {
|
||||
upvar $vsvar valstack
|
||||
upvar $osvar opstack
|
||||
set res 0
|
||||
@@ -108,42 +83,56 @@ proc lttb_eval {lttb_ex} {
|
||||
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]} {
|
||||
}
|
||||
|
||||
# Tiny BASIC expression evaluator (single-pass)
|
||||
proc lttb_eval {lttb_ex} {
|
||||
global vars
|
||||
# pre-checks
|
||||
set lttb_ex [string trim $lttb_ex]
|
||||
if {$lttb_ex eq {}} {return 0}
|
||||
if {[string is digit $lttb_ex]} {return [lttb_norm $(int($lttb_ex))]}
|
||||
lassign {{( 0 + 1 - 1 * 2 / 2 $ 3} {} {} {} {} 0} precmap valbuf prevc valstack opstack i
|
||||
set l [string length $lttb_ex]
|
||||
loop i 0 $l {
|
||||
set c [string index $lttb_ex $i]
|
||||
if [string is space $c] {continue}
|
||||
if [string is digit $c] {
|
||||
append valbuf $c
|
||||
} else {
|
||||
if {[string length $valbuf] > 0} {
|
||||
lappend valstack $(int($valbuf))
|
||||
if {$valbuf ne {}} {
|
||||
lappend valstack [lttb_norm $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
|
||||
} elseif {$c eq {)}} {
|
||||
while {[llength $opstack] > 0 && [lindex $opstack end] ne "("} {
|
||||
while {[llength $opstack] > 0 && [lindex $opstack end] ne {(}} {
|
||||
eval_helper valstack opstack
|
||||
}
|
||||
if {[lindex $opstack end] eq "("} {_lpop opstack}
|
||||
} elseif {$c in {+ - * / $}} {
|
||||
set prec $precmap($c)
|
||||
while {[llength $opstack] > 0} {
|
||||
set top_op [lindex $opstack end]
|
||||
if {$top_op eq "("} { break }
|
||||
if {$precmap($top_op) >= $prec} {
|
||||
if {[lindex $opstack end] eq {(}} {_lpop opstack}
|
||||
}
|
||||
if {$c in {+ - * / $}} {
|
||||
if {$c in {+ -} && $prevc in {{} ( + - * / $}} {
|
||||
lappend valstack 0
|
||||
}
|
||||
while {[llength $opstack] > 0 && $precmap([lindex $opstack end]) >= $precmap($c) } {
|
||||
eval_helper valstack opstack
|
||||
} else { break }
|
||||
}
|
||||
lappend opstack $c
|
||||
}
|
||||
}
|
||||
set prevc $c
|
||||
}
|
||||
if {$valbuf ne {}} {lappend valstack [lttb_norm $valbuf]}
|
||||
while {[llength $opstack] > 0} {eval_helper valstack opstack}
|
||||
set res [lindex $valstack 0]
|
||||
if {$res eq {}} {set res 0}
|
||||
return $res
|
||||
return [lttb_norm [lindex $valstack 0]]
|
||||
}
|
||||
|
||||
# Tiny BASIC routines implementing various commands
|
||||
|
||||
Reference in New Issue
Block a user