2026-06-13 12:05:25 +03:00
|
|
|
#!/usr/bin/env jimsh
|
|
|
|
|
# Luxferre's Truly Tiny BASIC (Jim Tcl implementation)
|
|
|
|
|
# Based upon the original Tiny BASIC Design Notes by Dennis Allison
|
|
|
|
|
# Created in 2026 by Luxferre, released into the public domain
|
|
|
|
|
|
|
|
|
|
lassign {{} {} {} 0} progmem callstack vars ip
|
|
|
|
|
loop i 65 91 {set vars([format {%c} $i]) 0}
|
|
|
|
|
|
|
|
|
|
# Input line parser (single-pass)
|
|
|
|
|
proc lttb_proginput {rawline} {
|
|
|
|
|
set rawline [string trim $rawline]
|
|
|
|
|
if {[string length $rawline] > 0} {
|
|
|
|
|
lassign {0 0} lno i
|
|
|
|
|
set l [string length $rawline]
|
|
|
|
|
loop i $l {
|
|
|
|
|
set c [string index $rawline $i]
|
|
|
|
|
if {[string is space $c] || [string is digit $c]} {
|
|
|
|
|
if {[string is digit $c]} {set lno $($lno * 10 + int($c))}
|
|
|
|
|
} else {break}
|
|
|
|
|
}
|
|
|
|
|
set linebody [string toupper [string range $rawline $i end]]
|
|
|
|
|
# populate program memory or execute directly
|
|
|
|
|
if {$lno > 0} {
|
|
|
|
|
global progmem
|
|
|
|
|
dict set progmem $lno $linebody
|
|
|
|
|
} else {lttb_exec $linebody}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Main line interpreter
|
|
|
|
|
proc lttb_exec {pline} {
|
|
|
|
|
set kw [string range [lttb_stripspace $pline] 0 1]
|
|
|
|
|
if {[string index $kw 1] eq {=}} {
|
|
|
|
|
set kw LE
|
|
|
|
|
set pline [string cat {LE } $pline]
|
|
|
|
|
}
|
2026-06-13 14:58:48 +03:00
|
|
|
if {$kw in {EN GO IF IN LE PR RE}} {
|
|
|
|
|
set bi [string first { } $pline]
|
|
|
|
|
set body [string trim [string range $pline $bi end]]
|
2026-06-13 12:05:25 +03:00
|
|
|
if {$kw eq {GO} && [string index $pline 2] eq {S}} {
|
|
|
|
|
lttb_GOS $body
|
|
|
|
|
} else {lttb_$kw $body}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Integer number normalizer (for TB-valid signed 16-bit range)
|
|
|
|
|
proc lttb_norm {num} {
|
2026-06-13 20:11:12 +03:00
|
|
|
if {$num eq {}} {return 0}
|
2026-06-13 12:05:25 +03:00
|
|
|
set num $(int($num))
|
|
|
|
|
if {($num > 32767) || ($num < -32768)} {
|
|
|
|
|
set num $($num % 65536)
|
|
|
|
|
if {$num > 32767} {incr num -65536}
|
|
|
|
|
}
|
|
|
|
|
return $num
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Whitespace remover
|
|
|
|
|
proc lttb_stripspace {s} {return [regsub -all -- {\s} $s ""]}
|
|
|
|
|
|
|
|
|
|
# List pop helper
|
|
|
|
|
proc _lpop {list_var} {
|
|
|
|
|
upvar $list_var l
|
|
|
|
|
if {[llength $l] == 0} {return 0}
|
|
|
|
|
set val [lindex $l end]
|
|
|
|
|
set l [lreplace $l end end]
|
|
|
|
|
return $val
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-13 20:11:12 +03:00
|
|
|
# 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)
|
2026-06-13 12:05:25 +03:00
|
|
|
proc lttb_eval {lttb_ex} {
|
2026-06-13 20:11:12 +03:00
|
|
|
global vars
|
2026-06-13 12:05:25 +03:00
|
|
|
# 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))]}
|
2026-06-13 20:11:12 +03:00
|
|
|
lassign {{( 0 + 1 - 1 * 2 / 2 $ 3} {} {} {} {} 0} precmap valbuf prevc valstack opstack i
|
|
|
|
|
set l [string length $lttb_ex]
|
2026-06-13 20:20:58 +03:00
|
|
|
for {set i 0} {$i < $l} {incr i} {
|
2026-06-13 20:11:12 +03:00
|
|
|
set c [string index $lttb_ex $i]
|
|
|
|
|
if [string is space $c] {continue}
|
|
|
|
|
if [string is digit $c] {
|
2026-06-13 12:05:25 +03:00
|
|
|
append valbuf $c
|
|
|
|
|
} else {
|
2026-06-13 20:11:12 +03:00
|
|
|
if {$valbuf ne {}} {
|
|
|
|
|
lappend valstack [lttb_norm $valbuf]
|
2026-06-13 12:05:25 +03:00
|
|
|
set valbuf {}
|
|
|
|
|
}
|
2026-06-13 20:11:12 +03:00
|
|
|
if {[string range $lttb_ex $i $($i+2)] eq {RND}} {
|
|
|
|
|
set c {$}
|
|
|
|
|
incr i 2
|
|
|
|
|
} elseif {[string is upper $c]} {
|
|
|
|
|
lappend valstack [lttb_norm $vars($c)]
|
|
|
|
|
} elseif {$c eq {(}} {
|
2026-06-13 12:05:25 +03:00
|
|
|
lappend opstack $c
|
|
|
|
|
} elseif {$c eq {)}} {
|
2026-06-13 20:11:12 +03:00
|
|
|
while {[llength $opstack] > 0 && [lindex $opstack end] ne {(}} {
|
2026-06-13 12:05:25 +03:00
|
|
|
eval_helper valstack opstack
|
|
|
|
|
}
|
2026-06-13 20:11:12 +03:00
|
|
|
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
|
2026-06-13 12:05:25 +03:00
|
|
|
}
|
|
|
|
|
lappend opstack $c
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-13 20:11:12 +03:00
|
|
|
set prevc $c
|
2026-06-13 12:05:25 +03:00
|
|
|
}
|
2026-06-13 20:11:12 +03:00
|
|
|
if {$valbuf ne {}} {lappend valstack [lttb_norm $valbuf]}
|
2026-06-13 12:05:25 +03:00
|
|
|
while {[llength $opstack] > 0} {eval_helper valstack opstack}
|
2026-06-13 20:11:12 +03:00
|
|
|
return [lttb_norm [lindex $valstack 0]]
|
2026-06-13 12:05:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Tiny BASIC routines implementing various commands
|
|
|
|
|
|
|
|
|
|
proc lttb_LE {stmt} {
|
|
|
|
|
global vars
|
|
|
|
|
lassign [split $stmt =] vname ex
|
|
|
|
|
set vname [string index [string trim $vname] 0]
|
|
|
|
|
set vars($vname) [lttb_eval $ex]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
proc lttb_IF {stmt} {
|
|
|
|
|
lassign {-1 -1 {} 0} thenpos restpos relop istrue
|
|
|
|
|
regexp -indices -- {[>=<][>=<]*} $stmt rores
|
|
|
|
|
lassign $rores ropos roend
|
|
|
|
|
set relop [string range $stmt $ropos $roend]
|
|
|
|
|
set expr1 [string range $stmt 0 $($ropos-1)]
|
|
|
|
|
set rest [string range $stmt $($roend + 1) end]
|
|
|
|
|
foreach kw {THEN LET {=} RETURN GOSUB INPUT PRINT GOTO END IF} {
|
|
|
|
|
set thenpos [string first $kw $rest]
|
|
|
|
|
if {$thenpos > -1} {
|
|
|
|
|
if {$kw eq {=}} {incr thenpos -1}
|
|
|
|
|
set restpos $thenpos
|
|
|
|
|
if {$kw eq {THEN}} {incr restpos 4}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
set expr2 [string range $rest 0 $($thenpos - 1)]
|
|
|
|
|
set exdif $([lttb_eval $expr1] - [lttb_eval $expr2])
|
|
|
|
|
switch -exact -- $relop {
|
|
|
|
|
{=} {set istrue $($exdif == 0)}
|
|
|
|
|
{><} - {<>} {set istrue $($exdif != 0)}
|
|
|
|
|
{>} {set istrue $($exdif > 0)}
|
|
|
|
|
{<} {set istrue $($exdif < 0)}
|
|
|
|
|
{>=} {set istrue $($exdif >= 0)}
|
|
|
|
|
{<=} {set istrue $($exdif <= 0)}
|
|
|
|
|
}
|
|
|
|
|
if {$istrue} {lttb_proginput [string range $rest $restpos end]}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
proc lttb_GO {stmt} {
|
|
|
|
|
global ip
|
|
|
|
|
set ip $([lttb_eval $stmt] - 1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
proc lttb_GOS {stmt} {
|
|
|
|
|
global ip callstack
|
|
|
|
|
lappend callstack $ip
|
|
|
|
|
set ip $([lttb_eval $stmt] - 1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
proc lttb_RE {stmt} {
|
|
|
|
|
global ip callstack
|
|
|
|
|
set ip [_lpop callstack]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
proc lttb_IN {stmt} {
|
|
|
|
|
global vars
|
|
|
|
|
set varlist [split [lttb_stripspace $stmt] ,]
|
|
|
|
|
set vlen [llength $varlist]
|
|
|
|
|
puts -nonewline {? }
|
|
|
|
|
stdout flush
|
|
|
|
|
set inputbuf [string toupper [string trim [stdin gets]]]
|
2026-06-13 17:13:05 +03:00
|
|
|
set inputlist [split [lttb_stripspace $inputbuf] ,]
|
2026-06-13 12:05:25 +03:00
|
|
|
loop i 0 $vlen {
|
|
|
|
|
set vname [string index [lindex $varlist $i] 0]
|
2026-06-13 17:13:05 +03:00
|
|
|
set vars($vname) [lttb_eval [lindex $inputlist $i]]
|
2026-06-13 12:05:25 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
proc lttb_PR {stmt} {
|
|
|
|
|
lassign {0 {} {}} quoting printlist item
|
|
|
|
|
# Build the printlist
|
|
|
|
|
foreach c [split $stmt {}] {
|
|
|
|
|
if {$c eq {"}} {
|
|
|
|
|
set quoting $(1 - $quoting)
|
|
|
|
|
append item $c
|
|
|
|
|
} elseif {$quoting} {
|
|
|
|
|
append item $c
|
|
|
|
|
} elseif {$c in {; ,}} {
|
|
|
|
|
if {[string length $item] > 0} {
|
|
|
|
|
lappend printlist $item
|
|
|
|
|
set item {}
|
|
|
|
|
}
|
|
|
|
|
lappend printlist $c
|
|
|
|
|
} elseif {[string is graph $c]} {
|
|
|
|
|
append item $c
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if {[string length $item] > 0} {lappend printlist $item}
|
|
|
|
|
# Iterate over the printlist
|
|
|
|
|
foreach item $printlist {
|
|
|
|
|
if {[string index $item 0] eq {"}} {
|
|
|
|
|
puts -nonewline [string trim $item {"}]
|
|
|
|
|
} elseif {$item eq {,}} {
|
|
|
|
|
puts -nonewline "\t"
|
|
|
|
|
} elseif {$item ne {;}} {
|
|
|
|
|
puts -nonewline [lttb_eval $item]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if {$item ni {; ,}} {puts {}}
|
|
|
|
|
stdout flush
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
proc lttb_EN {stmt} {
|
|
|
|
|
global ip callstack
|
|
|
|
|
lassign {-1 {}} ip callstack
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Interactive-only routines start here
|
|
|
|
|
|
2026-06-13 13:36:32 +03:00
|
|
|
proc int_LIST {} {
|
2026-06-13 12:05:25 +03:00
|
|
|
global progmem
|
|
|
|
|
loop i 1 32768 {
|
2026-06-13 17:47:11 +03:00
|
|
|
if {[dict exists $progmem $i] && [string length $progmem($i)]} {
|
2026-06-13 12:05:25 +03:00
|
|
|
puts [format "%d\t%s" $i $progmem($i)]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-13 13:36:32 +03:00
|
|
|
proc int_RUN {} {
|
2026-06-13 12:05:25 +03:00
|
|
|
global progmem ip
|
|
|
|
|
set ip 1
|
|
|
|
|
while {$ip > 0 && $ip < 32768} {
|
|
|
|
|
if {[dict exists $progmem $ip]} {lttb_exec $progmem($ip)}
|
|
|
|
|
incr ip
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-13 13:36:32 +03:00
|
|
|
proc int_CLEAR {} {
|
2026-06-13 12:05:25 +03:00
|
|
|
global progmem callstack vars ip
|
|
|
|
|
lassign {{} {} {} 0} progmem callstack vars ip
|
|
|
|
|
loop i 65 91 {set vars([format {%c} $i]) 0}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-13 13:36:32 +03:00
|
|
|
proc int_LOAD {fname} {
|
2026-06-13 12:05:25 +03:00
|
|
|
if {$fname eq {}} {
|
|
|
|
|
puts -nonewline "File name? "
|
|
|
|
|
stdout flush
|
|
|
|
|
stdin gets fname
|
|
|
|
|
}
|
|
|
|
|
if {[file exists $fname]} {
|
2026-06-13 13:36:32 +03:00
|
|
|
int_CLEAR
|
2026-06-13 12:05:25 +03:00
|
|
|
set fh [open $fname r]
|
|
|
|
|
while {![eof $fh]} {
|
|
|
|
|
$fh gets progline
|
|
|
|
|
set progline [string trim $progline]
|
|
|
|
|
if {$progline ne {}} {lttb_proginput $progline}
|
|
|
|
|
}
|
|
|
|
|
$fh close
|
|
|
|
|
puts [format {Loaded %s} $fname]
|
|
|
|
|
} else {puts {Given file doesn't exist, skipping!}}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-13 13:36:32 +03:00
|
|
|
proc int_SAVE {} {
|
2026-06-13 12:05:25 +03:00
|
|
|
global progmem
|
|
|
|
|
puts -nonewline "File name? "
|
|
|
|
|
stdout flush
|
|
|
|
|
stdin gets fname
|
|
|
|
|
set fh [open $fname w]
|
|
|
|
|
loop i 1 32768 {
|
|
|
|
|
if {[dict exists $progmem $i]} {
|
|
|
|
|
$fh puts [format {%d %s} $i $progmem($i)]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$fh flush
|
|
|
|
|
$fh close
|
|
|
|
|
puts [format {Saved %s} $fname]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Interpreter entry point
|
|
|
|
|
|
2026-06-13 13:33:55 +03:00
|
|
|
puts {-----------------------------
|
2026-06-13 12:05:25 +03:00
|
|
|
Luxferre's Truly Tiny BASIC
|
|
|
|
|
Jim Tcl Edition
|
2026-06-13 13:33:55 +03:00
|
|
|
@ Copyleft 2026
|
2026-06-13 12:05:25 +03:00
|
|
|
All wrongs reserved
|
2026-06-13 13:33:55 +03:00
|
|
|
-----------------------------}
|
2026-06-13 12:05:25 +03:00
|
|
|
|
2026-06-13 13:36:32 +03:00
|
|
|
if {$argc > 0} {int_LOAD [lindex $argv 0]}
|
2026-06-13 12:05:25 +03:00
|
|
|
|
|
|
|
|
while {1} {
|
|
|
|
|
puts -nonewline {> }
|
|
|
|
|
stdout flush
|
|
|
|
|
set cmd [string toupper [string trim [stdin gets]]]
|
|
|
|
|
switch -exact -- $cmd {
|
|
|
|
|
BYE {puts "Bye!"; break}
|
2026-06-13 13:36:32 +03:00
|
|
|
RUN {int_RUN; continue}
|
|
|
|
|
LIST {int_LIST; continue}
|
|
|
|
|
NEW - CLEAR {int_CLEAR; continue}
|
|
|
|
|
LOAD {int_LOAD {}; continue}
|
|
|
|
|
SAVE {int_SAVE; continue}
|
2026-06-13 12:05:25 +03:00
|
|
|
default {lttb_proginput $cmd}
|
|
|
|
|
}
|
|
|
|
|
}
|