moving everything neatly into the assets directory

This commit is contained in:
minjaesong
2020-12-24 11:00:32 +09:00
parent 74b1279ed4
commit 608f61c617
111 changed files with 98 additions and 28 deletions

1
assets/disk0/!BOOTSEC Normal file
View File

@@ -0,0 +1 @@
let p=_BIOS.FIRST_BOOTABLE_PORT;com.sendMessage(p[0],"DEVRST\x17");com.sendMessage(p[0],'OPENR"tvdos/TVDOS.SYS",'+p[1]);let r=com.getStatusCode(p[0]);if(0==r)if(com.sendMessage(p[0],"READ"),r=com.getStatusCode(p[0]),0==r){let g=com.pullMessage(p[0]);eval(g)}else println("I/O Error");else println("TVDOS.SYS not found");println("Shutting down...");println("It is now safe to turn off the power");

View File

@@ -0,0 +1,6 @@
echo "Starting TVDOS..."
rem put set-xxx commands here:
rem this line specifies which shell to be presented after the boot precess:
command /fancy

254
assets/disk0/bf/99 Normal file
View File

@@ -0,0 +1,254 @@
# 99 bottles of beer in Brainf*ck
# Copyright (C) 2008 Raphael Bois
# 1671 brainf*ck instructions
# Published under GPL v2
Initialization
++ Counter for loop (a)
>+ unused
>++ Counter for loop (b)
> Flag for 'no more'
>+ Flag for not 'no more'
>>> (5) to (7) : temporary values
++++++++++[->+>+>++++++++++<<<]>>> 10 10 100 in (8) (9) (10)
>++++++++++ 10 in (11)
[-
>+++++ 50 in (12)
>++++++++++ 100 in (13)
>+++++++++++ 110 in (14)
>++++++++ 80 in (15)
>++++++++ 80 in (16)
>+++ 30 in (17)
>++++ 40 in (18)
>+ 10 in (19)
<<<<<<<<]
+
>-- + 48 '0' plus 1 in (12)
>++ + 102 'f' plus 1 in (13)
>+++++ + 115 's' plus 1 in (14)
>-- + 78 'N' plus 1 in (15)
>++++ + 84 'T' plus 1 in (16)
>++ + 32 ' ' plus 1 in (17)
>++++ + 44 comma plus 1 in (18)
> + 10 LF plus 1 in (19)
stuff for writing parts of the song
>+ select stuff
>+ select stuff
>+ write song part 3
>++ write song part 1
>+ write song part 2
>+ Flag for 'end of song'
>++ Flag for not 'end of song'
All bytes are at val plus 1
Go back to (7) with final initialization step (remove 1 to all bytes)
[-<]
<<<<<<< at (0)
[ loop (a)
-
>> at (2)
[ loop (b)
>>>>>>>> at (10)
[ start loop
<<<<<<< at (3)
[->[-]
print '(N|n)o more'
>>>>>>>>>>>. '(N|n)'
<----. 'o'
>>>. ' '
<<<--. 'm'
++. 'o'
+++.+ 'r'
<-.+ 'e'
<<+<<<<<<<<
]+> at (4)
[-<[-]>>>>> at (9)
prints number (using (9) and (10))
[>>>+<<<<+<+<+>>>-]<<<[->>>+<<<]> at (6)
[>>>>>>+<<<<<<-]>>>>>[[-]>.<]<<<<[>>>>>-<<<<<-]>> at (9)
[<<+<+<+>>>>-]<<<<[->>>>+<<<<]> at (6)
[>>>>>>+<<<<<<-]>>>>>>.<<<<<[>>>>>-<<<<<-] at (7)
memorize in (11) if (10) not 1
>>>[-<<<+<+>>>>]<<<<[->>>>+<<<<]>-[[-]>>>>+<<<<]<<< at (4)
]+
>>>>>>>> at (12)
print ' bottle(s) of beer'
>>>>>. ' '
<<<<----. 'b'
>----. 'o'
+++++..- 'tt'
<++++++++++. 'l'
-------. 'e'
<<[[-]>>>.<<<]>> 's' if (11)==1 ie if (10)!=1
>>>>. ' '
<<<----. 'o'
<+. 'f'
>>>>. ' '
<<<<----. 'b'
+++..+ 'ee'
>+++.+ 'r'
[>] at (20)
+>+>[->+<<-<-
print ' on the wall' DOT LF
<<<. ' '
<<<----. 'o'
-. 'n'
>>>. ' '
<<<++++++. 't'
<++. 'h'
---. 'e'
>>>>. ' '
<<<+++. 'w'
<----. 'a'
+++++++++++.. 'll'
------>---- reset to 'f' and 's'
>---------- ---------- ---------- -- sets (15) to 'N'
>>>++.-- DOT
>. LF
>>>] at (22)
>>>[->[-]<<<<<<<[<]<[-]>>[>]>>>>>]+ if end of song reset bottles counter
>[-<[-] at (25)
<<<< at (21)
[->>[->+<<<<-
print ' on the wall' COMMA ' '
<<<. ' '
<<<----. 'o'
-. 'n'
>>>. ' '
<<<++++++. 't'
<++. 'h'
---. 'e'
>>>>. ' '
<<<+++. 'w'
<----. 'a'
+++++++++++.. 'll'
------>---- reset (13) and (14) to 'f' and 's'
>++++++++++ ++++++++++ ++++++++++ ++ sets (15) to 'n'
>>>. comma
<. ' '
>>>>>>]<<]< at (20)
[->>>>[-<<+< at (21)
<<<++.-- DOT
>. LF
[<]<<<<<<<< at (3)
[->[-]<]+> at (4)
[-<[-]>
>>>>>>>>>>>>. 'T'
<<<-----. 'a'
++++++++++. 'k'
------. 'e'
>>>>. ' '
<<<----. 'o'
-. 'n'
<. 'e'
>>>>. ' '
<<<<-. 'd'
>+. 'o'
++++++++. 'w'
---------. 'n'
>>>. ' '
<<<<---. 'a'
>. 'n'
<+++. 'd'
>>>>. ' '
<<<++. 'p'
<---. 'a'
>+++.. 'ss'
>>>. ' '
<<<<++++++++. 'i'
>+. 't'
>>>. ' '
<<<<--------. 'a'
>--. 'r'
---. 'o'
++++++. 'u'
-------. 'n'
<+++. 'd'
++>+++++ reset (13) and (14) to 'f' and 's'
>>>>. comma
<. ' '
[<]<<<<<<< at (4)
]+
>>>>>> at (10)
decrements values
-<<<+>>[<<[-]<+<+>>>>-]<<<<[>-<[-]]>[->>>+<<<]>[->->+++++++++<<]>>> at (10)
>>[>]>>>>] at (24)
<<<<] at (20)
>>>>>>]+ at (26)
<<<<<<<[<]< at (10)
]
+<+
<<<<<<+< at (2)
-
]
print 'Go to the store and buy some more' comma ' '
>>>>>>>>>>[>]>>>>> at (25)
[->[-]<]+> at (26)
[-<[-]
<<<<<<<<< at (16)
-------------. 'G'
<<----. 'o'
>>>. ' '
<<<+++++. 't'
-----. 'o'
>>>. ' '
<<<+++++. 't'
<++. 'h'
---. 'e'
>>>>. ' '
<<<-. 's'
+. 't'
-----. 'o'
+++. 'r'
<. 'e'
>>>>. ' '
<<<<----. 'a'
>----. 'n'
<+++. 'd'
>>>>. ' '
<<<<--. 'b'
>+++++++. 'u'
++++. 'y'
>>>. ' '
<<<------. 's'
----. 'o'
--. 'm'
<+++. 'e'
>>>>. ' '
<<<. 'm'
++. 'o'
+++.+ 'r'
<.+ 'e'
>>>>>. coma
<. ' '
>>>>>>>>>
]+
Initialize last loop to print final '99 bottles of beer on the wall' DOT
<[-]+<[-]<[-]<[-]+<<< at (19)
[<]<[-]<[-]<[-]<[-] at (7)
++++++++++[->+>+>++++++++++<<<]>->->-
<<<<<<[-]+<[-]<+<< at (0)
]

2
assets/disk0/bf/hello Normal file
View File

@@ -0,0 +1,2 @@
>++++++++[-<+++++++++>]<.>>+>-[+]++>++>+++[>[->+++<<+++>]<<]>-----.>->
+++..+++.>-.<<+[>[+>+]>>]<--------------.>>.+++.------.--------.>+.>+.

21
assets/disk0/bf/scan Normal file
View File

@@ -0,0 +1,21 @@
Calculate the value 256 and test if it's zero
If the interpreter errors on overflow this is where it'll happen
++++++++[>++++++++<-]>[<++++>-]
+<[>-<
Not zero so multiply by 256 again to get 65536
[>++++<-]>[<++++++++>-]<[>++++++++<-]
+>[>
# Print "32"
++++++++++[>+++++<-]>+.-.[-]<
<[-]<->] <[>>
# Print "16"
+++++++[>+++++++<-]>.+++++.[-]<
<<-]] >[>
# Print "8"
++++++++[>+++++++<-]>.[-]<
<-]<
# Print " bit cells\n"
+++++++++++[>+++>+++++++++>+++++++++>+<<<<-]>-.>-.+++++++.+++++++++++.<.
>>.++.+++++++..<-.>>-
Clean up used cells.
[[-]<]

View File

@@ -0,0 +1,13 @@
1 FOR I = 99 TO 1 STEP -1
2 MODE = 1
3 GOSUB 12
4 PRINT I;" bottle";BOTTLES;" of beer on the wall, ";i;" bottle";BOTTLES;" of beer."
5 MODE = 2
6 GOSUB 12
7 PRINT "Take one down and pass it around, ";(I-1);" bottle";BOTTLES;" of beer on the wall."
8 NEXT
9 PRINT "No more bottles of beer on the wall, no more bottles of beer."
10 PRINT "Go to the store and buy some more. 99 bottles of beer on the wall."
11 END
12 IF I == MODE THEN BOTTLES = "" ELSE BOTTLES = "s"
13 RETURN

View File

@@ -0,0 +1,203 @@
1 OPTIONBASE 1
10 PRINT SPC(28);"AMAZING PROGRAM"
20 PRINT SPC(15);"CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"
30 PRINT
31 PRINT
32 PRINT
33 PRINT
100 PRINT "WHAT ARE YOUR WIDTH";
101 INPUT H
102 PRINT "WHAT ARE YOUR LENGTH";
103 INPUT V
105 IF H<>1 AND V<>1 THEN GOTO 110
106 PRINT "MEANINGLESS DIMENSIONS. TRY AGAIN."
107 GOTO 100
110 WS=DIM(H,V)
111 VS=DIM(H,V)
120 PRINT
130 PRINT
140 PRINT
150 PRINT
160 Q=0
161 Z=0
162 X=INT(RND(1)*H+1)
165 FOR I=1 TO H
170 IF I==X THEN GOTO 173
171 PRINT ".--";
172 GOTO 175
173 PRINT ". ";
180 NEXT
190 PRINT "."
195 C=1
196 WS(X,1)=C
197 C=C+1
200 R=X
201 S=1
202 GOTO 260
210 IF R<>H THEN GOTO 240
215 IF S<>V THEN GOTO 230
220 R=1
221 S=1
222 GOTO 250
230 R=1
231 S=S+1
232 GOTO 250
240 R=R+1
250 IF WS(R,S)==0 THEN GOTO 210
260 IF R-1==0 THEN GOTO 530
265 IF WS(R-1,S)<>0 THEN GOTO 530
270 IF S-1==0 THEN GOTO 390
280 IF WS(R,S-1)<>0 THEN GOTO 390
290 IF R==H THEN GOTO 330
300 IF WS(R+1,S)<>0 THEN GOTO 330
310 X=INT(RND(1)*3+1)
320 REM ON X GOTO 790,820,860
321 IF X==1 THEN GOTO 790
322 IF X==2 THEN GOTO 820
323 IF X==3 THEN GOTO 860
330 IF S<>V THEN GOTO 340
334 IF Z==1 THEN GOTO 370
338 Q=1
339 GOTO 350
340 IF WS(R,S+1)<>0 THEN GOTO 370
350 X=INT(RND(1)*3+1)
360 REM ON X GOTO 790,820,910
361 IF X==1 THEN GOTO 790
362 IF X==2 THEN GOTO 820
363 IF X==3 THEN GOTO 910
370 X=INT(RND(1)*2+1)
380 REM ON X GOTO 790,820
390 IF R==H THEN GOTO 470
400 IF WS(R+1,S)<>0 THEN GOTO 470
405 IF S<>V THEN GOTO 420
410 IF Z==1 THEN GOTO 450
415 Q=1
416 GOTO 430
420 IF WS(R,S+1)<>0 THEN GOTO 450
430 X=INT(RND(1)*3+1)
440 REM ON X GOTO 790,860,910
441 IF X==1 THEN GOTO 790
442 IF X==2 THEN GOTO 860
443 IF X==3 THEN GOTO 910
450 X=INT(RND(1)*2+1)
460 REM ON X GOTO 790,860
470 IF S<>V THEN GOTO 490
480 IF Z==1 THEN GOTO 520
485 Q=1
486 GOTO 500
490 IF WS(R,S+1)<>0 THEN GOTO 520
500 X=INT(RND(1)*2+1)
510 REM ON X GOTO 790,910
511 IF X==1 THEN GOTO 790
512 IF X==2 THEN GOTO 910
520 GOTO 790
530 IF S-1==0 THEN GOTO 670
540 IF WS(R,S-1)<>0 THEN GOTO 670
545 IF R==H THEN GOTO 610
547 IF WS(R+1,S)<>0 THEN GOTO 610
550 IF S<>V THEN GOTO 560
552 IF Z==1 THEN GOTO 590
554 Q=1
555 GOTO 570
560 IF WS(R,S+1)<>0 THEN GOTO 590
570 X=INT(RND(1)*3+1)
580 REM ON X GOTO 820,860,910
581 IF X==0 THEN GOTO 820
582 IF X==1 THEN GOTO 860
583 IF X==2 THEN GOTO 910
590 X=INT(RND(1)*2+1)
600 REM ON X GOTO 820,860
601 IF X==1 THEN GOTO 820
602 IF X==2 THEN GOTO 860
610 IF S<>V THEN GOTO 630
620 IF Z==1 THEN GOTO 660
625 Q=1
626 GOTO 640
630 IF WS(R,S+1)<>0 THEN GOTO 660
640 X=INT(RND(1)*2+1)
650 REM ON X GOTO 820,910
651 IF X==0 THEN GOTO 820
652 IF X==1 THEN GOTO 910
660 GOTO 820
670 IF R==H THEN GOTO 740
680 IF WS(R+1,S)<>0 THEN GOTO 740
685 IF S<>V THEN GOTO 700
690 IF Z==1 THEN GOTO 730
695 Q=1
696 GOTO 830
700 IF WS(R,S+1)<>0 THEN GOTO 730
710 X=INT(RND(1)*2+1)
720 REM ON X GOTO 860,910
721 IF X==0 THEN GOTO 860
722 IF X==1 THEN GOTO 910
730 GOTO 860
740 IF S<>V THEN GOTO 760
750 IF Z==1 THEN GOTO 780
755 Q=1
756 GOTO 770
760 IF WS(R,S+1)<>0 THEN GOTO 780
770 GOTO 910
780 GOTO 1000
790 WS(R-1,S)=C
800 C=C+1
801 VS(R-1,S)=2
802 R=R-1
810 IF C==H*V+1 THEN GOTO 1010
815 Q=0
816 GOTO 260
820 WS(R,S-1)=C
830 C=C+1
840 VS(R,S-1)=1
841 S=S-1
842 IF C==H*V+1 THEN GOTO 1010
850 Q=0
851 GOTO 260
860 WS(R+1,S)=C
870 C=C+1
871 IF VS(R,S)==0 THEN GOTO 880
875 VS(R,S)=3
876 GOTO 890
880 VS(R,S)=2
890 R=R+1
900 IF C==H*V+1 THEN GOTO 1010
905 GOTO 530
910 IF Q==1 THEN GOTO 960
920 WS(R,S+1)=C
921 C=C+1
922 IF VS(R,S)==0 THEN GOTO 940
930 VS(R,S)=3
931 GOTO 950
940 VS(R,S)=1
950 S=S+1
951 IF C==H*V+1 THEN GOTO 1010
955 GOTO 260
960 Z=1
970 IF VS(R,S)==0 THEN GOTO 980
975 VS(R,S)=3
976 Q=0
977 GOTO 1000
980 VS(R,S)=1
981 Q=0
982 R=1
983 S=1
984 GOTO 250
1000 GOTO 210
1010 FOR J=1 TO V
1011 PRINT "I";
1012 FOR I=1 TO H
1013 IF VS(I,J)<2 THEN GOTO 1030
1020 PRINT " ";
1021 GOTO 1040
1030 PRINT " I";
1040 NEXT
1041 PRINT
1043 FOR I=1 TO H
1045 IF VS(I,J)==0 THEN GOTO 1060
1050 IF VS(I,J)==2 THEN GOTO 1060
1051 PRINT ": ";
1052 GOTO 1070
1060 PRINT ":--";
1070 NEXT
1071 PRINT "."
1072 NEXT
1073 END

View File

@@ -0,0 +1,320 @@
1 OPTIONBASE 1
2 PRINT SPC(31);"BLACK JACK"
4 PRINT SPC(15);"CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"
6 PRINT:PRINT:PRINT
10 DEFUN TRUNCATEHAND(Q)=Q+11*(Q>=22)
20 HANDCARDS=DIM(15,12):HANDTOTAL=DIM(15):DECK=DIM(52):DISCARD=DIM(52):PTOTAL=DIM(8):THISHNDFOR=DIM(7):BETS=DIM(15)
30 CARDLEN=DIM(15)
31 ZARR=DIM(10)
40 REM--HANDCARDS(I,J) IS THE JTH CARD IN HAND I, HANDTOTAL(I) IS TOTAL OF HAND I
50 REM--DECK IS THE DECK BEING DEALT FROM, DISCARD IS THE DISCARD PILE,
60 REM--PTOTAL(I) IS THE TOTAL FOR PLAYER I, THISHNDFOR(I) IS THE TOTAL THIS HAND FOR
70 REM--PLAYER I, BETS(I) IS TH BET FOR HAND I
80 REM--CARDLEN(I) IS THE LENGTH OF HANDCARDS(I,*)
90 GOTO 1500
100 REM--SUBROUTINE TO GET A CARD. RESULT IS PUT IN X.
110 IF C<51 THEN GOTO 230
120 PRINT "RESHUFFLING"
130 FOR D=D TO 1 STEP -1
140 C=C-1
150 DECK(C)=DISCARD(D)
160 NEXT
170 FOR C1=52 TO C STEP -1
180 C2=INT(RND(1)*(C1-C+1))+C
190 C3=DECK(C2)
200 DECK(C2)=DECK(C1)
210 DECK(C1)=C3
220 NEXT
230 X=DECK(C)
240 C=C+1
250 RETURN
300 REM--SUBROUTINE TO EVALUATE HAND I. TOTAL IS PUT INTO
310 REM--HANDTOTAL(I). TOTALS HAVE THE FOLLOWING MEANING:
320 REM-- 2-10...HARD 2-10
330 REM-- 11-21...SOFT 11-21
340 REM-- 22-32...HARD 11-21
350 REM-- 33+....BUSTED
360 Q=0
370 FOR Q2=1 TO CARDLEN(I)
380 X=HANDCARDS(I,Q2)
390 GOSUB 500
400 NEXT
410 HANDTOTAL(I)=Q
420 RETURN
500 REM--SUBROUTINE TO ADD CARD X TO TOTAL Q.
510 X1=X: IF X1>10 THEN X1=10: REM SAME AS X1=10 MIN X
520 Q1=Q+X1
530 IF Q>=11 THEN GOTO 590
540 IF X>1 THEN GOTO 570
550 Q=Q+11
560 RETURN
570 Q=Q1-11*(Q1>=11)
580 RETURN
590 Q=Q1-(Q<=21 AND Q1>21)
600 IF Q<33 THEN GOTO 620
610 Q=-1
620 RETURN
700 REM--CARD PRINTING SUBROUTINE
710 REM DSTR DEFINED ELSEWHERE
720 PRINT MID(DSTR,3*X-2,3);
730 PRINT " ";
740 RETURN
750 REM--ALTERNATIVE PRINTING ROUTINE
760 PRINT " ";MID(DSTR,3*X-1,2);
770 PRINT " ";
780 RETURN
800 REM--SUBROUTINE TO PLAY OUT A HAND.
810 REM--NO SPLITTING OR BLACKJACKS ALLOWED
820 H1=5
830 GOSUB 1410
840 H1=3
850 ON H GOTO 950,930
860 GOSUB 100
870 BETS(I)=BETS(I)*2
880 PRINT "RECEIVED A";
890 GOSUB 700
900 GOSUB 1100
910 IF Q>0 THEN GOSUB 1300
920 RETURN
930 GOSUB 1320
940 RETURN
950 GOSUB 100
960 PRINT "RECEIVED A";
970 GOSUB 700
980 GOSUB 1100
990 IF Q<0 THEN GOTO 940
1000 PRINT "HIT";
1010 GOTO 830
1100 REM--SUBROUTINE TO ADD A CARD TO ROW I
1110 CARDLEN(I)=CARDLEN(I)+1
1120 HANDCARDS(I,CARDLEN(I))=X
1130 Q=HANDTOTAL(I)
1140 GOSUB 500
1150 HANDTOTAL(I)=Q
1160 IF Q>=0 THEN GOTO 1190
1170 PRINT "...BUSTED"
1180 GOSUB 1200
1190 RETURN
1200 REM--SUBROUTINE TO DISCARD ROW I
1210 IF CARDLEN(I)<>0 THEN GOTO 1230
1220 RETURN
1230 D=D+1
1240 DISCARD(D)=HANDCARDS(I,CARDLEN(I))
1250 CARDLEN(I)=CARDLEN(I)-1
1260 GOTO 1210
1300 REM--PRINTS TOTAL OF HAND I
1310 PRINT
1320 AA=TRUNCATEHAND(HANDTOTAL(I))
1325 PRINT "TOTAL IS";AA
1330 RETURN
1400 REM--SUBROUTINE TO READ REPLY
1410 REM ISTR DEFINED ELSEWHERE
1420 INPUT HSTR: HSTR=LEFT(HSTR,1)
1430 FOR H=1 TO H1 STEP 2
1440 IF HSTR==MID(ISTR,H,1) THEN GOTO 1480
1450 NEXT
1460 PRINT "TYPE ";MID(ISTR,1,H1-1);" OR ";MID(ISTR,H1,2);" PLEASE";
1470 GOTO 1420
1480 H=(H+1)/2
1490 RETURN
1500 REM--PROGRAM STARTS HERE
1510 REM--INITIALIZE
1520 DSTR="N A 2 3 4 5 6 7N 8 9 10 J Q K"
1530 ISTR="H,S,D,/,"
1540 FOR I=1 TO 13
1550 FOR J=4*I-3 TO 4*I
1560 DISCARD(J)=I
1570 NEXT
1580 NEXT
1590 D=52
1600 C=53
1610 PRINT "DO YOU WANT INSTRUCTIONS";
1620 INPUT HSTR
1630 IF LEFT(HSTR,1)=="N" OR LEFT(HSTR,1)=="n" THEN GOTO 1760
1640 PRINT "THIS IS THE GAME OF 21. AS MANY AS 7 PLAYERS MAY PLAY THE"
1650 PRINT "GAME. ON EACH DEAL, BETS WILL BE ASKED FOR, AND THE"
1660 PRINT "PLAYERS' BETS SHOULD BE TYPED IN. THE CARDS WILL THEN BE"
1670 PRINT "DEALT, AND EACH PLAYER IN TURN PLAYS HIS HAND. THE"
1680 PRINT "FIRST RESPONSE SHOULD BE EITHER 'D', INDICATING THAT THE"
1690 PRINT "PLAYER IS DOUBLING DOWN, 'S', INDICATING THAT HE IS"
1700 PRINT "STANDING, 'H', INDICATING HE WANTS ANOTHER CARD, OR '/',"
1710 PRINT "INDICATING THAT HE WANTS TO SPLIT HIS CARDS. AFTER THE"
1720 PRINT "INITIAL RESPONSE, ALL FURTHER RESPONSES SHOULD BE 'S' OR"
1730 PRINT "'H', UNLESS THE CARDS WERE SPLIT, IN WHICH CASE DOUBLING"
1740 PRINT "DOWN IS AGAIN PERMITTED. IN ORDER TO COLLECT FOR"
1750 PRINT "BLACKJACK, THE INITIAL RESPONSE SHOULD BE 'S'."
1760 PRINT "NUMBER OF PLAYERS";
1770 INPUT N
1775 PRINT
1780 IF N<1 OR N>7 OR N>INT(N) THEN GOTO 1760
1790 FOR I=1 TO 8: PTOTAL(I)=0: NEXT
1800 D1=N+1
1810 IF 2*D1+C>=52 THEN GOSUB 120
1820 IF C==2 THEN C=C-1
1830 FOR I=1 TO N: ZARR(I)=0: NEXT
1840 FOR I=1 TO 15: BETS(I)=0: NEXT
1850 FOR I=1 TO 15: HANDTOTAL(I)=0: NEXT
1860 FOR I=1 TO 7: THISHNDFOR(I)=0: NEXT
1870 FOR I=1 TO 15: CARDLEN(I)=0: NEXT
1880 PRINT "BETS:"
1890 FOR I=1 TO N: PRINT "#";I;: INPUT ZARR(I): NEXT
1900 FOR I=1 TO N
1910 IF ZARR(I)<=0 OR ZARR(I)>500 THEN GOTO 1880
1920 BETS(I)=ZARR(I)
1930 NEXT
1940 PRINT "PLAYER";
1950 FOR I=1 TO N
1960 PRINT I;" ";
1970 NEXT
1980 PRINT "DEALER"
1990 FOR J=1 TO 2
2000 PRINT SPC(5);
2010 FOR I=1 TO D1
2020 GOSUB 100
2030 HANDCARDS(I,J)=X
2040 IF J==1 OR I<=N THEN GOSUB 750
2050 NEXT
2060 PRINT
2070 NEXT
2080 FOR I=1 TO D1
2090 CARDLEN(I)=2
2100 NEXT
2110 REM--TEST FOR INSURANCE
2120 IF HANDCARDS(D1,1)>1 THEN GOTO 2240
2130 PRINT "ANY INSURANCE";
2140 INPUT HSTR
2150 IF LEFT(HSTR,1)<>"Y" THEN GOTO 2240
2160 PRINT "INSURANCE BETS"
2170 FOR I=1 TO N: PRINT "#";I;: INPUT ZARR(I): NEXT
2180 FOR I=1 TO N
2190 IF ZARR(I)<0 OR ZARR(I)>BETS(I)/2 THEN GOTO 2160
2200 NEXT
2210 FOR I=1 TO N
2220 THISHNDFOR(I)=ZARR(I)*(3*(-(HANDCARDS(D1,2)>=10))-1)
2230 NEXT
2240 REM--TEST FOR DEALER BLACKJACK
2250 L1=1: L2=1
2252 IF HANDCARDS(D1,1)==1 AND HANDCARDS(D1,2)>9 THEN L1=0: L2=0
2253 IF HANDCARDS(D1,2)==1 AND HANDCARDS(D1,1)>9 THEN L1=0: L2=0
2254 IF L1<>0 OR L2<>0 THEN GOTO 2320
2260 PRINT:PRINT "DEALER HAS A";MID(DSTR,3*HANDCARDS(D1,2)-2,3);" IN THE HOLE ";
2270 PRINT "FOR BLACKJACK"
2280 FOR I=1 TO D1
2290 GOSUB 300
2300 NEXT
2310 GOTO 3140
2320 REM--NO DEALER BLACKJACK
2330 IF HANDCARDS(D1,1)>1 AND HANDCARDS(D1,1)<10 THEN GOTO 2350
2340 PRINT:PRINT "NO DEALER BLACKJACK."
2350 REM--NOW PLAY THE HANDS
2360 FOR I=1 TO N
2370 PRINT "PLAYER";I;
2380 H1=7
2390 GOSUB 1410
2400 ON H GOTO 2550,2410,2510,2600
2410 REM--PLAYER WANTS TO STAND
2420 GOSUB 300
2430 IF HANDTOTAL(I)<>21 THEN GOTO 2490
2440 PRINT "BLACKJACK"
2450 THISHNDFOR(I)=THISHNDFOR(I)+1.5*BETS(I)
2460 BETS(I)=0
2470 GOSUB 1200
2480 GOTO 2900
2490 GOSUB 1320
2500 GOTO 2900
2510 REM--PLAYER WANTS TO DOUBLE DOWN
2520 GOSUB 300
2530 GOSUB 860
2540 GOTO 2900
2550 REM--PLAYER WANTS TO BE HIT
2560 GOSUB 300
2570 H1=3
2580 GOSUB 950
2590 GOTO 2900
2600 REM--PLAYER WANTS TO SPLIT
2610 L1=HANDCARDS(I,1): IF HANDCARDS(I,1)>10 THEN L1=10
2612 L2=HANDCARDS(I,2): IF HANDCARDS(I,2)>10 THEN L2=10
2614 IF L1==L2 THEN GOTO 2640
2620 PRINT "SPLITTING NOT ALLOWED."
2630 GOTO 2370
2640 REM--PLAY OUT SPLIT
2650 I1=I+D1
2660 CARDLEN(I1)=2
2670 HANDCARDS(I1,1)=HANDCARDS(I,2)
2680 BETS(I+D1)=BETS(I)
2690 GOSUB 100
2700 PRINT "FIRST HAND RECEIVES A";
2710 GOSUB 700
2720 HANDCARDS(I,2)=X
2730 GOSUB 300
2740 PRINT
2750 GOSUB 100
2760 PRINT "SECOND HAND RECEIVES A";
2770 I=I1
2780 GOSUB 700
2790 HANDCARDS(I,2)=X
2800 GOSUB 300
2810 PRINT
2820 I=I1-D1
2830 IF HANDCARDS(I,1)==1 THEN GOTO 2900
2840 REM--NOW PLAY THE TWO HANDS
2850 PRINT "HAND";1-(I>D1);
2860 GOSUB 800
2870 I=I+D1
2880 IF I==I1 THEN GOTO 2850
2890 I=I1-D1
2900 NEXT
2910 GOSUB 300
2920 REM--TEST FOR PLAYING DEALER'S HAND
2930 FOR I=1 TO N
2940 IF CARDLEN(I)>0 OR CARDLEN(I+D1)>0 THEN GOTO 3010
2950 NEXT
2960 PRINT "DEALER HAD A";
2970 X=HANDCARDS(D1,2)
2980 GOSUB 700
2990 PRINT " CONCEALED."
3000 GOTO 3140
3010 PRINT "DEALER HAS A";MID(DSTR,3*HANDCARDS(D1,2)-2,3);" CONCEALED ";
3020 I=D1
3030 AA=TRUNCATEHAND(HANDTOTAL(I))
3035 PRINT "FOR A TOTAL OF";AA
3040 IF AA>16 THEN GOTO 3130
3050 PRINT "DRAWS";
3060 GOSUB 100
3070 GOSUB 750
3080 GOSUB 1100
3090 AA=TRUNCATEHAND(Q)
3095 IF Q>0 AND AA<17 THEN GOTO 3060
3100 HANDTOTAL(I)=Q-(Q<0)/2
3110 IF Q<0 THEN GOTO 3140
3120 AA=TRUNCATEHAND(Q)
3125 PRINT "---TOTAL IS";AA
3130 PRINT
3140 REM--TALLY THE RESULT
3150 REM
3160 ZSTR="LOSES PUSHES WINS "
3165 PRINT
3170 FOR I=1 TO N
3180 AA=TRUNCATEHAND(HANDTOTAL(I))
3182 AB=TRUNCATEHAND(HANDTOTAL(I+D1))
3184 AC=TRUNCATEHAND(HANDTOTAL(D1))
3186 THISHNDFOR(I)=THISHNDFOR(I)+BETS(I)*SGN(AA-AC)+BETS(I+D1)*SGN(AB-AC)
3188 BETS(I+D1)=0
3200 PRINT "PLAYER";I;
3210 PRINT MID(ZSTR,SGN(THISHNDFOR(I))*6+7,6);" ";
3220 IF THISHNDFOR(I)<>0 THEN GOTO 3250
3230 PRINT " ";
3240 GOTO 3260
3250 PRINT ABS(THISHNDFOR(I));
3260 PTOTAL(I)=PTOTAL(I)+THISHNDFOR(I)
3270 PRINT "TOTAL=";PTOTAL(I)
3280 GOSUB 1200
3290 PTOTAL(D1)=PTOTAL(D1)-THISHNDFOR(I)
3300 I=I+D1
3310 GOSUB 1200
3320 I=I-D1
3330 NEXT
3340 PRINT "DEALER'S TOTAL=";PTOTAL(D1)
3345 PRINT
3350 GOSUB 1200
3360 GOTO 1810

View File

@@ -0,0 +1,6 @@
1 KS=4!1!2!5!3!NIL
10 DEFUN LESSER(P,X)=X<P
11 KLS=LESSER<~HEAD KS
20 FOR K=1 TO 5
21 PRINT KLS(K)
22 NEXT

View File

@@ -0,0 +1,4 @@
10 DEFUN F(K,T)=ABS(T)==K
20 CF=F<~32
30 PRINT CF(24) : REM will print 'false'
40 PRINT CF(-32) : REM will print 'true'

View File

@@ -0,0 +1,3 @@
10 DEFUN KA(X)=IF X>2 THEN DO(PRINT("HAI");PRINT(X)) ELSE DO(PRINT("BYE");PRINT(X))
20 INPUT N
30 KA(N)

View File

@@ -0,0 +1,2 @@
10 PRINT GETKEYSDOWN()
20 GOTO 10

View File

@@ -0,0 +1,6 @@
10 POKE -40,255
20 FOR K=-41 TO -48 STEP -1
30 PRINT(PEEK(K);" ";)
40 NEXT
50 PRINT
60 GOTO 10

View File

@@ -0,0 +1,3 @@
10 for k=0 to 1024
20 emit(k mod 1024;)
30 next

View File

@@ -0,0 +1,3 @@
10 DEFUN FAC(N)=IF N==0 THEN 1 ELSE N*FAC(N-1)
20 K=MAP FAC, 1 TO 10
30 PRINT K

View File

@@ -0,0 +1,4 @@
10 DEFUN FAC(N)=IF N==0 THEN 1 ELSE N*FAC(N-1)
20 FOR K=1 TO 6
30 PRINT FAC(K)
40 NEXT

View File

@@ -0,0 +1,4 @@
10 DEFUN FIB(N)=IF N==0 THEN 0 ELSE IF N==1 THEN 1 ELSE FIB(N-1)+FIB(N-2)
20 FOR K=1 TO 12
30 PRINT FIB(K);" ";
40 NEXT

View File

@@ -0,0 +1,4 @@
1 XS=4!1!2!3!5!NIL
10 DEFUN K(P,X)=X<P
20 NXS=FILTER(K<~HEAD XS,XS)
30 PRINT NXS

View File

@@ -0,0 +1,13 @@
1 k=0
2 goto 100
20 k=1
30 a=10
40 return
100 for a=5 to 1 step -1
110 print a
120 if a==3 and k==0 then gosub 20
130 next
140 print "=="
150 print a
1000 rem expected output according to gw-basic:
1001 rem 5 4 3 9 8 7 6 5 4 3 2 1 == 0

View File

@@ -0,0 +1,7 @@
1 GOTO START
100 LABEL PRINTRNDNUM
110 K=FIX(RND(1)*256)
120 PRINT " ";K;" ";
130 RETURN
1000 LABEL START
1010 PRINT "HELLO,";:GOSUB PRINTRNDNUM:PRINT(IF K==1 THEN "WORLD" ELSE "WORLDS")

View File

@@ -0,0 +1,6 @@
10 FOR I = 2 TO 9
20 FOR J = 1 TO 9
30 PRINT I; "*"; J; "="; I * J
40 NEXT
50 INPUT "Press Enter Key..."
60 NEXT

View File

@@ -0,0 +1,160 @@
10 PRINT SPC(32);"HAMURABI"
20 PRINT SPC(15);"CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"
30 PRINT
31 PRINT
32 PRINT
80 PRINT "TRY YOUR HAND AT GOVERNING ANCIENT SUMERIA"
90 PRINT "FOR A TEN-YEAR TERM OF OFFICE."
91 PRINT
95 D1=0
96 P1=0
100 Z=0
101 P=95
102 S=2800
103 H=3000
104 E=H-S
110 Y=3
111 A=H/Y
112 I=5
113 Q=1
210 D=0
215 PRINT
216 PRINT
217 PRINT "HAMURABI: I BEG TO REPORT TO YOU,"
218 Z=Z+1
219 PRINT "IN YEAR ";Z;", ";D;" PEOPLE STARVED, ";I;" CAME TO THE CITY,"
220 P=P+I
227 IF Q>0 THEN GOTO 230
228 P=INT(P/2)
229 PRINT "A HORRIBLE PLAGUE STRUCK! HALF THE PEOPLE DIED."
230 PRINT "POPULATION IS NOW ";P
232 PRINT "THE CITY NOW OWNS ";A;" ACRES."
235 PRINT "YOU HARVESTED ";Y;" BUSHELS PER ACRE."
250 PRINT "THE RATS ATE ";E;" BUSHELS."
260 PRINT "YOU NOW HAVE ";S;" BUSHELS IN STORE."
261 PRINT
270 IF Z==11 THEN GOTO 860
310 C=INT(10*RND(1))
311 Y=C+17
312 PRINT "LAND IS TRADING AT ";Y;" BUSHELS PER ACRE."
320 PRINT "HOW MANY ACRES DO YOU WISH TO BUY";
321 INPUT Q
322 IF Q<0 THEN GOTO 850
323 IF Y*Q<=S THEN GOTO 330
324 GOSUB 710
325 GOTO 320
330 IF Q==0 THEN GOTO 340
331 A=A+Q
332 S=S-Y*Q
333 C=0
334 GOTO 400
340 PRINT "HOW MANY ACRES DO YOU WISH TO SELL";
341 INPUT Q
342 IF Q<0 THEN GOTO 850
343 IF Q<A THEN GOTO 350
344 GOSUB 720
345 GOTO 340
350 A=A-Q
351 S=S+Y*Q
352 C=0
400 PRINT
410 PRINT "HOW MANY BUSHELS DO YOU WISH TO FEED YOUR PEOPLE";
411 INPUT Q
412 IF Q<0 THEN GOTO 850
418 REM *** TRYING TO USE MORE GRAIN THAN IS IN SILOS?
420 IF Q<=S THEN GOTO 430
421 GOSUB 710
422 GOTO 410
430 S=S-Q
431 C=1
432 PRINT
440 PRINT "HOW MANY ACRES DO YOU WISH TO PLANT WITH SEED";
441 INPUT D
442 IF D==0 THEN GOTO 511
443 IF D<0 THEN GOTO 850
444 REM *** TRYING TO PLANT MORE ACRES THAN YOU OWN?
445 IF D<=A THEN GOTO 450
446 GOSUB 720
447 GOTO 440
449 REM *** ENOUGH GRAIN FOR SEED?
450 IF INT(D/2)<=S THEN GOTO 455
452 GOSUB 710
453 GOTO 440
454 REM *** ENOUGH PEOPLE TO TEND THE CROPS?
455 IF D<10*P THEN GOTO 510
460 PRINT "BUT YOU HAVE ONLY ";P;" PEOPLE TO TEND THE FIELDS! NOW THEN,"
470 GOTO 440
510 S=S-INT(D/2)
511 GOSUB 800
512 REM *** A BOUNTIFUL HARVEST!
515 Y=C
516 H=D*Y
517 E=0
521 GOSUB 800
522 IF INT(C/2)<>C/2 THEN GOTO 530
523 REM *** RATS ARE RUNNING WILD!!
525 E=INT(S/C)
530 S=S-E+H
531 GOSUB 800
532 REM *** LET'S HAVE SOME BABIES
533 I=INT(C*(20*A+S)/P/100+1)
539 REM *** HOW MANY PEOPLE HAD FULL TUMMIES?
540 C=INT(Q/20)
541 REM *** HORROS, A 15% CHANCE OF PLAGUE
542 Q=INT(10*(2*RND(1)-0.3))
550 IF P<C THEN GOTO 210
551 REM *** STARVE ENOUGH FOR IMPEACHMENT?
552 D=P-C
553 IF D>0.45*P THEN GOTO 560
554 P1=((Z-1)*P1+D*100/P)/Z
555 P=C
556 D1=D1+D
557 GOTO 215
560 PRINT
561 PRINT "YOU STARVED ";D;" PEOPLE IN ONE YEAR!!!"
565 PRINT "DUE TO THIS EXTREME MISMANAGEMENT YOU HAVE NOT ONLY"
566 PRINT "BEEN IMPEACHED AND THROWN OUT OF OFFICE BUT YOU HAVE"
567 PRINT "ALSO BEEN DECLARED NATIONAL FINK!!!!"
568 GOTO 990
710 PRINT "HAMURABI: THINK AGAIN. YOU HAVE ONLY"
711 PRINT S;" BUSHELS OF GRAIN. NOW THEN,"
712 RETURN
720 PRINT "HAMURABI: THINK AGAIN. YOU OWN ONLY ";A;" ACRES. NOW THEN,"
730 RETURN
800 C=INT(RND(1)*5)+1
801 RETURN
850 PRINT
851 PRINT "HAMURABI: I CANNOT DO WHAT YOU WISH."
855 PRINT "GET YOURSELF ANOTHER STEWARD!!!!!"
857 GOTO 990
860 PRINT "IN YOUR 10-YEAR TERM OF OFFICE, ";P1;" PERCENT OF THE"
862 PRINT "POPULATION STARVED PER YEAR ON THE AVERAGE, I.E. A TOTAL OF"
865 PRINT D1;"PEOPLE DIED!!"
866 L=A/P
870 PRINT "YOU STARTED WITH 10 ACRES PER PERSON AND ENDED WITH"
875 PRINT L;"ACRES PER PERSON."
876 PRINT
880 IF P1>33 THEN GOTO 565
885 IF L<7 THEN GOTO 565
890 IF P1>10 THEN GOTO 940
892 IF L<9 THEN GOTO 940
895 IF P1>3 THEN GOTO 960
896 IF L<10 THEN GOTO 960
900 PRINT "A FANTASTIC PERFORMANCE!!! CHARLEMANGE, DISRAELI, AND"
905 PRINT "JEFFERSON COMBINED COULD NOT HAVE DONE BETTER!"
906 GOTO 990
940 PRINT "YOUR HEAVY-HANDED PERFORMANCE SMACKS OF NERO AND IVAN IV."
945 PRINT "THE PEOPLE (REMIANING) FIND YOU AN UNPLEASANT RULER, AND,"
950 PRINT "FRANKLY, HATE YOUR GUTS!!"
951 GOTO 990
960 PRINT "YOUR PERFORMANCE COULD HAVE BEEN SOMEWHAT BETTER, BUT"
965 PRINT "REALLY WASN'T TOO BAD AT ALL. ";INT(P*0.8*RND(1));" PEOPLE"
970 PRINT "WOULD DEARLY LIKE TO SEE YOU ASSASSINATED BUT WE ALL HAVE OUR"
975 PRINT "TRIVIAL PROBLEMS."
990 PRINT
991 FOR N=1 TO 10
992 PRINT EMIT(7;)
993 NEXT
995 PRINT "SO LONG FOR NOW."
996 PRINT
999 END

View File

@@ -0,0 +1,25 @@
1 PRINT SPC(35);"MULT-TABLE"
2 PRINT "THIS PROGRAM TESTS THE MULTIDIMENSIONAL ARRAY ALLOCATING AND INDEXING"
3 PRINT
4 OPTIONBASE 1
5 OPTIONDEBUG 1
6 OPTIONTRACE 1
9 I=DIM(2)
10 PRINT "TABLE WIDTH";
11 INPUT I(1)
20 PRINT "TABLE HEIGHT";
21 INPUT I(2)
30 T=DIM(I(2),I(1))
40 FOR Y=1 TO I(2)
50 FOR X=1 TO I(1)
60 T(Y,X)=X*Y
70 NEXT
80 NEXT
90 RESOLVE T
100 FOR Y=1 TO I(2)
110 FOR X=1 TO I(1)
120 PRINT T(Y,X);" ";
130 NEXT
140 PRINT
150 NEXT
200 PRINT "ENDE"

View File

@@ -0,0 +1,15 @@
1 OPTIONBASE 1
2 GOTO STARTPOINT
100 LABEL PRINTA
110 PRINT "A"
120 RETURN
200 LABEL PRINTB
210 PRINT "B":PRINT "B"
220 RETURN
300 LABEL PRINTC
310 PRINT "C":PRINT 2+2
320 RETURN
1000 INPUT K:LABEL STARTPOINT
1001 IF K>3 OR K<1 THEN DO(PRINT "INPUT MUST BE 1,2 OR 3";GOTO STARTPOINT)
1010 ON K GOSUB PRINTA,PRINTB,PRINTC
1020 PRINT "BYE"

View File

@@ -0,0 +1,8 @@
1 REM qsort [] = []
2 REM qsort xs = qsort [x | x<-tail xs, x<head xs] ++ [head xs] ++ qsort [x | x<-tail xs, x>=head xs]
10 DEFUN LESS(P,X)=X<P
11 DEFUN GTEQ(P,X)=X>=P
12 DEFUN QSORT(XS)=IF LEN(XS)<1 THEN NIL ELSE QSORT(FILTER(LESS<~HEAD(XS),TAIL(XS))) # HEAD(XS)!NIL # QSORT(FILTER(GTEQ<~HEAD(XS),TAIL(XS)))
100 L=7!9!4!5!2!3!1!8!6!NIL
110 PRINT L
120 PRINT QSORT(L)

View File

@@ -0,0 +1,2 @@
10 print(chr(47+round(rnd(1))*45);)
20 goto 10

View File

@@ -0,0 +1,4 @@
10 DEFUN SINC(X)=SIN(X)/X
20 FOR K=1 TO 10
30 PRINT SINC(K)
40 NEXT

View File

@@ -0,0 +1,17 @@
1 ZEROLINE=10
2 AMP=20
3 GOTO 1000
100 LABEL SINCQ:REM gets Sinc(Q)
110 Q=IF I==0 THEN 1.0 ELSE SIN(I)/I
120 RETURN
200 LABEL PLOTLINE:REM Converts 0-1 value into screen line. input is Q, results are stored to SQ
210 SQ=CHR(0)
220 FOR X=1 TO ZEROLINE+AMP
230 SQ=SQ+(IF X==ROUND(ZEROLINE+Q*AMP) THEN "@" ELSE IF X==10 THEN "|" ELSE CHR(250))
240 NEXT
250 RETURN
1000 FOR I=0 TO 20
1010 GOSUB SINCQ
1020 GOSUB PLOTLINE
1030 PRINT(SQ)
1040 NEXT

View File

@@ -0,0 +1,17 @@
1 ZEROLINE=10
2 AMP=20
10 DEFUN SINC(P)=IF P==0 THEN 1.0 ELSE SIN(P)/P
20 DEFUN TOCHAR(P,X)=IF (X==ROUND(ZEROLINE+P*AMP)) THEN "@" ELSE IF (X==ZEROLINE) THEN "|" ELSE CHR(250)
30 DEFUN SCONCAT(ACC,S)=ACC+S
40 REM DEFUN PLOTLINE(X)=FOLD(SCONCAT,CHR(0),MAP(TOCHAR<~X,1 TO ZEROLINE+AMP))
41 DEFUN PLOTLINE(F,X)=FOLD(SCONCAT,CHR(0),MAP(TOCHAR<~F(X),1 TO ZEROLINE+AMP))
100 FOR I=0 TO 20
110 PRINT PLOTLINE(SINC(I))
120 NEXT
999 END
1000 REM Known bugs
1010 DEFUN PLOTLINE(F,X)=FOLD(SCONCAT,CHR(0),MAP(TOCHAR<~F(X),1 TO ZEROLINE+AMP))
1011 REM calling user-defined function within DEFUN is not working
1020 REM empty string literal "" is translated to number zero (another JS blunder)

View File

@@ -0,0 +1,7 @@
1 REM Calculates a square root using newtonian method
20 INPUT X
30 Y = 0.5 * X
40 Z = Y
50 Y = Y-(((Y^2)-X)/(2*Y))
60 IF Z <> Y THEN GOTO 40
100 PRINT "Square root of ";X;" is approximately ";Y

View File

@@ -0,0 +1,6 @@
10 FOR I = 1 TO 20
20 PRINT SPC(20-I);
30 FOR J = 1 TO I*2-1
40 PRINT "*";
50 NEXT:PRINT
60 NEXT

66
assets/disk0/home/bf.js Normal file
View File

@@ -0,0 +1,66 @@
// exec_args: bf.js input_file optional_memsize
let memsize = exec_args[2]|0;
if (memsize <= 0) memsize = (system.maxmem() < 30000) ? system.maxmem()-256 : 30000;
let nativePtr = undefined;
try {
nativePtr = sys.malloc(memsize);
}
catch (e) {
printerrln("Could not allocate memory with size "+memsize);
return 10;
}
let stubHead = "let mx="+memsize+";"+
"let fmod=function(a,b){return Number((a-(Math.floor(a/b)*b)).toPrecision(8));};"+
"let ip=function(){p=fmod(p+1,mx)};"+
"let dp=function(){p=fmod(p-1,mx)};"+
"let iv=function(){sys.poke(p,fmod(sys.peek(p)+1,256))};"+
"let dv=function(){sys.poke(p,fmod(sys.peek(p)-1,256))};"+
"let p="+nativePtr+";"
let translation = {
62: "ip();",
60: "dp();",
43: "iv();",
45: "dv();",
46: "sys.print(String.fromCharCode(sys.peek(p)));",
44: "sys.poke(p,sys.readKey());",
91: "while(sys.peek(p)!=0){",
93: "}"
};
if (exec_args[1] === undefined) {
printerrln("Usage: bf <path-to-BF-program>");
return 1;
}
let bfprg = "";
try {
filesystem.open(_G.shell.getCurrentDrive(), exec_args[1], "R");
bfprg = filesystem.readAll(_G.shell.getCurrentDrive());
}
catch(e) {
printerrln(e);
sys.free(nativePtr);
return 1;
}
try {
// translate
let tprg = stubHead;
for (let k = 0; k < bfprg.length; k++) {
tprg += (translation[bfprg.charCodeAt(k)] || "");
}
// clear memory
for (let k = 0; k < memsize; k++) {
sys.poke(nativePtr+k, 0);
}
// run
execApp(tprg);
}
catch (e) {
printerrln(e);
return 1;
}
finally {
sys.free(nativePtr);
}
return 0;

View File

@@ -0,0 +1,7 @@
con.clear();
con.move(1,1);
for (let i = 0; i < 1024; i++) {
if (i < 512) con.color_pair(239, 0); else con.color_pair(0, 239);
con.addch(i%256);
}
println();

View File

@@ -0,0 +1,5 @@
let src = "var tObj = {}; tObj.testvalue = 'hai'; tObj;"
var testGlobalObject = eval(src);
serial.println(testGlobalObject.testvalue);

View File

@@ -0,0 +1,6 @@
println("Hit Ctrl-C or Ctrl-D to exit");
while (true) {
let key = con.getch()
println(key);
if (key == 3 || key == 4) break;
}

View File

@@ -0,0 +1,12 @@
10 GOSUB PRINTHELLO
20 GOSUB PRINTHAI
30 GOSUB PRINTBYE
40 END
100 LABEL PRINTHELLO
110 PRINT "HELLO"
120 RETURN
200 LABEL PRINTHAI
210 PRINT "HAI"
220 RETURN
230 PRINT "BYE":LABEL PRINTBYE
240 RETURN

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
sys.mapRom((exec_args[1]|0)||0);
println(sys.romReadAll());

View File

@@ -0,0 +1,7 @@
println("Hello, world!")
filesystem.open("A", "fsh.js", "R");
let prg = filesystem.readAll("A");
println(prg);
eval(prg);

View File

@@ -0,0 +1,4 @@
println(_TVDOS.VERSION); // sanity check
try { cmdHistory.push("lol haxxxxxx"); } catch(_) {}
try { CURRENT_DRIVE = "B"; } catch(_) {}
return "XwX";

View File

@@ -0,0 +1,19 @@
serial.println(typeof atob);
const inputstr =
'println("TERRAN Megatrends inc.");let p=0;let m=[[0,255,170,85,105,15,165,30,199,113,142,227,202,254,186,190],[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255]];con.move(2,1),print("000 KB OK");try{for(;p<8<<20;){con.move(2,1);var x=""+(p+1>>10);print(x<10?"00"+x:x<100?"0"+x:x);for(var t=0;t<m.length;t++){for(var b=0;b<m[t].length;b++)if(sys.poke(p+b,m[t][b]),m[t][b]!=sys.peek(p+b))throw"Memory Error";for(var b=0;b<m[t].length;b++)if(sys.poke(p+b,255-m[t][b]),255-m[t][b]!=sys.peek(p+b))throw"Memory Error"}p+=m[0].length}}catch(t){"Memory Error"==t?println(" Memory Error"):println(" KB OK!")}var _BIOS={FIRST_BOOTABLE_PORT:[0,1]};Object.freeze(_BIOS);let n=0,s=0;for(;n<4&&(!com.areYouThere(n)||(com.sendMessage(n,"LOADBOOT"),s=com.getStatusCode(n),0!=s));)n+=1;n<4?eval(com.fetchResponse(n).trimNull()):printerrln("No bootable medium found.");';
serial.println(inputstr);
let inputbytes = [];
for (let i = 0; i < inputstr.length; i++) {
inputbytes.push(inputstr.charCodeAt(i));
}
let compstr = gzip.comp(inputbytes);
for (let i = 0; i < compstr.length; i++) {
serial.print((compstr[i] & 255).toString(16).padStart(2,'0') + " ");
}

3102
assets/disk0/tbas/basic.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
\begin{itemlist}
\item \emph{PC-BASIC 2.0.3 Documentation} \\ \url{https://robhagemans.github.io/pcbasic/doc/2.0/}
\end{itemlist}

View File

@@ -0,0 +1,38 @@
\quad
\chapterprecishere{``Caution! Under no circumstances confuse the adjective \emph{basic} with the noun \emph{BASIC}, except under confusing circumstances!''\par\raggedleft --- \textup{\tbas\ Reference Manual, \theedition}\footnote{Original quotation is from \emph{The INTERCAL Programming Language Reference Manual} by Donald R. Woods and James M. Lyon}}
This chapter describes the basic concepts of the \tbas\ language.
\section{Values and Types}
\label{valuesandtypes}
BASIC is a \emph{Dynamically Typed Language}, which means variables do not know which group they should barge in; only values of the variable do. In fact, there is no type definition in the language: \emph{we do want our variables to feel themselves awkward.}
There are six basic types: \emph{number}, \emph{boolean}, \emph{string}, \emph{array}, \emph{generator} and \emph{function}.
\emph{Number} represents real (double-precision floating-point or \emph{actually rational}) numbers. Operations on numbers follow the same rules of the underlying virtual machine\footnote{if you are not a computer person, just disregard}, and such machines must follow the IEEE 754 standard\footnote{ditto.}.
\emph{Boolean} is the type of the values that is either \codebf{TRUE} or \codebf{FALSE}. Number \codebf{0} and \codebf{FALSE} makes condition \emph{false}. When used in numeric context, \codebf{FALSE} will be interpreted as 0 and \codebf{TRUE} as 1.
\emph{String} represents immutable\footnote{cannot be altered directly} sequences of bytes. However, you can't weave them to make something like \emph{string array}\footnote{future feature\ldots\ maybe\ldots? Probably not\ldots}.
\emph{Array} represents collection of numbers in 1- or more dimensions.
\emph{Generator} represents a value that automatically counts up/down whenever they have been called in For-Next loop.
\emph{Functions} are, well\ldots\ functions\footnote{This is not a closure; there is no way you can define a local- or anonymous variable in BASIC.}, especially user-defined ones. Functions are \emph{type} because some built-in functions will actually take \emph{functions} as arguments.
\section{Control Flow}
A program is executed starting with its lowest line number. Statements on a line are executed from left to right. When all Statements are finished execution, next lowest line number will be executed. Control flow functions can modify this normal flow.
You can dive into other line in the middle of the program with \code{GOTO}. The program flow will continue normally at the new line \emph{and it will never know ya just did that}.
If you want less insane jumping, \code{GOSUB} is used to jump to a subroutine. Subroutine is a little section of a code that serves as a tiny program inside of a program. \code{GOSUB} will remember from which statement in the line you have came from, and will return your program flow to that line when \code{RETURN} statement is encountered. (of course, if \code{RETURN} is used without \code{GOSUB}, program will raise some error) Do note that while you can reserve some portion of a program line as a \code{subroutine}, \tbas\ will not provide local variables and whatnot as all variables in \tbas\ are global, and you can just \code{GOTO} out of a subroutine to anywhere you desire and wreak havoc \emph{if you really want to}.
The \code{ON} statement provides alternative branching construct. You can enter multiple line numbers and let your variable (or mathematical expression) to choose which index of line numbers to \code{GOTO}- or \code{GOSUB} into.
The \code{IF-THEN-ELSE} lets you to conditionally select which of the two branches to execute.
The \code{FOR-NEXT} lets you to loop a portion of a program while automatically counting your chosen variable up or down.

View File

@@ -0,0 +1,3 @@
10 DEFUN FAC(N)=IF N==0 THEN 1 ELSE N*FAC(N-1)
20 K=MAP FAC, 1 TO 10
30 PRINT K

View File

@@ -0,0 +1,4 @@
10 DEFUN FAC(N)=IF N==0 THEN 1 ELSE N*FAC(N-1)
20 FOR K=1 TO 6
30 PRINT FAC(K)
40 NEXT

View File

@@ -0,0 +1,4 @@
10 DEFUN FIB(N)=IF N==0 THEN 0 ELSE IF N==1 THEN 1 ELSE FIB(N-1)+FIB(N-2)
20 FOR K=1 TO 12
30 PRINT FIB(K);" ";
40 NEXT

View File

@@ -0,0 +1,241 @@
\label{functions}
Functions are a form of expression that may taks input arguments surrounded by parentheses. Most of the traditional BASIC \emph{statements} that does not return a value are \emph{functions} in \tbas , and like those, while \tbas\ functions can be called without parentheses, it is highly \emph{discouraged} because of the ambiguities in syntax. \textbf{Always use parentheses on function call!}
\section{Mathematical}
\subsection{ABS}
\codeline{Y \textbf{= ABS(}X\textbf{)}}\par
Returns absolute value of \code{X}.
\subsection{ACO}
\codeline{Y \textbf{= ACO(}X\textbf{)}}\par
Returns inverse cosine of \code{X}.
\subsection{ASN}
\codeline{Y \textbf{= ASN(}X\textbf{)}}\par
Returns inverse sine of \code{X}.
\subsection{ATN}
\codeline{Y \textbf{= ATN(}X\textbf{)}}\par
Returns inverse tangent of \code{X}.
\subsection{CBR}
\codeline{Y \textbf{= CBR(}X\textbf{)}}\par
Returns cubic root of \code{X}.
\subsection{CEIL}
\codeline{Y \textbf{= CEIL(}X\textbf{)}}\par
Returns integer value of \code{X}, truncated towards positive infinity.
\subsection{COS}
\codeline{Y \textbf{= COS(}X\textbf{)}}\par
Returns cosine of \code{X}.
\subsection{COSH}
\codeline{Y \textbf{= COSH(}X\textbf{)}}\par
Returns hyperbolic cosine of \code{X}.
\subsection{EXP}
\codeline{Y \textbf{= EXP(}X\textbf{)}}\par
Returns exponential of \code{X}, i.e. $e^X$.
\subsection{FIX}
\codeline{Y \textbf{= FIX(}X\textbf{)}}\par
Returns integer value of \code{X}, truncated towards zero.
\subsection{FLOOR, INT}
\codeline{Y \textbf{= FLOOR(}X\textbf{)}}
\codeline{Y \textbf{= INT(}X\textbf{)}}\par
Returns integer value of \code{X}, truncated towards negative infinity.
\subsection{LEN}
\codeline{Y \textbf{= LEN(}X\textbf{)}}\par
Returns length of \code{X}. \code{X} can be either a string or an array.
\subsection{LOG}
\codeline{Y \textbf{= LOG(}X\textbf{)}}\par
Returns natural logarithm of \code{X}.
\subsection{ROUND}
\codeline{Y \textbf{= ROUND(}X\textbf{)}}\par
Returns closest integer value of \code{X}, rounding towards positive infinity.
\subsection{RND}
\codeline{Y \textbf{= RND(}X\textbf{)}}\par
Returns a random number within the range of $[0..1)$. If \code{X} is zero, previous random number will be returned; otherwise new random number will be returned.
\subsection{SIN}
\codeline{Y \textbf{= SIN(}X\textbf{)}}\par
Returns sine of \code{X}.
\subsection{SINH}
\codeline{Y \textbf{= SINH(}X\textbf{)}}\par
Returns hyperbolic sine of \code{X}.
\subsection{SGN}
\codeline{Y \textbf{= SGN(}X\textbf{)}}\par
Returns sign of \code{X}: 1 for positive, -1 for negative, 0 otherwise.
\subsection{SQR}
\codeline{Y \textbf{= SQR(}X\textbf{)}}\par
Returns square root of \code{X}.
\subsection{TAN}
\codeline{Y \textbf{= TAN(}X\textbf{)}}\par
Returns tangent of \code{X}.
\subsection{TANH}
\codeline{Y \textbf{= TANH(}X\textbf{)}}\par
Returns hyperbolic tangent of \code{X}.
\section{Input}
\subsection{CIN}
\codeline{S \textbf{= CIN()}}\par
Waits for the user input and returns it.
\subsection{DATA}
\codeline{\textbf{DATA} CONST0 [\textbf{,} CONST1]\ldots}\par
Adds data that can be read by \code{READ} function. \code{DATA} declarations need not be reacheable in the program flow.
\subsection{DGET}
\codeline{S \textbf{= DGET()}}\par
Fetches a data declared from \code{DATA} statements and returns it, incrementing the \code{DATA} position.
\subsection{DIM}
\codeline{Y \textbf{= DIM(}X\textbf{)}}\par
Returns array with size of \code{X}, all filled with zero.
\subsection{GETKEYSDOWN}
\codeline{K \textbf{= GETKEYSDOWN()}}\par
Stores array that contains keycode of keys held down into the given variable.\par
Actual keycode and the array length depends on the machine: in \thismachine , array length will be fixed to 8. For the list of available keycodes, see \ref{implementation}.
\subsection{INPUT}
\codeline{\textbf{INPUT} VARIABLE}\par
Prints out \code{? } to the console and waits for user input. Input can be any length and terminated with return key. The input will be stored to given variable.\par
This behaviour is to keep the compatibility with the traditional BASIC. For function-like usage, use \code{CIN} instead.
\subsection{READ}
\codeline{\textbf{READ} VARIABLE}\par
Assigns data declared from \code{DATA} statements to given variable. Reading starts at the current \code{DATA} position, and the data position will be incremented by one. The position is reset to the zero by the \code{RUN} command.\par
This behaviour is to keep the compatibility with the traditional BASIC. For function-like usage, use \code{DGET} instead.
\section{Output}
\subsection{EMIT}
\codeline{\textbf{EMIT(}EXPR [\{\textbf{,}|\textbf{;}\} EXPR]\ldots\textbf{)}}\par
Prints out characters corresponding to given number on the code page being used.\par
\code{EXPR} is numeric expression.
\subsection{PRINT}
\codeline{\textbf{PRINT(}EXPR [\{\textbf{,}|\textbf{;}\} EXPR]\ldots\textbf{)}}\par
Prints out given string expressions.\par
\code{EXPR} is a string, numeric expression, or array.\par
\code{PRINT} is one of the few function that differentiates two style of argument separator: \codebf{;} will simply concatenate two expressions (unlike traditional BASIC, numbers will not have surrounding spaces), \codebf{,} tabulates the expressions.
\section{Program Manipulation}
\subsection{CLEAR}
\codeline{\textbf{CLEAR}}\par
Clears all declared variables.
\subsection{END}
\codeline{\textbf{END}}\par
Stops program execution and returns control to the user.
\subsection{FOR}
\codeline{\textbf{FOR} LOOPVAR \textbf{=} START \textbf{TO} STOP [\textbf{STEP} STEP]}
\codeline{\textbf{FOR} LOOPVAR \textbf{=} GENERATOR}\par
Starts a \code{FOR-NEXT} loop.\par
Initially, \code{LOOPVAR} is set to \code{START} then statements between the \code{FOR} statement and corresponding \code{NEXT} statements are executed and \code{LOOPVAR} is incremented by \code{STEP}, or by 1 if \code{STEP} is not specified. The program flow will continue to loop around until \code{LOOPVAR} is outside the range of \code{START}--\code{STOP}. The value of the \code{LOOPVAR} is equal to \code{STOP}$+$\code{STEP} when the looping finishes.
\subsection{FOREACH}
\codeline{\textbf{FOREACH} LOOPVAR \textbf{IN} ARRAY}\par
Same as \code{FOR} but fetches \code{LOOPVAR} from given \code{ARRAY}.
\subsection{GOSUB}
\codeline{\textbf{GOSUB} LINENUM}\par
Jumps to a subroutine at \code{LINENUM}. The next \code{RETURN} statements makes program flow to jump back to the statement after the \code{GOSUB}.\par
\code{LINENUM} can be either a numeric expression or a Label.
\subsection{GOTO}
\codeline{\textbf{GOTO} LINENUM}\par
Jumps to \code{LINENUM}.\par
\code{LINENUM} can be either a numeric expression or a Label.
\subsection{LABEL}
\codeline{\textbf{LABEL} NAME}\par
Puts a name onto the line numeber the statement is located. Jumping to the \code{NAME}
\subsubsection*{Notes}
\begin{itemlist}
\item \code{NAME} must be a valid variable name.
\end{itemlist}
\subsection{NEXT}
\codeline{\textbf{NEXT}}\par
Iterates \code{FOR-NEXT} loop and increments the loop variable from the most recent \code{FOR} statement and jumps to that statement.
\subsection{RESTORE}
\codeline{\textbf{RESTORE}}\par
Resets the \code{DATA} pointer.
\subsection{RETURN}
\codeline{\textbf{RETURN}}\par
Returns from the \code{GOSUB} statement.
\section{String Manipulation}
\subsection{CHR}
\codeline{CHAR \textbf{= CHR(}X\textbf{)}}\par
Returns the character with code point of \code{X}. Code point is a numeric expression in the range of $[0-255]$.
\subsection{LEFT}
\codeline{SUBSTR \textbf{= LEFT(}STR \textbf{,} NUM\_CHARS\textbf{)}}\par
Returns the leftmost \code{NUM\_CHARS} characters of \code{STR}.
\subsection{MID}
\codeline{SUBSTR \textbf{= MID(}STR \textbf{,} POSITION \textbf{,} LENGTH\textbf{)}}\par
Returns a substring of \code{STR} starting at \code{POSITION} with specified \code{LENGTH}.\par
When \code{OPTIONBASE 1} is specified, the position starts from 1; otherwise it will start from 0.
\subsection{RIGHT}
\codeline{SUBSTR \textbf{= RIGHT(}STR \textbf{,} NUM\_CHARS\textbf{)}}\par
Returns the rightmost \code{NUM\_CHARS} characters of \code{STR}.
\subsection{SPC}
\codeline{STR \textbf{= SPC(}STR \textbf{,} NUM\_CHARS\textbf{)}}\par
Returns a string of \code{NUM\_CHARS} spaces.
\section{Array Manipulation}
\subsection{HEAD}
\codeline{K \textbf{= HEAD(}ARRAY\textbf{)}}\par
Returns the head element of the given array.
\subsection{INIT}
\codeline{K \textbf{= INIT(}ARRAY\textbf{)}}\par
Returns the new array that has its last element removed.
\subsection{LAST}
\codeline{K \textbf{= LAST(}ARRAY\textbf{)}}\par
Returns the last element of the given array.
\subsection{TAIL}
\codeline{K \textbf{= TAIL(}ARRAY\textbf{)}}\par
Returns the new array that has its head element removed.
\section{Graphics}
\subsection{PLOT}
\codeline{\textbf{PLOT(}X\_POS \textbf{,} Y\_POS \textbf{,} COLOUR\textbf{)}}\par
Plots a pixel to the framebuffer of the display, at XY-position of \code{X\_POS} and \code{Y\_POS}, with colour of \code{COLOUR}.\par
Top-left corner of the pixel will be 1 if \code{OPTIONBASE 1} is specified; otherwise it will be 0.
\section{Meta}
\subsection{OPTIONBASE}
\codeline{\textbf{OPTIONBASE} \{\textbf{0}|\textbf{1}\}}\par
Specifies at which number the array/string/pixel indices begin.
\subsection{OPTIONDEBUG}
\codeline{\textbf{OPTIONDEBUG} \{\textbf{0}|\textbf{1}\}}\par
Specifies whether or not the debugging messages should be printed out. The messages will be printed out to the \emph{serial debugging console}, or to the stdout.
\subsection{OPTIONTRACE}
\codeline{\textbf{OPTIONTRACE} \{\textbf{0}|\textbf{1}\}}\par
Specifies whether or not the line numbers should be printed out. The messages will be printed out to the \emph{serial debugging console}, or to the stdout.
\section{System}
\subsection{PEEK}
\codeline{BYTE \textbf{= PEEK(}MEM\_ADDR\textbf{)}}\par
Returns whatever the value stored in the \code{MEM\_ADDR} of the Scratchpad Memory.\par
Address mirroring, illegal access, etc. are entirely up to the virtual machine which the BASIC interpreter is running on.
\subsection{POKE}
\codeline{\textbf{POKE(}MEM\_ADDR \textbf{,} BYTE\textbf{)}}\par
Puts a \code{BYTE} into the \code{MEM\_ADDR} of the Scratchpad Memory.
\section{Higher-order Function}
\subsection{DO}
\codeline{\textbf{DO(}EXPR0 [\textbf{;} EXPR1]\ldots\textbf{)}}\par
Executes \code{EXPRn}s sequentially.
\subsection{FILTER}
\codeline{NEWLIST \textbf{= FILTER(}FUNCTION \textbf{,} ITERABLE\textbf{)}}\par
Returns an array of values from the \code{ITERABLE} that passes the given function. i.e. values that makes \code{FUNCTION(VALUE\_FROM\_ITERABLE)} true.
\subsubsection*{Parameters}
\begin{itemlist}
\item \code{FUNCTION} is a user-defined function with single parameter.
\item \code{ITERABLE} is either an array or a generator.
\end{itemlist}
\subsection{FOLD}
\codeline{NEWVALUE \textbf{= FOLD(}FUNCTION \textbf{,} INIT\_VALUE \textbf{,} ITERABLE\textbf{)}}\par
Iteratively applies given function with accumulator and the value from the \code{ITERABLE}, returning the final accumulator. Accumulator will be set to \code{INIT\_VALUE} before iterating over the iterable. In the first execution, the accumulator will be set to \code{ACC=FUNCTION(ACC,ITERABLE(0))}, and the execution will continue to remaining values within the iterable until all values are consumed. The \code{ITERABLE} will not be modified after the execution.
\subsubsection*{Parameters}
\begin{itemlist}
\item \code{FUNCTION} is a user-defined function with two parameters: first parameter being accumulator and second being a value.
\end{itemlist}
\subsection{MAP}
\codeline{NEWLIST \textbf{= MAP(}FUNCTION \textbf{,} ITERABLE\textbf{)}}\par
Applies given function onto the every element in the iterable, and returns an array that contains such items. i.e. returns tranformation of \code{ITERABLE} of which the transformation is \code{FUNCTION}. The \code{ITERABLE} will not be modified after the execution.
\subsubsection*{Parameters}
\begin{itemlist}
\item \code{FUNCTION} is a user-defined function with single parameter.
\end{itemlist}

View File

@@ -0,0 +1,123 @@
\label{implementation}
This chapter explains implementation details of \tbas\ running on \thismachine.
\section{Keycodes}
This is a keycodes recognised by LibGDX, a framework that \thismachine\ runs on.
\begin{longtable}{*{2}{m{\textwidth}}}\hline
\endfirsthead
\endhead
\endfoot
\hline
\endlastfoot
\centering
\begin{tabulary}{\textwidth}{rl}
Key & Code \\
\hline
\ttfamily{1} & 8 \\
\ttfamily{2} & 9 \\
\ttfamily{3} & 10 \\
\ttfamily{4} & 11 \\
\ttfamily{5} & 12 \\
\ttfamily{6} & 13 \\
\ttfamily{7} & 14 \\
\ttfamily{8} & 15 \\
\ttfamily{9} & 16 \\
\ttfamily{0} & 17 \\
$\hookleftarrow$ & 66 \\
\condensedfont{BkSp} & 67 \\
\condensedfont{Tab} & 61 \\
\ttfamily{`} & 68 \\
\ttfamily{'} & 75 \\
\ttfamily{;} & 43 \\
\ttfamily{,} & 55 \\
\ttfamily{.} & 56 \\
\ttfamily{/} & 76 \\
\ttfamily{[}\hspace*{0.083em} & 71 \\
\ttfamily{]}\hspace*{-0.083em} & 72 \\
\ttfamily{-} & 69 \\
\end{tabulary}
\begin{tabulary}{\textwidth}{rl}
Key & Code \\
\hline
\ttfamily{+} & 70 \\
\ttfamily{A} & 29 \\
\ttfamily{B} & 30 \\
\ttfamily{C} & 31 \\
\ttfamily{D} & 32\\
\ttfamily{E} & 33 \\
\ttfamily{F} & 34 \\
\ttfamily{G} & 35 \\
\ttfamily{H} & 36 \\
\ttfamily{I} & 37 \\
\ttfamily{J} & 38 \\
\ttfamily{K} & 39 \\
\ttfamily{L} & 40 \\
\ttfamily{M} & 41 \\
\ttfamily{N} & 42 \\
\ttfamily{O} & 43 \\
\ttfamily{P} & 44 \\
\ttfamily{Q} & 45 \\
\ttfamily{R} & 46 \\
\ttfamily{S} & 47 \\
\ttfamily{T} & 48 \\
\ttfamily{U} & 49 \\
\end{tabulary}
\begin{tabulary}{\textwidth}{rl}
Key & Code \\
\hline
\ttfamily{V} & 50 \\
\ttfamily{W} & 51 \\
\ttfamily{X} & 52 \\
\ttfamily{Y} & 53 \\
\ttfamily{Z} & 54 \\
\condensedfont{LCtrl} & 57 \\
\condensedfont{RCtrl} & 58 \\
\condensedfont{LShift} & 59 \\
\condensedfont{RShift} & 60 \\
\condensedfont{LAlt} & 129 \\
\condensedfont{RAlt} & 130 \\
$\uparrow$ & 19 \\
$\downarrow$ & 20 \\
$\leftarrow$ & 21 \\
$\rightarrow$ & 22 \\
\condensedfont{Ins} & 133 \\
\condensedfont{Del} & 112 \\
\condensedfont{PgUp} & 92 \\
\condensedfont{PgDn} & 93 \\
\condensedfont{Home} & 3 \\
\condensedfont{End} & 132 \\
F1 & 244 \\
\end{tabulary}
\begin{tabulary}{\textwidth}{rl}
Key & Code \\
\hline
F2 & 245 \\
F3 & 246 \\
F4 & 247 \\
F5 & 248 \\
F6 & 249 \\
F7 & 250 \\
F8 & 251 \\
F9 & 252 \\
F10 & 253 \\
F11 & 254 \\
\condensedfont{Num} \ttfamily{0} & 144 \\
\condensedfont{Num} \ttfamily{1} & 145 \\
\condensedfont{Num} \ttfamily{2} & 146 \\
\condensedfont{Num} \ttfamily{3} & 147 \\
\condensedfont{Num} \ttfamily{4} & 148 \\
\condensedfont{Num} \ttfamily{5} & 149 \\
\condensedfont{Num} \ttfamily{6} & 150 \\
\condensedfont{Num} \ttfamily{7} & 151 \\
\condensedfont{Num} \ttfamily{8} & 152 \\
\condensedfont{Num} \ttfamily{9} & 153 \\
\condensedfont{NumLk} & 78 \\
\ttfamily{*} & 17 \\
\end{tabulary}
\end{longtable}
Keys not listed on the table may not be available depending on the system, for example, F12 may not be recognised.

View File

@@ -0,0 +1,7 @@
\tbas\ is a BASIC dialect and its interpreter. \tbas\ emulates most of the common BASIC syntax while adds more advanced and up-to-date concepts gracefully, such as user-defined function that can \emph{actually} recurse, arbitrary list construction using CONS-operator and some of the features in the realm of functional programming from \code{MAP} and \code{FOLD} to \code{CURRY}-ing a function.
This is the documentation for \tbas\ \tbasver.
\vfill
\small\emph{Henceforward this documentation will use more friendly and sometimes vulgar language because that's more fun to write!}

View File

@@ -0,0 +1,99 @@
\quad
\chapterprecishere{``Begin at the beginning'', the King said gravely, ``and go on till you come to the end: then stop.''\par\raggedleft --- \textup{Lewis Carroll, } Alice in Wonderland}
We'll begin at the beginning; how beginning? This:
\begin{lstlisting}
10 PRINT 2+2
run
4
Ok
\end{lstlisting}
Oh \emph{boy} we just did a computation! It printed out \code{4} which is a correct answer for $2+2$ and it didn't crash!
\section[GOTO]{GOTO here and there}
\section[When GOTO Is Bad]{Severe Acute Spaghettification Syndrome}
\section[Subroutine with GOSUB]{GOSUB to the rescue!}
\section[FOR-NEXT Loop]{FOR ever loop NEXT}
\begin{lstlisting}
10 FOR I = 1 TO 20
20 PRINT SPC(20-I);
30 FOR J = 1 TO I*2-1
40 PRINT "*";
50 NEXT:PRINT
60 NEXT
\end{lstlisting}
\section[Get User INPUT]{Isn't It Nice To Have a Computer That Will Question You?}
\section[Recursion]{BRB: Bad Recursion BRB: Bad Recursion BRB: Bad Recursion BRB: Bad RecursionBRB: Bad Recursion BRBRangeError: Maximum call stack size exceeded}
\begin{lstlisting}
10 DEFUN ENDLESS(SHIT)=ENDLESS(SHIT)
20 ENDLESS(1)
\end{lstlisting}
\begin{lstlisting}
10 DEFUN FAC(N)=IF N==0 THEN 1 ELSE N*FAC(N-1)
20 FOR K=1 TO 6
30 PRINT FAC(K)
40 NEXT
\end{lstlisting}
\begin{lstlisting}
10 DEFUN FAC(N)=IF N==0 THEN 1 ELSE N*FAC(N-1)
20 K=MAP FAC, 1 TO 10
30 PRINT K
\end{lstlisting}
\begin{lstlisting}
10 DEFUN FIB(N)=IF N==0 THEN 0 ELSE IF N==1 THEN 1 ELSE FIB(N-1)+FIB(N-2)
20 FOR K=1 TO 12
30 PRINT FIB(K);" ";
40 NEXT
\end{lstlisting}
\section[Currying]{Haskell Curry Wants to Know Your Location}
\label{currying101}
So what the fsck is currying? Consider the following code:
\begin{lstlisting}
10 DEFUN F(K,T)=ABS(T)==K
20 CF=F<~32
30 PRINT CF(24) : REM will print 'false'
40 PRINT CF(-32) : REM will print 'true'
\end{lstlisting}
% NOTE: you can't use \basiccurry within \code{}
Here, \code{CF} is a curried function of \code{F}; built-in operator \code{$<\!\sim$} applies \code{32} to the first parameter of the function \code{F}, which dynamically returns a \emph{function} of \code{CF(T) = ABS(T) == 32}. The fact that Curry Operator returns a \emph{function} opens many possibilities, for example, you can create loads of sibling functions without making loads of duplicate codes.
\section[Wrapping-Up]{The Grand Unification}
Using all the knowledge we have learned, it should be trivial\footnote{/s} to write a Quicksort function in \tbas, like this:
\begin{lstlisting}
10 DEFUN LESS(P,X)=X<P
11 DEFUN GTEQ(P,X)=X>=P
12 DEFUN QSORT(XS)=IF LEN(XS)<1 THEN NIL ELSE
QSORT(FILTER(LESS<~HEAD(XS),TAIL(XS))) # HEAD(XS)!NIL #
QSORT(FILTER(GTEQ<~HEAD(XS),TAIL(XS)))
100 L=7!9!4!5!2!3!1!8!6!NIL
110 PRINT L
120 PRINT QSORT(L)
\end{lstlisting}
Line 12 implements quicksort algorithm, using \code{LESS} and \code{GTEQ} as helper functions. \code{LESS} is a user-function version of less-than operator, and \code{GTEQ} is similar. \code{QSORT} selects a pivot by taking the head-element of the array \code{XS}\footnote{stands for \emph{X's}} with \code{HEAD(XS)}, then utilises curried version of \code{LESS} and \code{GTEQ} to move lesser-than-pivot values to the left and greater to the right (the head element itself does not get recursed, here \code{TAIL(XS)} is applied to make head-less copy of the array), and these two separated \emph{chunks} are recursively sorted using the same \code{QSORT} function. Currying is exploited to give comparison functions a pivot-value to compare against, and also because \code{FILTER} wants a \emph{function} and not an \emph{expression}. \code{HEAD(XS)!NIL} creates a single-element array contains head-element of the \code{XS}.
%Uncomment this if you finally decided to support a closure%
%% Using \emph{closure}, the definition of \code{QSORT} can truly be a one-liner and be \emph{even more cryptic}:
%%
%% \begin{lstlisting}
%% 10 QSORT=[XS]~>IF LEN(XS)<1 THEN NIL ELSE
%% QSORT(FILTER([K]~>K<HEAD XS,TAIL XS)) # HEAD(XS)!NIL #
%% QSORT(FILTER([K]~>K>=HEAD XS,TAIL XS))
%% \end{lstlisting}

View File

@@ -0,0 +1,258 @@
\newcommand{\intrange}{\hl{[$0..2^{53}-1$]}}
This chapter describes the \tbas\ language.
\section{Metasyntax}
In the descriptions of BASIC syntax, these conventions apply.
\begin{itemlist}
\item \codebf{VERBATIM} --- Type exactly as shown
\item \code{IDENTIFIER} --- Replace \emph{identifier} with appropriate metavariable
\item \code{[a]} --- Words within square brackets are optional
\item \code{\{a|b\}} --- Choose either \code{a} or \code{b}
\item \code{[a|b]} --- Optional version of above
\item \code{a\ldots} --- The preceding entity can be repeated
\end{itemlist}
\section{Definitions}
A \emph{Program Line} consists of a line number followed by a \emph{Statements}. Program Lines are terminated by a line break or by the end-of-the-file.
A \emph{Line Number} is an integer within the range of \intrange.
A \emph{Statement} is special form of code which has special meaning. A program line can be composed of 1 or more statements, separated by colons. For the details of statements available in \tbas , see \ref{statements}.
\codeline{STATEMENT [: STATEMENT]\ldots}
An \emph{Expression} is rather normal program lines, e.g. mathematical equations and function calles. The expression takes one of the following forms. For the details of functions available in \tbas , see \ref{functions}.
\codeline{VARIABLE\_OR\_FUNCTION}\\
\codeline{( EXPRESSION )}\\
\codeline{\textbf{IF} EXPRESSION \textbf{THEN} EXPRESSION [\textbf{ELSE} EXPRESSION]}\\
\codeline{FUNCTION \textbf{(} [EXPRESSION \{\textbf{,}|\textbf{;}\} [\{\textbf{,}|\textbf{;}\}]] \textbf{)}}\\
\codeline{FUNCTION [EXPRESSION \{\textbf{,}|\textbf{;}\} [\{\textbf{,}|\textbf{;}\}]]}\\
\codeline{EXPRESSION BINARY\_OPERATOR EXPRESSION}\\
\codeline{UNARY\_OPERATOR EXPRESSION}
An \emph{Array} takes following form:
\codeline{ARRAY\_NAME \textbf{(} EXPRESSION [\textbf{,} EXPRESSION]\ldots\ \textbf{)}}
\section{Literals}
\subsection{String Literals}
String literals take the following form:
\codeline{\textbf{"} [CHARACTERS] \textbf{"}}
where \code{CHARACTERS} is a 0 or more repetition of ASCII-printable letters.\footnote{In other words, \code{0x20..0x7E}}
To print out graphical letters outside of ASCII-printable, use string concatenation with \code{CHR} function, or use \code{EMIT} function.
\subsection{Numeric Literals}
Numeric literals take one of the following forms:
\codeline{[\textbf{+}|\textbf{-}][\textbf{0}|\textbf{1}|\textbf{2}|\textbf{3}|\textbf{4}|\textbf{5}|\textbf{6}|\textbf{7}|\textbf{8}|\textbf{9}]\ldots\ [\textbf{.}][\textbf{0}|\textbf{1}|\textbf{2}|\textbf{3}|\textbf{4}|\textbf{5}|\textbf{6}|\textbf{7}|\textbf{8}|\textbf{9}]\ldots}\\
\codeline{\textbf{0}\{\textbf{x}|\textbf{X}\}[\textbf{0}|\textbf{1}|\textbf{2}|\textbf{3}|\textbf{4}|\textbf{5}|\textbf{6}|\textbf{7}|\textbf{8}|\textbf{9}]\ldots}\\
\codeline{\textbf{0}\{\textbf{b}|\textbf{B}\}[\textbf{0}|\textbf{1}|\textbf{2}|\textbf{3}|\textbf{4}|\textbf{5}|\textbf{6}|\textbf{7}|\textbf{8}|\textbf{9}]\ldots}
Hexadecimal and binary literals are always interpreted as \emph{unsigned} integers. They must range between \intrange.
\subsection{Variables}
Variable names must start with a letter and all characters of the name must be letters \code{A-Z}, figures \code{0-9}. Variable names must not be identical to reserved words, but may \emph{contain} one. Variable names are case-insensitive.
Unlike conventional BASIC dialects (especially GW-BASIC), name pool of variables are shared between all the types. For example, if you have a numeric variable \code{A}, and define an array named \code{A} later in the program, the new array will overwrite your numeric \code{A}.
Furthermore, \emph{sigils} are not used in the \tbas\ and attempting to use one will raise syntax-error or undefined behaviour.
\subsection{Types}
Types of data recognised by \tbas\ are distinguished by some arcane magic of Javascript auto-casing mumbo-jumbo
\begin{tabulary}{\textwidth}{rCL}
Type & Range & Precision \\
\hline
String & As many as the machine can handle & \, \\
Integer & $ \pm 2^{53}-1 $ & exact within the range \\
Float & $ \pm 4.9406564584124654 \times 10^{-324} $ -- $ \pm 1.7976931348623157 \times 10^{308} $ & about 16 significant figures \\
\end{tabulary}
\section{Operators}
\subsection{Order of Precedence}
The order of precedence of the operators is as shown below, lower numbers means they have higher precedence (more tightly bound)
\begin{longtable}{*{2}{m{\textwidth}}}\hline
\endfirsthead
\endhead
\endfoot
\hline
\endlastfoot
\centering
\begin{tabulary}{\textwidth}{cCc}
Order & Op & Associativity \\
\hline
1 & \basicexp & Right \\
2 & \ast\quad$/$\quad$\backslash$ & Left \\
3 & \condensedfont{MOD} & Left \\
4 & $+$\quad$-$ & Left \\
5 & \condensedfont{NOT}\quad\condensedfont{BNOT} & Left \\
6 & <\!<\quad>\!> & Left \\
7 & <\enskip>\enskip=\!<\enskip<\!=\enskip=\!>\enskip>\!= & Left \\
8 & ==\quad<\!>\quad>\!< & Left \\
9 & \condensedfont{MIN}\quad\condensedfont{MAX} & Left \\
10 & \condensedfont{BAND} & Left \\
\end{tabulary}
\begin{tabulary}{\textwidth}{cCc}
Order & Op & Associativity \\
\hline
11 & \condensedfont{BXOR} & Left \\
12 & \condensedfont{BOR} & Left \\
13 & \condensedfont{AND} & Left \\
14 & \condensedfont{OR} & Left \\
15 & \condensedfont{TO}\quad\condensedfont{STEP} & Left \\
16 & ! & Right \\
17 & \sim & Left\\
18 & \# & Left \\
19 & \basiccurry & Right \\
20 & = & Right \\
\end{tabulary}
\end{longtable}
\subsubsection*{Examples}
\begin{itemlist}
\item Exponentiation is more tightly bound than negation: \code{-1\basicexp 2 == -(1\basicexp 2) == -1} but \code{(-1)\basicexp 2 == 1}
\item Exponentiation is right-associative: \code{4\basicexp 3\basicexp 2 == 4\basicexp (3\basicexp 2) == 262144}. This behaviour is \emph{different} from GW-BASIC in which its exponentiation is left-associative.
\end{itemlist}
\subsection{Mathematical Operators}
Mathematical operators operate on expressions that returns numeric value only, except for the \code{+} operator which will take the action of string concatenation if either of the operand is non-numeric.
\begin{tabulary}{\textwidth}{clL}
Code & Operation & Result \\
\hline
\emph{x} $=$ \emph{y} & Assignment & Assigns \emph{y} into \emph{x} \\
\emph{x} $\basicexp$ \emph{y} & Exponentiation & \emph{x} raised to the \emph{y}th power \\
\emph{x} $\ast$ \emph{y} & Multiplication & Product of \emph{x} and \emph{y} \\
\emph{x} $/$ \emph{y} & Division & Quotient of \emph{x} and \emph{y} \\
\emph{x} $\backslash$ \emph{y} & Truncated Division & Integer quotient of \emph{x} and \emph{y} \\
\emph{x} \condensedfont{MOD} \emph{y} & Modulo & Integer remainder of \emph{x} and \emph{y} with sign of \emph{x} \\
\emph{x} $+$ \emph{y} & Addition & Sum of \emph{x} and \emph{y} \\
\emph{x} $-$ \emph{y} & Subtraction & Difference of \emph{x} and \emph{y} \\
$+$ \emph{x} & Unary Plus & Value of \emph{x} \\
$-$ \emph{x} & Unary Minus & Negative value of \emph{x} \\
\emph{x} \condensedfont{MIN} \emph{y} & Minimum & Lesser value of two \\
\emph{x} \condensedfont{MAX} \emph{y} & Maximum & Greater value of two \\
\end{tabulary}
\subsubsection*{Notes}
\begin{itemlist}
\item Type conversion rule follows underlying Javascript implementation. In other words, \emph{only the god knows.}
\item The expression \code{0\basicexp 0} will return \code{1}, even though the expression is indeterminant.
\end{itemlist}
\subsubsection*{Errors}
\begin{itemlist}
\item Any expression that results \code{NaN} or \code{Infinity} in Javascript will return some kind of errors, mainly \code{Division by zero}.
\item If \code{\emph{x}<0} and \code{\emph{y}} is not integer, \code{\emph{x}\basicexp\emph{y}} will raise \code{Illegal function call}.
\end{itemlist}
\subsection{Comparison Operators}
Comparison operator can operate on numeric and string operands. String operands will be automatically converted to numeric value if they can be; if one operand is numeric and other is non-numeric string, the former will be converted to string value.
\begin{tabulary}{\textwidth}{clL}
Code & Operation & Result \\
\hline
\emph{x} == \emph{y} & Equal & True if \emph{x} equals \emph{y} \\
\emph{x} <\!> \emph{y} \quad \emph{x} >\!< \emph{y} & Not equal & False if \emph{x} equals \emph{y} \\
\emph{x} < \emph{y} & Less than & True if \emph{x} is less than \emph{y} \\
\emph{x} > \emph{y} & Greater than & True if \emph{x} is greater than \emph{y} \\
\emph{x} <\!= \emph{y} \quad \emph{x} =\!< \emph{y} & Less than or equal & False if \emph{x} is greater than \emph{y} \\
\emph{x} >\!= \emph{y} \quad \emph{x} =\!> \emph{y} & Greater than or equal & False if \emph{x} is less than \emph{y} \\
\end{tabulary}
When comparing strings, the ordering is as follows:
\begin{itemlist}
\item Two strings are equal only when they are of the same length and every codepoint of the first string is identical to that of the second. This includes any whitespace or unprintable characters.
\item Each character position of the string is compared starting from the leftmost character. When a pair of different characters is encountered, the string with the character of lesser codepoint is less than the string with the character of greater codepoint.
\item If the strings are of different length, but equal up to the length of the shorter string, then the shorter string is less than the longer string.
\end{itemlist}
\subsection{Bitwise Operators}
Bitwise operators operate on unsigned integers only. Floating points are truncated\footnote{truncated towards zero} to integers.
\begin{tabulary}{\textwidth}{clL}
Code & Operation & Result \\
\hline
\emph{x} <\!< \emph{y} & Bitwise Shift Left & Shifts entire bits of \emph{x} by \emph{y} \\
\emph{x} >\!> \emph{y} & Bitwise Shift Right & Shift entire bits \emph{x} by \emph{y}, including sign bit \\
\condensedfont{BNOT} \emph{x} & Ones' complement & $-\emph{x}-1$ \\
\emph{x} \condensedfont{BAND} \emph{y} & Bitwise conjunction & Bitwise AND of \emph{x} and \emph{y} \\
\emph{x} \condensedfont{BOR} \emph{y} & Bitwise disjunction & Bitwise OR of \emph{x} and \emph{y} \\
\emph{x} \condensedfont{BXOR} \emph{y} & Bitwise add-with-no-carry & Bitwise XOR of \emph{x} and \emph{y} \\
\end{tabulary}
\subsection{Boolean Operators}
Boolean operators operate on boolean values. If one of the operand is not boolean, it will be cast to appropriate boolean value. See \ref{valuesandtypes} for casting rules.
\begin{tabulary}{\textwidth}{clL}
Code & Operation & Result \\
\hline
\condensedfont{NOT} \emph{x} & Logical negation & True if \emph{x} is false and vice versa \\
\emph{x} \condensedfont{AND} \emph{y} & Bitwise conjunction & True if \emph{x} and \emph{y} are both true \\
\emph{x} \condensedfont{OR} \emph{y} & Bitwise disjunction & True if \emph{x} or \emph{y} is true, or both are true \\
\end{tabulary}
\subsection{Generator Operators}
Generator operators operate on numeric values and generators to create and modify a generator.
\begin{tabulary}{\textwidth}{clL}
Code & Result \\
\hline
\emph{x} \condensedfont{TO} \emph{y} & Creates an generator that counts from \emph{x} to \emph{y} \\
\emph{x} \condensedfont{STEP} \emph{y} & Modifies an counting stride of the generator \emph{x} into \emph{y} \\
\end{tabulary}
\subsection{Array Operators}
Array operators operate on arrays and numeric values.
\begin{tabulary}{\textwidth}{clL}
Code & Operation & Result \\
\hline
\emph{x} $!$ \emph{y} & Cons & Prepends a value of \emph{x} into an array of \emph{y} \\
\emph{x} $\sim$ \emph{y} & Push & Appends a value of \emph{y} into an array of \emph{x} \\
\emph{x} $\#$ \emph{y} & Concat & Concatenates two arrays \\
\end{tabulary}
Arbitrary arrays can be constructed using empty-array constant \codebf{NIL}.
\subsection{Function Operators}
Function operators operate on functions and some values.
\begin{tabulary}{\textwidth}{clL}
Code & Operation & Result \\
\hline
\emph{f} \basiccurry\ \emph{x} & Curry & Apply \emph{x} into the first parameter of the function \emph{f} \\
%{[}\emph{x},\,\emph{y}\ldots{]} \basicclosure\ \emph{e} & Closure & Creates a closure (anonymous function) from one or more parameters \emph{x},\,\emph{y}\ldots\ and an expression \emph{e} \\
\end{tabulary}
\emph{Currying} is an operation that returns new function that has given value applied to the original function's first parameter. See \ref{currying101} for tutorials.
\section{Syntax In EBNF}
If you're \emph{that} into the language theory of computer science, texts above are just waste of bytes/inks/pixel-spaces/whatever; this little section should be more than enough!
\verbatiminput{syntax.txt}

View File

@@ -0,0 +1,47 @@
\label{statements}
A Program line is composed of a line number and one or more statements. Multiple statements are separated by colons \code{:}.
\section{IF}
\codeline{\textbf{IF} TRUTH\_VALUE \textbf{THEN} TRUE\_EXPRESSION [\textbf{ELSE} FALSE\_EXPERSSION]}
If \code{TRUTH\_VALUE} is truthy, executes \code{TRUE\_EXPRESSION}. If \code{TRUTH\_VALUE} is falsy and \code{FALSE\_EXPERSSION} is specified, executes that expression; otherwise next line or next statement will be executed.
\subsubsection*{Notes}
\begin{itemlist}
\item \codebf{IF} is both statement and expression. You can use IF-clause after \codebf{ELSE}, or within functions as well, for example.
\item \codebf{THEN} is \emph{not} optional, this behaviour is different from most of the BASIC dialects.
\item Also unlike the most dialects, \codebf{GOTO} cannot be omitted; doing so will make the number be returned to its parent expression.
\end{itemlist}
\section{ON}
\codeline{\textbf{ON} INDEX\_EXPRESSION \{\textbf{GOTO}|\textbf{GOSUB}\} LINE0 [\textbf{,} LINE1]\ldots}
Jumps to the line number returned by the \code{INDEX\_EXPRESSION}. If the result is outside of range of the arguments, no jump will be performed.
\subsubsection*{Parameters}
\begin{itemlist}
\item \code{LINEn} can be a number, numeric expression (aka equations) or a line label.
\item When \code{OPTIONBASE 1} is used within the program, \code{LINEn} starts from 1 instead of 0.
\end{itemlist}
\section{DEFUN}
\emph{There it is, the} DEFUN. \emph{All those new-fangled parser\footnote{a computer program that translates program code entered by you into some data bits that only it can understand} and paradigms\footnote{a guidance to in which way you must think to assimilate your brain into the computer-overlord} are tied to this very statement on \tbas, and only Wally knows its secrets\ldots}
\codeline{\textbf{DEFUN} NAME \textbf{(} [ARGS0 [\textbf{,} ARGS1]\ldots] \textbf{)} \textbf{=} EXPRESSION }
With the aid of other statements\footnote{Actually, only the IF is useful, unless you want to \emph{transcend} from the \emph{dung} of mortality by using DEFUN within DEFUN (a little modification of the source code is required)} and functions, DEFUN will allow you to ascend from traditional BASIC and do godly things such as \emph{recursion}\footnote{see recursion} and \emph{functional programming}.
Oh, and you can define your own function, in traditional \code{DEF FN} sense.
\subsubsection*{Parameters}
\begin{itemlist}
\item \code{NAME} must be a valid variable name.
\item \code{ARGSn} must be valid variable names, but can be a name of variables already used within the BASIC program; their value will not be affected nor be used.
\end{itemlist}

View File

@@ -0,0 +1,94 @@
(* quick reference to EBNF *)
(* { word } = word is repeated 0 or more times *)
(* [ word ] = word is optional (repeated 0 or 1 times) *)
line =
linenumber , stmt , {":" , stmt}
| linenumber , "REM" , ? basically anything ? ;
linenumber = digits ;
stmt =
"REM" , ? anything ?
| "IF" , expr_sans_asgn , "THEN" , stmt , ["ELSE" , stmt]
| "DEFUN" , [ident] , "(" , [ident , {" , " , ident}] , ")" , "=" , expr
| "ON" , expr_sans_asgn , ("GOTO" | "GOSUB") , expr_sans_asgn , {"," , expr_sans_asgn}
| "(" , stmt , ")"
| expr ; (* if the statement is 'lit' and contains only one word, treat it as function_call
e.g. NEXT for FOR loop *)
expr = (* this basically blocks some funny attemps such as using DEFUN as anon function
because everything is global in BASIC *)
lit
| "(" , expr , ")"
| "IF" , expr_sans_asgn , "THEN" , expr , ["ELSE" , expr]
| kywd , expr - "(" (* also deals with FOR statement *)
(* at this point, if OP is found in paren-level 0, skip function_call *)
| function_call
| expr , op , expr
| op_uni , expr ;
expr_sans_asgn = ? identical to expr except errors out whenever "=" is found ? ;
function_call =
ident , "(" , [expr , {argsep , expr} , [argsep]] , ")"
| ident , expr , {argsep , expr} , [argsep] ;
kywd = ? words that exists on the list of predefined function that are not operators ? ;
(* don't bother looking at these, because you already know the stuff *)
argsep = "," | ";" ;
ident = alph , [digits] ; (* variable and function names *)
lit = alph , [digits] | num | string ; (* ident + numbers and string literals *)
op = "^" | "*" | "/" | "MOD" | "+" | "-" | "<<" | ">>" | "<" | ">" | "<="
| "=<" | ">=" | "=>" | "==" | "<>" | "><" | "BAND" | "BXOR" | "BOR"
| "AND" | "OR" | "TO" | "STEP" | "!" | "~" | "#" | "=" ;
op_uni = "-" | "+" ;
alph = letter | letter , alph ;
digits = digit | digit , digits ;
hexdigits = hexdigit | hexdigit , hexdigits ;
bindigits = bindigit | bindigit , bindigits ;
num = digits | digits , "." , [digits] | "." , digits
| ("0x"|"0X") , hexdigits
| ("0b"|"0B") , bindigits ; (* sorry, no e-notation! *)
visible = ? ASCII 0x20 to 0x7E ? ;
string = '"' , (visible | visible , stringlit) , '"' ;
letter = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N"
| "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b"
| "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p"
| "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "_" ;
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
hexdigit = "A" | "B" | "C" | "D" | "E" | "F" | "a" | "b" | "c" | "d" | "e" | "f" | "0" | "1"
| "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
bindigit = "0" | "1" ;
(* all possible token states: lit num op bool qot paren sep *)
(* below are schematic of trees generated after parsing the statements *)
IF (type: function, value: IF)
1. cond
2. true
[3. false]
FOR (type: function, value: FOR)
1. expr (normally (=) but not necessarily)
DEFUN (type: function, value: DEFUN)
1. funcname
1. arg0
[2. arg1]
[3. argN...]
2. stmt
ON (type: function, value: ON)
1. testvalue
2. functionname (type: lit)
3. arg0
[4. arg1]
[5. argN...]
FUNCTION_CALL (type: function, value: PRINT or something)
1. arg0
2. arg1
[3. argN...]

View File

@@ -0,0 +1,211 @@
% !TEX TS-program = LuaLaTeX
%% Copyright (c) 2020 CuriousTorvald and the contributors.
\documentclass[10pt, stock, openany, chapter]{memoir}
\usepackage{fontspec}
\setmainfont[Ligatures=TeX]{TeX Gyre Heros}
\newfontfamily\condensedfont{TeX Gyre Heros Cn}
\newfontfamily\monofont{TeX Gyre Cursor}
\usepackage{fapapersize}
\usefapapersize{148mm,210mm,15mm,15mm,20mm,15mm} % A5 paper
\usepackage{afterpage}
\usepackage{hyperref}
\usepackage{graphicx}
\usepackage{tabulary}
\usepackage{longtable}
\usepackage[table]{xcolor}
\usepackage{ltablex}
\usepackage{parskip}
\usepackage{multicol}
\usepackage{soul}
\usepackage{verbatim}
\usepackage{etoolbox}
\usepackage[most]{tcolorbox}
\usepackage{listings}
\usepackage{amsmath,amssymb}
\usepackage{lineno} % debug
\makeatletter
\newlength{\mytextsize}
\setlength{\mytextsize}{\f@size pt}
\newlength{\mybaselineskip}
\setlength{\mybaselineskip}{1.3\mytextsize}
\patchcmd{\verbatim@input}{\@verbatim}{\scriptsize\@verbatim}{}{}
\makeatother
\setlength{\baselineskip}{\mybaselineskip}
\frenchspacing
\setlength{\parindent}{0pt}
\setlength{\parskip}{\mytextsize}
\setsecnumdepth{subsection}
%% More compact itemize %%
\newenvironment{itemlist}{\vspace{0pt}\itemize}{\enditemize}
%% Idioms %%
\hyphenation{Java-script}
\hyphenation{ECMA-script}
\newcommand\forceindent{\hskip1.5em}
%% BASIC operators %%
\newcommand\basicexp{\raisebox{0.25ex}{\scriptsize\wedge}}
\newcommand\basiccurry{$<\!\sim$}
\newcommand\basicclosure{$\sim\!>$}
% Title styling
\pretitle{\begin{flushright}}
\posttitle{\par\end{flushright}}
\preauthor{\begin{flushright}}
\postauthor{\par\end{flushright}}
% new sections are new page
%\let\oldsection\chapter
%\renewcommand\chapter{\clearpage\oldsection}
% shorten spaces before section header
\setbeforesubsecskip{\mytextsize}
\setbeforesubsubsecskip{\mytextsize}
% extra space for table
\setlength{\extrarowheight}{0.166ex}
% chapter title -- no now page after
\renewcommand\chapterheadstart{} % kill the drop
\renewcommand\afterchapternum{\vskip 0.5em} % space between number and title
\setlength{\afterchapskip}{\baselineskip} % reduce space after chapter title
\makeatletter
\renewcommand\memendofchapterhook{%
\m@mindentafterchapter\@afterheading}
\makeatother
\definecolor{lgrey}{HTML}{eeeeee}
\sethlcolor{lgrey}
\renewcommand{\thefootnote}{\fnsymbol{footnote}}
\newcommand{\code}[1]{{\monofont\hl{\,#1\,}}}
\newcommand{\codebf}[1]{{\monofont \textbf{\hl{\,#1\,}}}}
%%\newcommand{\codeline}[1]{{\monofont\hl{\,#1\,}}}
\newcommand{\codeline}[1]{%
\colorbox{lgrey}{%
\begin{tabular*}{\textwidth}{l}%
\monofont #1 \\% TODO fill the cell with \hl colour
\end{tabular*}%
}}
\newtcolorbox{lgreybox}[1][]{%
breakable,
enhanced,
colback=lgrey,
attach title to upper,
fontupper=\monofont,
#1
}
\definecolor{sourcecomment}{HTML}{888888}
\lstset{frame=tb,
language=[Visual]Basic,
aboveskip=3mm,
belowskip=3mm,
showstringspaces=false,
columns=flexible,
basicstyle={\small\ttfamily},
numbers=none,
numberstyle=\textbf,
keywordstyle=,
commentstyle=\color{sourcecomment},
stringstyle=\textbf,
breaklines=true,
breakatwhitespace=true,
tabsize=3
}
\addtocontents{toc}{\protect\thispagestyle{empty}} % no page number for the TOC header page
\aliaspagestyle{part}{empty} % aliasing PART as empty so that page number would not be printed
\aliaspagestyle{chapter}{section} % aliasing CHAPTER as section so that page numbering style would be the same as section
% The title
\newcommand{\tbas}{Terran BASIC}
\newcommand{\thismachine}{TSVM}
\newcommand{\tbasver}{1.0}
\newcommand{\theedition}{First Edition}
\newcommand{\oreallypress}{\large\textbf{O'REALLY\raisebox{1ex}{\scriptsize ?}} \normalsize Press}
\title{\HUGE\textbf{\MakeUppercase{\tbas} \\ REFERENCE MANUAL} \\ \Large \vspace{1em} For Language Version \tbasver \\ \vspace{7mm} \theedition}
\date{}
\author{}
\hypersetup{
pdfauthor={CuriousTorvald},
pdftitle={\tbas\ Reference Manual For Language Version \tbasver, \theedition},
unicode=true,
pdfkeywords={BASIC} {Functional Programming} {Improper Hierarchy},
pdfcreator=\oreallypress
}
\begin{document}
\begin{titlingpage}
\maketitle{}
\vfill
\oreallypress
\end{titlingpage}
\setcounter{page}{3}
\tableofcontents*
%\linenumbers % debug
\openright
\chapter{Introduction}
\input{intro}
\openany
\part{Language}
\chapter{Basic Concepts}
\input{concepts}
\chapter{Language Guide}
\input{langguide}
\chapter{Language Reference}
\input{langref}
\chapter{Statements}
\input{statements}
\chapter{Functions}
\input{functions}
\part{Implementation}
\chapter{Technical Reference}
\input{technical}
\chapter{Implementation Details}
\input{Implementation}
\chapter{Bibliography}
\input{bibliography}
\chapter*{Disclaimers}
\oreallypress{} is entirely fictional publishing entity; \oreallypress{} has no affiliation whatsoever with any of the real-world publishers.
\afterpage{\pagestyle{empty}\null\newpage}
\end{document}

View File

@@ -0,0 +1,57 @@
\section{Resolving Variables}
This section describes all use cases of \code{BasicVar}.
When a variable is \code{resolve}d, an object with instance of \code{BasicVar} is returned. A \code{bvType} of Javascript value is determined using \code{JStoBASICtype}.
\begin{tabulary}{\textwidth}{R|LL}
Typical User Input & TYPEOF(\textbf{Q}) & Instanceof \\
\hline
\code{\textbf{Q}=42.195} & {\ttfamily num} & \emph{primitive} \\
\code{\textbf{Q}=42>21} & {\ttfamily boolean} & \emph{primitive} \\
\code{\textbf{Q}="BASIC!"} & {\ttfamily string} & \emph{primitive} \\
\code{\textbf{Q}=DIM(12)} & {\ttfamily array} & Array (JS) \\
\code{\textbf{Q}=1 TO 9 STEP 2} & {\ttfamily generator} & ForGen \\
\code{DEFUN \textbf{Q}(X)=X+3} & {\ttfamily usrdefun} & BasicAST \\
\end{tabulary}
\subsection*{Notes}
\begin{itemlist}
\item \code{TYPEOF(\textbf{Q})} is identical to the variable's bvType; the function simply returns \code{BasicVar.bvType}.
\item Do note that all resolved variables have \code{troType} of \code{Lit}, see next section for more information.
\end{itemlist}
\section{Unresolved Values}
Unresolved variables has JS-object of \code{troType}, with \emph{instanceof} \code{SyntaxTreeReturnObj}. Its properties are defined as follows:
\begin{tabulary}{\textwidth}{RL}
Properties & Description \\
\hline
{\ttfamily troType} & Type of the TRO (Tree Return Object) \\
{\ttfamily troValue} & Value of the TRO \\
{\ttfamily troNextLine} & Pointer to next instruction, array of: [\#line, \#statement] \\
\end{tabulary}
Following table shows which BASIC object can have which \code{troType}:
\begin{tabulary}{\textwidth}{RLL}
BASIC Type & troType \\
\hline
Any Variable & {\ttfamily lit} \\
Boolean & {\ttfamily bool} \\
Generator & {\ttfamily generator} \\
Array & {\ttfamily array} \\
Number & {\ttfamily num} \\
String & {\ttfamily string} \\
DEFUN'd Function & {\ttfamily internal\_lambda} \\
Array Indexing & {\ttfamily internal\_arrindexing\_lazy} \\
Assignment & {\ttfamily internal\_assignment\_object} \\
\end{tabulary}
\subsection*{Notes}
\begin{itemlist}
\item All type that is not \code{lit} only appear when the statement returns such values, e.g. \code{internal\_lambda} only get returned by DEFUN statements as the statement itself returns defined function as well as assign them to given BASIC variable.
\item As all variables will have \code{troType} of \code{lit} when they are not resolved, the property must not be used to determine the type of the variable; you must \code{resolve} it first.
\item The type string \code{function} should not appear outside of TRO and \code{astType}; if you do see them in the wild, please check your JS code because you probably meant \code{usrdefun}.
\end{itemlist}

View File

@@ -0,0 +1,117 @@
digraph g {
concentrate=true;
splines=true;
newrank=true;
overlap=false;
LITERAL [shape=box]
QUOTE [shape=box]
PAREN [shape=box]
SEP [shape=diamond]
OPERATOR [shape=diamond]
OPERATOR2 [shape=diamond] // needs second pass to rename it as "OPERATOR"
NUMBER [shape=diamond]
numbersep [style=filled]
NUMBER2 [shape=diamond] // needs second pass to rename it as "NUMBER"
limbo [style=filled]
escape [style=filled]
quote_end [style=filled]
start [shape=Mdiamond]
error [shape=Msquare,style=filled]
subgraph clusternum {
shape=none;
NUMBER; NUMBER2; numbersep;
}
subgraph clusterops {
shape=none;
OPERATOR; OPERATOR2;
}
start -> LITERAL
LITERAL -> QUOTE [label="\""]
LITERAL -> PAREN [label="()[]"]
LITERAL -> limbo [label=_]
LITERAL -> SEP [label=","]
LITERAL -> OPERATOR [label="^*/+->=<"]
LITERAL -> LITERAL [label=otherwise]
NUMBER -> NUMBER [label="0..9_"]
NUMBER -> numbersep [label="xXbB."]
NUMBER -> QUOTE [label="\""]
NUMBER -> limbo [label=" "]
NUMBER -> PAREN [label="()[]"]
NUMBER -> SEP [label=","]
NUMBER -> OPERATOR [label="^*/+->=<"]
NUMBER -> LITERAL [label=otherwise]
numbersep -> NUMBER2 [label="0..9_"]
numbersep -> error [label=otherwise]
NUMBER2 -> NUMBER2 [label="0..9_"]
NUMBER2 -> QUOTE [label="\""]
NUMBER2 -> limbo [label=" "]
NUMBER2 -> PAREN [label="()[]"]
NUMBER2 -> SEP [label=","]
NUMBER2 -> OPERATOR [label="^*/+->=<"]
NUMBER2 -> LITERAL [label=otherwise]
OPERATOR -> OPERATOR2 [label=">=<"]
OPERATOR -> error [label="^*/+-"]
OPERATOR -> NUMBER [label="0..9"]
OPERATOR -> QUOTE [label="\""]
OPERATOR -> limbo [label=" "]
OPERATOR -> PAREN [label="()[]"]
OPERATOR -> SEP [label=","]
OPERATOR -> LITERAL [label=otherwise]
OPERATOR2 -> error [label="^*/+->=<"]
OPERATOR2 -> NUMBER [label="0..9"]
OPERATOR2 -> QUOTE [label="\""]
OPERATOR2 -> limbo [label=" "]
OPERATOR2 -> PAREN [label="()[]"]
OPERATOR2 -> SEP [label=","]
OPERATOR2 -> LITERAL [label=otherwise]
QUOTE -> quote_end [label="\""]
QUOTE -> escape [label="\\"]
QUOTE -> QUOTE [label=otherwise]
escape -> QUOTE [label=any]
quote_end -> limbo [label=_]
quote_end -> QUOTE [label="\""]
quote_end -> PAREN [label="()[]"]
quote_end -> SEP [label=","]
quote_end -> NUMBER [label="0..9"]
quote_end -> OPERATOR [label="^*/+->=<"]
quote_end -> LITERAL [label=otherwise]
limbo -> limbo [label=_]
limbo -> QUOTE [label="\""]
limbo -> PAREN [label="()[]"]
limbo -> SEP [label=","]
limbo -> NUMBER [label="0..9"]
limbo -> OPERATOR [label="^*/+->=<"]
limbo -> LITERAL [label=otherwise]
PAREN -> limbo [label=_]
PAREN -> QUOTE [label="\""]
PAREN -> PAREN [label="()[]"]
PAREN -> SEP [label=","]
PAREN -> NUMBER [label="0..9"]
PAREN -> OPERATOR [label="^*/+->=<"]
PAREN -> LITERAL [label=otherwise]
SEP -> limbo [label=_]
SEP -> QUOTE [label="\""]
SEP -> PAREN [label="()[]"]
SEP -> SEP [label=","]
SEP -> NUMBER [label="0..9"]
SEP -> OPERATOR [label="^*/+->=<"]
SEP -> LITERAL [label=otherwise]
}

View File

@@ -0,0 +1,848 @@
class ParserError extends Error {
constructor(...args) {
super(...args);
Error.captureStackTrace(this, ParserError);
}
}
var DEFUNS_BUILD_DEFUNS = false;
let bF = {};
bF.parserPrintdbg = any => serial.println(any);
bF.parserPrintdbg2 = function(icon, lnum, tokens, states, recDepth) {
let treeHead = String.fromCharCode(0x2502,32).repeat(recDepth);
bF.parserPrintdbg(`${icon}${lnum} ${treeHead}${tokens.join(' ')}`);
bF.parserPrintdbg(`${icon}${lnum} ${treeHead}${states.join(' ')}`);
}
bF.parserPrintdbgline = function(icon, msg, lnum, recDepth) {
let treeHead = String.fromCharCode(0x2502,32).repeat(recDepth);
bF.parserPrintdbg(`${icon}${lnum} ${treeHead}${msg}`);
}
let lang = {};
lang.syntaxfehler = function(line, reason) {
return Error("Syntax error" + ((line !== undefined) ? (" in "+line) : "") + ((reason !== undefined) ? (": "+reason) : ""));
};
/**
* @return ARRAY of BasicAST
*/
bF._parseTokens = function(lnum, tokens, states) {
bF.parserPrintdbg2('Line ', lnum, tokens, states, 0);
if (tokens.length !== states.length) throw lang.syntaxfehler(lnum);
if (tokens[0].toUpperCase() == "REM" && states[0] != "qot") return;
/*************************************************************************/
let parenDepth = 0;
let parenStart = -1;
let parenEnd = -1;
let seps = [];
// scan for parens and (:)s
for (let k = 0; k < tokens.length; k++) {
// increase paren depth and mark paren start position
if (tokens[k] == "(" && states[k] != "qot") {
parenDepth += 1;
if (parenStart == -1 && parenDepth == 1) parenStart = k;
}
// decrease paren depth
else if (tokens[k] == ")" && states[k] != "qot") {
if (parenEnd == -1 && parenDepth == 1) parenEnd = k;
parenDepth -= 1;
}
if (parenDepth == 0 && tokens[k] == ":" && states[k] == "seq")
seps.push(k);
}
let startPos = [0].concat(seps.map(k => k+1));
let stmtPos = startPos.map((s,i) => {return{start:s, end:(seps[i] || tokens.length)}}); // use end of token position as separator position
return stmtPos.map((x,i) => {
if (stmtPos.length > 1)
bF.parserPrintdbgline('Line ', 'Statement #'+(i+1), lnum, 0);
// check for empty tokens
if (x.end - x.start <= 0) throw new ParserError("Malformed Line");
let tree = bF._parseStmt(lnum,
tokens.slice(x.start, x.end),
states.slice(x.start, x.end),
1
);
bF.parserPrintdbgline('Tree in ', '\n'+astToString(tree), lnum, 0);
return tree;
});
}
/** Parses following EBNF rule:
stmt =
"REM" , ? anything ?
| "IF" , expr_sans_asgn , "THEN" , stmt , ["ELSE" , stmt]
| "DEFUN" , [ident] , "(" , [ident , {" , " , ident}] , ")" , "=" , expr
| "ON" , expr_sans_asgn , ident , expr_sans_asgn , {"," , expr_sans_asgn}
| "(" , stmt , ")"
| expr ; (* if the statement is 'lit' and contains only one word, treat it as function_call e.g. NEXT for FOR loop *)
* @return: BasicAST
*/
bF._parseStmt = function(lnum, tokens, states, recDepth) {
bF.parserPrintdbg2('$', lnum, tokens, states, recDepth);
/*************************************************************************/
// case for: single word (e.g. NEXT for FOR loop)
if (tokens.length == 1 && states.length == 1) {
bF.parserPrintdbgline('$', "Single Word Function Call", lnum, recDepth);
return bF._parseLit(lnum, tokens, states, recDepth + 1, true);
}
/*************************************************************************/
let headTkn = tokens[0].toUpperCase();
let headSta = states[0];
let treeHead = new BasicAST();
treeHead.astLnum = lnum;
/*************************************************************************/
// case for: "REM" , ? anything ?
if (headTkn == "REM" && headSta != "qot") return;
/*************************************************************************/
let parenDepth = 0;
let parenStart = -1;
let parenEnd = -1;
let onGoPos = -1;
let sepsZero = [];
let sepsOne = [];
// scan for parens that will be used for several rules
// also find nearest THEN and ELSE but also take parens into account
for (let k = 0; k < tokens.length; k++) {
// increase paren depth and mark paren start position
if (tokens[k] == "(" && states[k] != "qot") {
parenDepth += 1;
if (parenStart == -1 && parenDepth == 1) parenStart = k;
}
// decrease paren depth
else if (tokens[k] == ")" && states[k] != "qot") {
if (parenEnd == -1 && parenDepth == 1) parenEnd = k;
parenDepth -= 1;
}
if (parenDepth == 0 && states[k] == "sep")
sepsZero.push(k);
if (parenDepth == 1 && states[k] == "sep")
sepsOne.push(k);
if (parenDepth == 0) {
let tok = tokens[k].toUpperCase();
if (-1 == onGoPos && ("GOTO" == tok || "GOSUB" == tok) && "lit" == states[k])
onGoPos = k;
}
}
// unmatched brackets, duh!
if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets);
/*************************************************************************/
// ## case for:
// "IF" , expr_sans_asgn , "THEN" , stmt , ["ELSE" , stmt]
try {
bF.parserPrintdbgline('$', "Trying IF Statement...", lnum, recDepth);
return bF._parseIfMode(lnum, tokens, states, recDepth + 1, false);
}
// if ParserError is raised, continue to apply other rules
catch (e) {
bF.parserPrintdbgline('$', 'It was NOT!', lnum, recDepth);
if (!(e instanceof ParserError)) throw e;
}
/*************************************************************************/
// ## case for:
// | "DEFUN" , [ident] , "(" , [ident , {" , " , ident}] , ")" , "=" , expr
if ("DEFUN" == headTkn && "lit" == headSta &&
parenStart == 2 && tokens[parenEnd + 1] == "=" && states[parenEnd + 1] == "op"
) {
bF.parserPrintdbgline('$', 'DEFUN Stmt', lnum, recDepth);
treeHead.astValue = "DEFUN";
treeHead.astType = "function";
// parse function name
if (tokens[1] == "(") {
// anonymous function
treeHead.astLeaves[0] = new BasicAST();
treeHead.astLeaves[0].astLnum = lnum;
treeHead.astLeaves[0].astType = "lit";
}
else {
bF.parserPrintdbgline('$', 'DEFUN Stmt Function Name:', lnum, recDepth);
treeHead.astLeaves[0] = bF._parseIdent(lnum, [tokens[1]], [states[1]], recDepth + 1);
}
// parse function arguments
bF.parserPrintdbgline('$', 'DEFUN Stmt Function Arguments -- ', lnum, recDepth);
let defunArgDeclSeps = sepsOne.filter((i) => i < parenEnd + 1).map(i => i-1).concat([parenEnd - 1]);
bF.parserPrintdbgline('$', 'DEFUN Stmt Function Arguments comma position: '+defunArgDeclSeps, lnum, recDepth);
treeHead.astLeaves[0].astLeaves = defunArgDeclSeps.map(i=>bF._parseIdent(lnum, [tokens[i]], [states[i]], recDepth + 1));
// parse function body
let parseFunction = (DEFUNS_BUILD_DEFUNS) ? bF._parseStmt : bF._parseExpr;
treeHead.astLeaves[1] = parseFunction(lnum,
tokens.slice(parenEnd + 2, tokens.length),
states.slice(parenEnd + 2, states.length),
recDepth + 1
);
return treeHead;
}
/*************************************************************************/
// ## case for:
// | "ON" , if_equation , ident , if_equation , {"," , if_equation}
if ("ON" == headTkn && "lit" == headSta) {
bF.parserPrintdbgline('$', 'ON Stmt', lnum, recDepth);
if (onGoPos == -1) throw ParserError("Malformed ON Statement");
treeHead.astValue = "ON";
treeHead.astType = "function";
// parse testvalue
let testvalue = bF._parseExpr(lnum,
tokens.slice(1, onGoPos),
states.slice(1, onGoPos),
recDepth + 1,
true
);
// parse functionname
let functionname = bF._parseExpr(lnum,
[tokens[onGoPos]],
[states[onGoPos]],
recDepth + 1,
true
);
// parse arguments
// get list of comma but filter ones appear before GOTO/GOSUB
let onArgSeps = sepsZero.filter(i => (i > onGoPos));
let onArgStartPos = [onGoPos + 1].concat(onArgSeps.map(k => k + 1));
let onArgPos = onArgStartPos.map((s,i) => {return{start:s, end: (onArgSeps[i] || tokens.length)}}); // use end of token position as separator position
// recursively parse expressions
treeHead.astLeaves = [testvalue, functionname].concat(onArgPos.map((x,i) => {
bF.parserPrintdbgline('$', 'ON GOTO/GOSUB Arguments #'+(i+1), lnum, recDepth);
// check for empty tokens
if (x.end - x.start <= 0) throw new ParserError("Malformed ON arguments");
return bF._parseExpr(lnum,
tokens.slice(x.start, x.end),
states.slice(x.start, x.end),
recDepth + 1,
true
);
}));
return treeHead;
}
/*************************************************************************/
// ## case for:
// | "(" , stmt , ")"
if (parenStart == 0 && parenEnd == tokens.length - 1) {
bF.parserPrintdbgline('$', '( Stmt )', lnum, recDepth);
return bF._parseStmt(lnum,
tokens.slice(parenStart + 1, parenEnd),
states.slice(parenStart + 1, parenEnd),
recDepth + 1
);
}
/*************************************************************************/
// ## case for:
// | expr ;
try {
bF.parserPrintdbgline('$', 'Trying Expression Call...', lnum, recDepth);
return bF._parseExpr(lnum, tokens, states, recDepth + 1);
}
catch (e) {
bF.parserPrintdbgline('$', 'Error!', lnum, recDepth);
throw new ParserError("Statement cannot be parsed in "+lnum+": "+e.stack);
}
/*************************************************************************/
throw new ParserError("Statement cannot be parsed in "+lnum);
} // END of STMT
/** Parses following EBNF rule:
expr = (* this basically blocks some funny attemps such as using DEFUN as anon function because everything is global in BASIC *)
lit
| "(" , expr , ")"
| "IF" , expr_sans_asgn , "THEN" , expr , ["ELSE" , expr]
| kywd , expr (* also deals with FOR statement; kywd = ? words that exists on the list of predefined function that are not operators ? ; *)
| function_call
| expr , op , expr
| op_uni , expr ;
* @return: BasicAST
*/
bF._parseExpr = function(lnum, tokens, states, recDepth, ifMode) {
bF.parserPrintdbg2('E', lnum, tokens, states, recDepth);
/*************************************************************************/
// ## special case for virtual dummy element (e.g. phantom element on "PRINT SPC(20);")
if (tokens[0] === undefined && states[0] === undefined) {
let treeHead = new BasicAST();
treeHead.astLnum = lnum;
treeHead.astValue = undefined;
treeHead.astType = "null";
return treeHead;
}
/*************************************************************************/
let headTkn = tokens[0].toUpperCase();
let headSta = states[0];
/*************************************************************************/
// ## case for:
// lit
if (!bF._EquationIllegalTokens.includes(headTkn) && tokens.length == 1) {
bF.parserPrintdbgline('E', 'Literal Call', lnum, recDepth);
return bF._parseLit(lnum, tokens, states, recDepth + 1);
}
/*************************************************************************/
// scan for operators with highest precedence, use rightmost one if multiple were found
let topmostOp;
let topmostOpPrc = 0;
let operatorPos = -1;
// find and mark position of parentheses
// properly deal with the nested function calls
let parenDepth = 0;
let parenStart = -1;
let parenEnd = -1;
// Scan for unmatched parens and mark off the right operator we must deal with
// every function_call need to re-scan because it is recursively called
for (let k = 0; k < tokens.length; k++) {
// increase paren depth and mark paren start position
if (tokens[k] == "(" && states[k] != "qot") {
parenDepth += 1;
if (parenStart == -1 && parenDepth == 1) parenStart = k;
}
// decrease paren depth
else if (tokens[k] == ")" && states[k] != "qot") {
if (parenEnd == -1 && parenDepth == 1) parenEnd = k;
parenDepth -= 1;
}
// determine the right operator to deal with
if (parenDepth == 0) {
if (states[k] == "op" && bF.isSemanticLiteral(tokens[k-1], states[k-1]) &&
((bF._opPrc[tokens[k].toUpperCase()] > topmostOpPrc) ||
(!bF._opRh[tokens[k].toUpperCase()] && bF._opPrc[tokens[k].toUpperCase()] == topmostOpPrc))
) {
topmostOp = tokens[k].toUpperCase();
topmostOpPrc = bF._opPrc[tokens[k].toUpperCase()];
operatorPos = k;
}
}
}
// unmatched brackets, duh!
if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets);
/*************************************************************************/
// ## case for:
// | "(" , expr , ")"
if (parenStart == 0 && parenEnd == tokens.length - 1) {
bF.parserPrintdbgline('E', '( Expr )', lnum, recDepth);
return bF._parseExpr(lnum,
tokens.slice(parenStart + 1, parenEnd),
states.slice(parenStart + 1, parenEnd),
recDepth + 1
);
}
/*************************************************************************/
// ## case for:
// | "IF" , expr_sans_asgn , "THEN" , expr , ["ELSE" , expr]
try {
bF.parserPrintdbgline('E', "Trying IF Expression...", lnum, recDepth);
return bF._parseIfMode(lnum, tokens, states, recDepth + 1, false);
}
// if ParserError is raised, continue to apply other rules
catch (e) {
bF.parserPrintdbgline('E', 'It was NOT!', lnum, recDepth);
if (!(e instanceof ParserError)) throw e;
}
/*************************************************************************/
// ## case for:
// | kywd , expr (* kywd = ? words that exists on the list of predefined function that are not operators ? ; *)
if (bStatus.builtin[headTkn] && headSta == "lit" && !bF._opPrc[headTkn] &&
states[1] != "paren"
) {
bF.parserPrintdbgline('E', 'Builtin Function Call w/o Paren', lnum, recDepth);
return bF._parseFunctionCall(lnum, tokens, states, recDepth + 1);
}
/*************************************************************************/
// ## case for:
// (* at this point, if OP is found in paren-level 0, skip function_call *)
// | function_call ;
if (topmostOp === undefined) { // don't remove this IF statement!
try {
bF.parserPrintdbgline('E', "Trying Function Call...", lnum, recDepth);
return bF._parseFunctionCall(lnum, tokens, states, recDepth + 1);
}
// if ParserError is raised, continue to apply other rules
catch (e) {
bF.parserPrintdbgline('E', 'It was NOT!', lnum, recDepth);
if (!(e instanceof ParserError)) throw e;
}
}
/*************************************************************************/
// ## case for:
// | expr , op, expr
// | op_uni , expr
// if operator is found, split by the operator and recursively parse the LH and RH
if (topmostOp !== undefined) {
bF.parserPrintdbgline('E', 'Operators', lnum, recDepth);
if (ifMode && topmostOp == "=") throw lang.syntaxfehler(lnum, "'=' used on IF, did you mean '=='?");
if (ifMode && topmostOp == ":") throw lang.syntaxfehler(lnum, "':' used on IF");
// this is the AST we're going to build up and return
// (other IF clauses don't use this)
let treeHead = new BasicAST();
treeHead.astLnum = lnum;
treeHead.astValue = topmostOp;
treeHead.astType = "op";
// BINARY_OP?
if (operatorPos > 0) {
let subtknL = tokens.slice(0, operatorPos);
let substaL = states.slice(0, operatorPos);
let subtknR = tokens.slice(operatorPos + 1, tokens.length);
let substaR = states.slice(operatorPos + 1, tokens.length);
treeHead.astLeaves[0] = bF._parseExpr(lnum, subtknL, substaL, recDepth + 1);
treeHead.astLeaves[1] = bF._parseExpr(lnum, subtknR, substaR, recDepth + 1);
}
else {
if (topmostOp === "-") treeHead.astValue = "UNARYMINUS"
else if (topmostOp === "+") treeHead.astValue = "UNARYPLUS"
else if (topmostOp === "NOT") treeHead.astValue = "UNARYLOGICNOT"
else if (topmostOp === "BNOT") treeHead.astValue = "UNARYBNOT"
else throw new ParserError(`Unknown unary op '${topmostOp}'`)
treeHead.astLeaves[0] = bF._parseExpr(lnum,
tokens.slice(operatorPos + 1, tokens.length),
states.slice(operatorPos + 1, states.length),
recDepth + 1
);
}
return treeHead;
}
/*************************************************************************/
throw new ParserError("Expression cannot be parsed in "+lnum);
} // END of EXPR
/** Parses following EBNF rule:
"IF" , expr_sans_asgn , "THEN" , stmt , ["ELSE" , stmt]
| "IF" , expr_sans_asgn , "THEN" , expr , ["ELSE" , expr]
if exprMode is true, only the latter will be used; former otherwise
* @return: BasicAST
*/
bF._parseIfMode = function(lnum, tokens, states, recDepth, exprMode) {
bF.parserPrintdbg2('/', lnum, tokens, states, recDepth);
/*************************************************************************/
let headTkn = tokens[0].toUpperCase();
let headSta = states[0];
let parseFunction = (exprMode) ? bF._parseExpr : bF._parseStmt
let thenPos = -1;
let elsePos = -1;
let parenDepth = 0;
let parenStart = -1;
let parenEnd = -1;
// scan for parens that will be used for several rules
// also find nearest THEN and ELSE but also take parens into account
for (let k = 0; k < tokens.length; k++) {
// increase paren depth and mark paren start position
if (tokens[k] == "(" && states[k] != "qot") {
parenDepth += 1;
if (parenStart == -1 && parenDepth == 1) parenStart = k;
}
// decrease paren depth
else if (tokens[k] == ")" && states[k] != "qot") {
if (parenEnd == -1 && parenDepth == 1) parenEnd = k;
parenDepth -= 1;
}
if (parenDepth == 0) {
if (-1 == thenPos && "THEN" == tokens[k].toUpperCase() && "lit" == states[k])
thenPos = k;
else if (-1 == elsePos && "ELSE" == tokens[k].toUpperCase() && "lit" == states[k])
elsePos = k;
}
}
// unmatched brackets, duh!
if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets);
let treeHead = new BasicAST();
treeHead.astLnum = lnum;
// ## case for:
// "IF" , expr_sans_asgn , "THEN" , stmt , ["ELSE" , stmt]
if ("IF" == headTkn && "lit" == headSta) {
// "THEN" not found, raise error!
if (thenPos == -1) throw lang.syntaxfehler(lnum, "IF without THEN");
treeHead.astValue = "IF";
treeHead.astType = "function";
treeHead.astLeaves[0] = bF._parseExpr(lnum,
tokens.slice(1, thenPos),
states.slice(1, thenPos),
recDepth + 1,
true // if_equation mode
);
treeHead.astLeaves[1] = parseFunction(lnum,
tokens.slice(thenPos + 1, (elsePos != -1) ? elsePos : tokens.length),
states.slice(thenPos + 1, (elsePos != -1) ? elsePos : tokens.length),
recDepth + 1
);
if (elsePos != -1)
treeHead.astLeaves[2] = parseFunction(lnum,
tokens.slice(elsePos + 1, tokens.length),
states.slice(elsePos + 1, tokens.length),
recDepth + 1
);
return treeHead;
}
throw new ParserError("not an IF "+(exprMode) ? "expression" : "statement");
} // END of IF
/** Parses following EBNF rule:
function_call =
ident , "(" , [expr , {argsep , expr} , [argsep]] , ")"
| ident , expr , {argsep , expr} , [argsep] ;
* @return: BasicAST
*/
bF._parseFunctionCall = function(lnum, tokens, states, recDepth) {
bF.parserPrintdbg2(String.fromCharCode(0x192), lnum, tokens, states, recDepth);
/*************************************************************************/
let parenDepth = 0;
let parenStart = -1;
let parenEnd = -1;
let _argsepsOnLevelZero = []; // argseps collected when parenDepth == 0
let _argsepsOnLevelOne = []; // argseps collected when parenDepth == 1
// Scan for unmatched parens and mark off the right operator we must deal with
// every function_call need to re-scan because it is recursively called
for (let k = 0; k < tokens.length; k++) {
// increase paren depth and mark paren start position
if (tokens[k] == "(" && states[k] != "qot") {
parenDepth += 1;
if (parenStart == -1 && parenDepth == 1) parenStart = k;
}
// decrease paren depth
else if (tokens[k] == ")" && states[k] != "qot") {
if (parenEnd == -1 && parenDepth == 1) parenEnd = k;
parenDepth -= 1;
}
if (parenDepth == 0 && states[k] == "sep")
_argsepsOnLevelZero.push(k);
if (parenDepth == 1 && states[k] == "sep")
_argsepsOnLevelOne.push(k);
}
// unmatched brackets, duh!
if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets);
let parenUsed = (parenStart == 1);
// && parenEnd == tokens.length - 1);
// if starting paren is found, just use it
// this prevents "RND(~~)*K" to be parsed as [RND, (~~)*K]
/*************************************************************************/
// ## case for:
// ident , "(" , [expr , {argsep , expr} , [argsep]] , ")"
// | ident , expr , {argsep , expr} , [argsep] ;
bF.parserPrintdbgline(String.fromCharCode(0x192), `Function Call (parenUsed: ${parenUsed})`, lnum, recDepth);
let treeHead = new BasicAST();
treeHead.astLnum = lnum;
// set function name and also check for syntax by deliberately parsing the word
treeHead.astValue = bF._parseIdent(lnum, [tokens[0]], [states[0]], recDepth + 1).astValue; // always UPPERCASE
// 5 8 11 [end]
let argSeps = parenUsed ? _argsepsOnLevelOne : _argsepsOnLevelZero; // choose which "sep tray" to use
bF.parserPrintdbgline(String.fromCharCode(0x192), "argSeps = "+argSeps, lnum, recDepth);
// 1 6 9 12
let argStartPos = [1 + (parenUsed)].concat(argSeps.map(k => k+1));
bF.parserPrintdbgline(String.fromCharCode(0x192), "argStartPos = "+argStartPos, lnum, recDepth);
// [1,5) [6,8) [9,11) [12,end)
let argPos = argStartPos.map((s,i) => {return{start:s, end:(argSeps[i] || (parenUsed ? parenEnd : tokens.length) )}}); // use end of token position as separator position
bF.parserPrintdbgline(String.fromCharCode(0x192), "argPos = "+argPos.map(it=>`${it.start}/${it.end}`), lnum, recDepth);
// check for trailing separator
// recursively parse function arguments
treeHead.astLeaves = argPos.map((x,i) => {
bF.parserPrintdbgline(String.fromCharCode(0x192), 'Function Arguments #'+(i+1), lnum, recDepth);
// check for empty tokens
if (x.end - x.start < 0) throw new ParserError("not a function call because it's malformed");
return bF._parseExpr(lnum,
tokens.slice(x.start, x.end),
states.slice(x.start, x.end),
recDepth + 1
)}
);
treeHead.astType = "function";
treeHead.astSeps = argSeps.map(i => tokens[i]);
bF.parserPrintdbgline(String.fromCharCode(0x192), "astSeps = "+treeHead.astSeps, lnum, recDepth);
return treeHead;
}
bF._parseIdent = function(lnum, tokens, states, recDepth) {
bF.parserPrintdbg2('i', lnum, tokens, states, recDepth);
if (!Array.isArray(tokens) && !Array.isArray(states)) throw new ParserError("Tokens and states are not array");
if (tokens.length != 1 || states[0] != "lit") throw new ParserError(`illegal tokens '${tokens}' with states '${states}' in ${lnum}`);
let treeHead = new BasicAST();
treeHead.astLnum = lnum;
treeHead.astValue = tokens[0].toUpperCase();
treeHead.astType = "lit";
return treeHead;
}
/**
* @return: BasicAST
*/
bF._parseLit = function(lnum, tokens, states, recDepth, functionMode) {
bF.parserPrintdbg2(String.fromCharCode(0xA2), lnum, tokens, states, recDepth);
if (!Array.isArray(tokens) && !Array.isArray(states)) throw new ParserError("Tokens and states are not array");
if (tokens.length != 1) throw new ParserError("parseLit 1");
let treeHead = new BasicAST();
treeHead.astLnum = lnum;
treeHead.astValue = ("qot" == states[0]) ? tokens[0] : tokens[0].toUpperCase();
treeHead.astType = ("qot" == states[0]) ? "string" :
("num" == states[0]) ? "num" :
(functionMode) ? "function" : "lit";
return treeHead;
}
bF._EquationIllegalTokens = ["IF","THEN","ELSE","DEFUN","ON"];
bF.isSemanticLiteral = function(token, state) {
return undefined == token || "]" == token || ")" == token ||
"qot" == state || "num" == state || "bool" == state || "lit" == state;
}
/////// TEST/////////
let astToString = function(ast, depth) {
let l__ = String.fromCharCode(0x2502,32);
let recDepth = depth || 0;
if (ast === undefined || ast.astType === undefined) return "";
var sb = "";
var marker = ("lit" == ast.astType) ? "i" :
("op" == ast.astType) ? String.fromCharCode(0xB1) :
("string" == ast.astType) ? String.fromCharCode(0xB6) :
("num" == ast.astType) ? String.fromCharCode(0xA2) :
("array" == ast.astType) ? "[" :
("defun_args" === ast.astType) ? String.fromCharCode(0x3B4) : String.fromCharCode(0x192);
sb += l__.repeat(recDepth) + marker+" Line "+ast.astLnum+" ("+ast.astType+")\n";
sb += l__.repeat(recDepth+1) + "leaves: "+(ast.astLeaves.length)+"\n";
sb += l__.repeat(recDepth+1) + "value: "+ast.astValue+" (type: "+typeof ast.astValue+")\n";
for (var k = 0; k < ast.astLeaves.length; k++) {
sb += astToString(ast.astLeaves[k], recDepth + 1);
sb += l__.repeat(recDepth+1) + " " + ast.astSeps[k] + "\n";
}
sb += l__.repeat(recDepth)+String.fromCharCode(0x2570)+String.fromCharCode(0x2500).repeat(13)+'\n';
return sb;
}
let BasicAST = function() {
this.astLnum = 0;
this.astLeaves = [];
this.astSeps = [];
this.astValue = undefined;
this.astType = "null"; // lit, op, string, num, array, function, null, defun_args (! NOT usrdefun !)
}
bF._opPrc = {
// function call in itself has highest precedence
"^":1,
"*":2,"/":2,"\\":2,
"MOD":3,
"+":4,"-":4,
"NOT":5,"BNOT":5,
"<<":6,">>":6,
"<":7,">":7,"<=":7,"=<":7,">=":7,"=>":7,
"==":8,"<>":8,"><":8,
"MIN":10,"MAX":10,
"BAND":20,
"BXOR":21,
"BOR":22,
"AND":30,
"OR":31,
"TO":40,
"STEP":41,
"!":50,"~":51, // array CONS and PUSH
"#": 52, // array concat
"<~": 100, // curry operator
"~>": 101, // closure operator
"=":999,
"IN":1000
};
bF._opRh = {"^":1,"=":1,"!":1,"IN":1,"<~":1,"~>":1};
let bStatus = {};
bStatus.builtin = {};
["PRINT","NEXT","SPC","CHR","ROUND","SQR","RND","GOTO","GOSUB","DEFUN","FOR","MAP"].forEach(w=>{ bStatus.builtin[w] = 1 });
let lnum = 10;
// if s<2 then (nop1) else (if s < 9999 then nop2 else nop3)
let tokens1 = ["if","s","<","2","then","(","nop1",")","else","(","if","s","<","9999","then","nop2","else","nop3",")"];
let states1 = ["lit","lit","op","num","lit","paren","lit","paren","lit","paren","lit","lit","op","num","lit","lit","lit","lit","paren"];
// DEFUN HYPOT(X,Y) = SQR(X*X+Y*Y)
let tokens2 = ["defun","HYPOT","(","X",",","Y",")","=","SQR","(","X","*","X","+","Y","*","Y",")"];
let states2 = ["lit","lit","paren","lit","sep","lit","paren","op","lit","paren","lit","op","lit","op","lit","op","lit","paren"];
// DEFUN SINC(X) = SIN(X) / X
let tokens3 = ["DEFUN","SINC","(","X",")","=","SIN","(","X",")","/","X"];
let states3 = ["lit","lit","paren","lit","paren","op","lit","paren","lit","paren","op","lit"];
// PRINT(IF S<2 THEN "111" ELSE IF S<3 THEN "222" ELSE "333")
let tokens4 = ["PRINT","(","IF","S","<","2","THEN","111","ELSE","IF","S","<","3","THEN","222","ELSE","333",")"];
let states4 = ["lit","paren","lit","lit","op","lit","lit","qot","lit","lit","lit","op","lit","lit","qot","lit","qot","paren"];
// ON 6*SQR(X-3) GOTO X+1, X+2, X+3
let tokens5 = ["ON","6","*","SQR","(","X","-","3",")","GOTO","X","+","1",",","X","+","2",",","X","+","3"];
let states5 = ["lit","num","op","lit","paren","lit","op","num","paren","lit","lit","op","num","sep","lit","op","num","sep","lit","op","num"];
// FOR K=10 TO 1 STEP -1
let tokens6 = ["FOR","K","=","10","TO","1","STEP","-","1"];
let states6 = ["lit","lit","op","num","op","num","op","op","num"];
// print(chr(47+round(rnd(1))*45);)
let tokens7 = ["PRINT","(","CHR","(","47","+","ROUND","(","RND","(","1",")",")","*","45",")",";",")"];
let states7 = ["lit","paren","lit","paren","num","op","lit","paren","lit","paren","num","paren","paren","op","num","paren","sep","paren"];
// PRINT 4 - 5 * 9
let tokens8 = ["PRINT","4","-","5","*","9"];
let states8 = ["lit","num","op","num","op","num"];
// NEXT
let tokens9 = ["NEXT"];
let states9 = ["lit"];
// PRINT -3
let tokens10 = ["PRINT","-","3"];
let states10 = ["lit","op","num"];
// PRINT SPC(20-I);
let tokens11 = ["PRINT","SPC","(","20","-","I",")",";"];
let states11 = ["lit","lit","paren","num","op","lit","paren","sep"];
// DEFUN FAC(N)=IF N==0 THEN 1 ELSE N*FAC(N-1)
let tokens12 = ["DEFUN","FAC","(","N",")","=","IF","N","==","0","THEN","1","ELSE","N","*","FAC","(","N","-","1",")"];
let states12 = ["lit","lit","paren","lit","paren","op","lit","lit","op","num","lit","num","lit","lit","op","lit","paren","lit","op","num","paren"];
// K = MAP FAC , 1 TO 10
let tokens13 = ["K","=","MAP","FAC",",","1","TO","10"];
let states13 = ["lit","op","lit","lit","sep","num","op","num"];
// DEFUN FIB(N)=IF N==0 THEN 0 ELSE IF N==1 THEN 1 ELSE FIB(N-1)+FIB(N-2)
let tokens14 = ["DEFUN","FIB","(","N",")","=","IF","N","==","0","THEN","0",
"ELSE","IF","N","==","1","THEN","1",
"ELSE","FIB","(","N","-","1",")","+","FIB","(","N","-","2",")"];
let states14 = ["lit","lit","paren","lit","paren","op","lit","lit","op","num","lit","num",
"lit","lit","lit","op","num","lit","num",
"lit","lit","paren","lit","op","num","paren","op","lit","paren","lit","op","num","paren"];
// PRINT(MAP FIB, 1 TO 10) is not broken, it's obvious syntax error
// use "PRINT MAP(FIB, 1 TO 10)" instead
let tokens15 = ["PRINT","MAP","(","FIB",",","1","TO","10",")"];
let states15 = ["lit","lit","paren","lit","sep","num","op","num","paren"];
// DEFUN KA(X)=IF X>2 THEN DO(PRINT(HAI);PRINT(X)) ELSE DO(PRINT(BYE);PRINT(X))
let tokens16 = ["DEFUN","KA","(","X",")","=","IF","X",">","2","THEN","DO","(","PRINT","(","HAI",")",";","PRINT","(","X",")",")","ELSE","DO","(","PRINT","(","BYE",")",";","PRINT","(","X",")",")"];
let states16 = ["lit","lit","paren","lit","paren","op","lit","lit","op","num","lit","lit","paren","lit","paren","qot","paren","sep","lit","paren","lit","paren","paren","lit","lit","paren","lit","paren","qot","paren","sep","lit","paren","lit","paren","paren"];
// FILTER(FN<~HEAD XS, TAIL XS)
let tokens17 = ["FILTER","(","FN","<~","HEAD","XS",",","TAIL","XS",")"];
let states17 = ["lit","paren","lit","op","lit","lit","sep","lit","lit","paren"];
try {
let trees = bF._parseTokens(lnum,
tokens17,
states17
);
trees.forEach((t,i) => {
serial.println("\nParsed Statement #"+(i+1));
serial.println(astToString(t));
});
}
catch (e) {
serial.printerr(e);
serial.printerr(e.stack || "stack trace undefined");
}

View File

@@ -0,0 +1,141 @@
// define TVDOS
var _TVDOS = {};
_TVDOS.VERSION = "1.0";
_TVDOS.DRIVES = {}; // Object where key-value pair is <drive-letter> : [serial-port, drive-number]
// actually figure out the drive letter association
// Drive A is always the device we're currently on
_TVDOS.DRIVES["A"] = _BIOS.FIRST_BOOTABLE_PORT;
//TODO
_TVDOS.getPath = function() {
return _TVDOS.variables.PATH.split(';');
};
// initial values
_TVDOS.variables = {
DOSDIR: "\\tvdos",
LANG: "EN",
PATH: ";\\tvdos\\bin;\\tbas;\\home",
PATHEXT: ".com;.bat;.js",
HELPPATH: "\\tvdos\\help",
OS_NAME: "Terrarum Virtual DOS",
OS_VERSION: _TVDOS.VERSION
};
Object.freeze(_TVDOS);
///////////////////////////////////////////////////////////////////////////////
var filesystem = {};
filesystem._toPorts = (driveLetter) => {
if (driveLetter.toUpperCase === undefined) {
throw Error("'"+driveLetter+"' (type: "+typeof driveLetter+") is not a valid drive letter");
}
var port = _TVDOS.DRIVES[driveLetter.toUpperCase()];
if (port === undefined) {
throw Error("Drive letter '" + driveLetter.toUpperCase() + "' does not exist");
}
return port
};
filesystem._close = (portNo) => {
com.sendMessage(portNo, "CLOSE");
};
filesystem._flush = (portNo) => {
com.sendMessage(portNo, "FLUSH");
};
// @return true if operation committed successfully, false if:
// - opening file with R-mode and target file does not exists
// throws if:
// - java.lang.NullPointerException if path is null
// - Error if operation mode is not "R", "W" nor "A"
filesystem.open = (driveLetter, path, operationMode) => {
var port = filesystem._toPorts(driveLetter);
filesystem._flush(port[0]); filesystem._close(port[0]);
var mode = operationMode.toUpperCase();
if (mode != "R" && mode != "W" && mode != "A") {
throw Error("Unknown file opening mode: " + mode);
}
com.sendMessage(port[0], "OPEN"+mode+'"'+path+'",'+port[1]);
var response = com.getStatusCode(port[0]);
return (response == 0);
};
// @return the entire contents of the file in String
filesystem.readAll = (driveLetter) => {
var port = filesystem._toPorts(driveLetter);
com.sendMessage(port[0], "READ");
var response = com.getStatusCode(port[0]);
if (135 == response) {
throw Error("File not opened");
}
if (response < 0 || response >= 128) {
throw Error("Reading a file failed with "+response);
}
return com.pullMessage(port[0]);
};
filesystem.write = (driveLetter, string) => {
var port = filesystem._toPorts(driveLetter);
com.sendMessage(port[0], "WRITE"+string.length);
var response = com.getStatusCode(port[0]);
if (135 == response) {
throw Error("File not opened");
}
if (response < 0 || response >= 128) {
throw Error("Writing a file failed with "+response);
}
com.sendMessage(port[0], string);
filesystem._flush(port[0]); filesystem._close(port[0]);
};
filesystem.isDirectory = (driveLetter) => {
var port = filesystem._toPorts(driveLetter);
com.sendMessage(port[0], "LISTFILES");
var response = com.getStatusCode(port[0]);
return (response === 0);
};
filesystem.mkDir = (driveLetter) => {
var port = filesystem._toPorts(driveLetter);
com.sendMessage(port[0], "MKDIR");
var response = com.getStatusCode(port[0]);
if (response < 0 || response >= 128) {
var status = com.getDeviceStatus(port[0]);
throw Error("Creating a directory failed with ("+response+"): "+status.message+"\n");
}
return (response === 0); // possible status codes: 0 (success), 1 (fail)
};
filesystem.touch = (driveLetter) => {
var port = filesystem._toPorts(driveLetter);
com.sendMessage(port[0], "TOUCH");
var response = com.getStatusCode(port[0]);
return (response === 0);
};
filesystem.mkFile = (driveLetter) => {
var port = filesystem._toPorts(driveLetter);
com.sendMessage(port[0], "MKFILE");
var response = com.getStatusCode(port[0]);
return (response === 0);
};
Object.freeze(filesystem);
///////////////////////////////////////////////////////////////////////////////
// install other stuffs
filesystem.open("A", "tvdos/gl.js", "R");
var GL = eval(filesystem.readAll("A"));
// @param cmdsrc JS source code
// @param args arguments for the program, must be Array, and args[0] is always the name of the program, e.g.
// for command line 'echo foo bar', args[0] must be 'echo'
var execApp = (cmdsrc, args) => {
var execAppPrg = eval("var _appStub=function(exec_args){"+cmdsrc+"};_appStub;"); // making 'exec_args' a app-level global
return execAppPrg(args);
}
///////////////////////////////////////////////////////////////////////////////
// Boot script
serial.println("TVDOS.SYS initialised, running boot script...");
var _G = {};
filesystem.open("A", "tvdos/bin/command.js", "R");
execApp(filesystem.readAll("A"), ["", "/c", "\\AUTOEXEC.BAT"]);

View File

@@ -0,0 +1,3 @@
// load a BASIC rom
sys.mapRom(1);
execApp(sys.romReadAll());

View File

@@ -0,0 +1,454 @@
let PROMPT_TEXT = ">";
let CURRENT_DRIVE = "A";
let shell_pwd = [""];
let goInteractive = false;
let goFancy = false;
let DEBUG_PRINT = true;
let errorlevel = 0;
const welcome_text = "TSVM Disk Operating System, version " + _TVDOS.VERSION;
function print_prompt_text() {
if (goFancy) {
con.color_pair(239,161);
print(" "+CURRENT_DRIVE+":");
con.color_pair(161,253);
con.addch(16);
con.color_pair(0,253);
print(" \\"+shell_pwd.join("\\").substring(1)+" ");
if (errorlevel != 0) {
con.color_pair(166,253);
print("["+errorlevel+"] ");
}
con.color_pair(253,255);
con.addch(16);
con.addch(32);
con.color_pair(239,255);
}
else {
if (errorlevel != 0)
print(CURRENT_DRIVE + ":\\" + shell_pwd.join("\\") + " [" + errorlevel + "]" + PROMPT_TEXT);
else
print(CURRENT_DRIVE + ":\\" + shell_pwd.join("\\") + PROMPT_TEXT);
}
}
function greet() {
if (goFancy) {
con.color_pair(0,253);
//print(welcome_text + " ".repeat(_fsh.scrwidth - welcome_text.length));
print(welcome_text + " ".repeat(80 - welcome_text.length));
con.color_pair(239,255);
println();
}
else
println(welcome_text);
}
function trimStartRevSlash(s) {
var cnt = 0;
while (cnt < s.length) {
var chr = s[cnt];
if (chr != '\\') break;
cnt += 1;
}
return s.substring(cnt);
}
let shell = {};
shell.getPwd = function() { return shell_pwd; }
shell.getCurrentDrive = function() { return CURRENT_DRIVE; }
// example input: echo "the string" > subdir\test.txt
shell.parse = function(input) {
var tokens = [];
var stringBuffer = "";
var mode = "LITERAL"; // LITERAL, QUOTE, ESCAPE, LIMBO
var i = 0
while (i < input.length) {
const c = input[i];
/*digraph g {
LITERAL -> QUOTE [label="\""]
LITERAL -> LIMBO [label="space"]
LITERAL -> LITERAL [label=else]
QUOTE -> LIMBO [label="\""]
QUOTE -> ESCAPE [label="\\"]
QUOTE -> QUOTE [label=else]
ESCAPE -> QUOTE
LIMBO -> LITERAL [label="not space"]
LIMBO -> QUOTE [label="\""]
LIMBO -> LIMBO [label="space"]
}*/
if ("LITERAL" == mode) {
if (' ' == c) {
tokens.push(stringBuffer); stringBuffer = "";
mode = "LIMBO";
}
else if ('"' == c) {
tokens.push(stringBuffer); stringBuffer = "";
mode = "QUOTE";
}
else {
stringBuffer += c;
}
}
else if ("LIMBO" == mode) {
if ('"' == c) {
mode = "QUOTE";
}
else if (c != ' ') {
mode = "LITERAL";
stringBuffer += c;
}
}
else if ("QUOTE" == mode) {
if ('"' == c) {
tokens.push(stringBuffer); stringBuffer = "";
mode = "LIMBO";
}
else if ('^' == c) {
mode = "ESCAPE";
}
else {
stringBuffer += c;
}
}
else if ("ESCAPE" == mode) {
TODO();
}
i += 1;
}
if (stringBuffer.length > 0) {
tokens.push(stringBuffer);
}
return tokens;
}
shell.resolvePathInput = function(input) {
// replace slashes into revslashes
var pathstr = input.replaceAll('/','\\\\');
var startsWithSlash = input.startsWith('\\');
var newPwd = [];
// split them into an array while filtering empty elements except for the root 'head'
var ipwd = (startsWithSlash ? [""] : shell_pwd).concat(pathstr.split("\\").filter(function(it) { return (it.length > 0); }));
serial.println("command.js > resolvePathInput > ipwd = "+ipwd);
serial.println("command.js > resolvePathInput > newPwd = "+newPwd);
// process dots
ipwd.forEach(function(it) {
serial.println("command.js > resolvePathInput > ipwd.forEach > it = "+it);
if (it === ".." && newPwd[1] !== undefined) {
newPwd.pop();
}
else if (it !== ".." && it !== ".") {
newPwd.push(it);
}
serial.println("command.js > resolvePathInput > newPwd = "+newPwd);
});
// construct new pathstr from pwd arr so it will be sanitised
pathstr = newPwd.join('\\').substring(1);
return { string: pathstr, pwd: newPwd };
}
shell.coreutils = {
/* Args follow this format:
* <command-name> <1st arg> <2nd arg> ...
* NOTE:
* even if there's no 1st arg, length of args may not be 1, therefore don't:
* if (args.length < 2)
* but do instead:
* if (args[1] === undefined)
*/
cd: function(args) {
if (args[1] === undefined) {
println(CURRENT_DRIVE+":"+shell_pwd.join("\\"));
return
}
var path = shell.resolvePathInput(args[1])
if (DEBUG_PRINT) serial.println("command.js > cd > pathstr = "+path.string);
// check if path is valid
filesystem.open(CURRENT_DRIVE, path.string, 'R');
var dirOpened = filesystem.isDirectory(CURRENT_DRIVE); // open a dir; if path is nonexistent, file won't actually be opened
if (!dirOpened) { printerrln("CHDIR failed for '"+path.string+"'"); return; } // if file is not opened, FALSE will be returned
shell_pwd = path.pwd;
},
mkdir: function(args) {
if (args[1] === undefined) {
printerrln("Syntax error");
return
}
var path = shell.resolvePathInput(args[1])
if (DEBUG_PRINT) serial.println("command.js > mkdir > pathstr = "+path.string);
// check if path is valid
var dirOpened = filesystem.open(CURRENT_DRIVE, path.string, 'W');
var mkdird = filesystem.mkDir(CURRENT_DRIVE);
if (!mkdird) { printerrln("MKDIR failed for '"+path.string+"'"); return; }
},
cls: function(args) {
con.clear();
},
exit: function(args) {
cmdExit = true;
},
ver: function(args) {
println(welcome_text);
},
echo: function(args) {
if (args[1] !== undefined) {
args.forEach(function(it,i) { if (i > 0) print(it+" ") });
}
println();
},
rem: function(args) {
return 0;
},
set: function(args) {
// print all the env vars
if (args[1] === undefined) {
Object.entries(_TVDOS.variables).forEach(function(a) { println(a[0]+"="+a[1]); })
}
else {
// parse key-value pair with splitter '='
var key = undefined; var value = undefined;
// if syntax "<key> = <value>" is used?
if ('=' == args[2]) {
key = args[1].toUpperCase(); value = args[3];
}
else if (args[2] === undefined) {
var pair = args[1].split('=');
key = pair[0].toUpperCase(); value = pair[1];
}
if (key == undefined) throw SyntaxError("Input format must be 'key=value'");
// if value is undefined, show what envvar[key] has
if (value === undefined) {
if (_TVDOS.variables[key] === undefined)
println("Environment variable '"+key+"' not found");
else
println(_TVDOS.variables[key])
}
else {
// TODO parse %var_name% line
_TVDOS.variables[key] = value;
}
}
},
dir: function(args) {
var pathstr = (args[1] !== undefined) ? args[1] : "\\"+shell_pwd.join("\\");
// check if path is valid
var pathOpened = filesystem.open(CURRENT_DRIVE, pathstr, 'R');
if (!pathOpened) { printerrln("File not found"); return; }
var port = filesystem._toPorts(CURRENT_DRIVE)[0]
com.sendMessage(port, "LIST");
println(com.pullMessage(port));
}
};
shell.coreutils.chdir = shell.coreutils.cd;
Object.freeze(shell.coreutils);
shell.execute = function(line) {
if (0 == line.size) return;
var tokens = shell.parse(line);
var cmd = tokens[0];
if (cmd === undefined || cmd === '') return 0;
// handle Ctrl-C
if (con.hitterminate()) return 1;
if (shell.coreutils[cmd.toLowerCase()] !== undefined) {
var retval = shell.coreutils[cmd.toLowerCase()](tokens);
return retval|0; // return value of undefined will cast into 0
}
else {
// search through PATH for execution
var fileExists = false;
var searchDir = (cmd.startsWith("\\")) ? [""] : ["\\"+shell_pwd.join("\\")].concat(_TVDOS.getPath());
var pathExt = []; // it seems Nashorn does not like 'let' too much? this line gets ignored sometimes
// fill pathExt using %PATHEXT% but also capitalise them
if (cmd.split(".")[1] === undefined)
_TVDOS.variables.PATHEXT.split(';').forEach(function(it) { pathExt.push(it); pathExt.push(it.toUpperCase()); });
else
pathExt.push(""); // final empty extension
searchLoop:
for (var i = 0; i < searchDir.length; i++) {
for (var j = 0; j < pathExt.length; j++) {
var search = searchDir[i]; if (!search.endsWith('\\')) search += '\\';
var path = trimStartRevSlash(search + cmd + pathExt[j]);
if (DEBUG_PRINT) {
serial.println("[command.js > shell.execute] file search path: "+path);
}
if (filesystem.open(CURRENT_DRIVE, path, "R")) {
fileExists = true;
break searchLoop;
}
}
}
if (!fileExists) {
printerrln('Bad command or filename: "'+cmd+'"');
return 127;
}
else {
var programCode = filesystem.readAll(CURRENT_DRIVE);
var extension = undefined;
// get proper extension
var dotSepTokens = cmd.split('.');
if (dotSepTokens.length > 1) extension = dotSepTokens[dotSepTokens.length - 1].toUpperCase();
if ("BAT" == extension) {
// parse and run as batch file
var lines = programCode.split('\n').filter(function(it) { return it.length > 0; });
lines.forEach(function(line) {
shell.execute(line);
});
}
else {
return execApp(programCode, tokens)|0; // return value of undefined will cast into 0
}
}
}
};
Object.freeze(shell);
_G.shell = shell;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (exec_args[1] !== undefined) {
// only meaningful switches would be either /c or /k anyway
var firstSwitch = exec_args[1].toLowerCase();
// command /c <commands>
// ^[0] ^[1] ^[2]
if ("/c" == firstSwitch) {
if ("" == exec_args[2]) return 0; // no commands were given, just exit successfully
return shell.execute(exec_args[2]);
}
else if ("/k" == firstSwitch) {
if ("" == exec_args[2]) return 0; // no commands were given, just exit successfully
shell.execute(exec_args[2]);
goInteractive = true;
}
else if ("/fancy" == firstSwitch) {
goFancy = true;
goInteractive = true;
}
else {
printerrln("Invalid switch: "+exec_args[1]);
return 1;
}
}
else {
goInteractive = true;
}
let cmdExit = false;
if (goInteractive) {
con.reset_graphics();
greet();
let cmdHistory = []; // zeroth element is the oldest
let cmdHistoryScroll = 0; // 0 for outside-of-buffer, 1 for most recent
while (!cmdExit) {
con.reset_graphics();
print_prompt_text();
var cmdbuf = "";
while (true) {
var key = con.getch();
// printable chars
if (key >= 32 && key <= 126) {
var s = String.fromCharCode(key);
cmdbuf += s;
print(s);
}
// backspace
else if (key === 8 && cmdbuf.length > 0) {
cmdbuf = cmdbuf.substring(0, cmdbuf.length - 1);
print(String.fromCharCode(key));
}
// enter
else if (key === 10 || key === 13) {
println();
try {
errorlevel = 0; // reset the number
errorlevel = shell.execute(cmdbuf);
if (isNaN(errorlevel)) errorlevel = 2;
}
catch (e) {
printerrln("\n"+e);
if (errorlevel === 0 || isNaN(errorlevel)) {
errorlevel = 1; // generic failure
}
}
finally {
if (cmdbuf.trim().length > 0)
cmdHistory.push(cmdbuf);
cmdHistoryScroll = 0;
con.curs_set(1);
break;
}
}
// up arrow
else if (key === 19 && cmdHistory.length > 0 && cmdHistoryScroll < cmdHistory.length) {
cmdHistoryScroll += 1;
// back the cursor in order to type new cmd
var x = 0;
for (x = 0; x < cmdbuf.length; x++) print(String.fromCharCode(8));
cmdbuf = cmdHistory[cmdHistory.length - cmdHistoryScroll];
// re-type the new command
print(cmdbuf);
}
// down arrow
else if (key === 20) {
if (cmdHistoryScroll > 0) {
// back the cursor in order to type new cmd
var x = 0;
for (x = 0; x < cmdbuf.length; x++) print(String.fromCharCode(8));
cmdbuf = cmdHistory[cmdHistory.length - cmdHistoryScroll];
// re-type the new command
print(cmdbuf);
cmdHistoryScroll -= 1;
}
else {
// back the cursor in order to type new cmd
var x = 0;
for (x = 0; x < cmdbuf.length; x++) print(String.fromCharCode(8));
cmdbuf = "";
}
}
}
}
}
return 0;

View File

@@ -0,0 +1 @@
return 1;

View File

@@ -0,0 +1,106 @@
let CURRENT_DRIVE = "A";
let shell_pwd = [""];
const welcome_text = "TSVM Disk Operating System, version " + _TVDOS.VERSION;
function print_prompt_text() {
// oh-my-zsh-like prompt
con.color_pair(239,161);
print(" "+CURRENT_DRIVE+":");
con.color_pair(161,253);
con.addch(16);
con.color_pair(0,253);
print(" \\"+shell_pwd.join("\\")+" ");
con.color_pair(253,255);
con.addch(16);
con.addch(32);
con.color_pair(239,255);
}
function greet() {
con.color_pair(0,253);
//print(welcome_text + " ".repeat(_fsh.scrwidth - welcome_text.length));
print(welcome_text + " ".repeat(80 - welcome_text.length));
con.color_pair(239,255);
println();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
con.clear();
greet();
let cmdHistory = []; // zeroth element is the oldest
let cmdHistoryScroll = 0; // 0 for outside-of-buffer, 1 for most recent
while (true) {
print_prompt_text();
let cmdbuf = "";
while (true) {
let key = con.getch();
// printable chars
if (key >= 32 && key <= 126) {
let s = String.fromCharCode(key);
cmdbuf += s;
print(s);
}
// backspace
else if (key === 8 && cmdbuf.length > 0) {
cmdbuf = cmdbuf.substring(0, cmdbuf.length - 1);
print(String.fromCharCode(key));
}
// enter
else if (key === 10 || key === 13) {
println();
try {
println("You entered: " + cmdbuf);
}
catch (e) {
println(e);
}
finally {
if (cmdbuf.trim().length > 0)
cmdHistory.push(cmdbuf);
cmdHistoryScroll = 0;
break;
}
}
// up arrow
else if (key === 19 && cmdHistory.length > 0 && cmdHistoryScroll < cmdHistory.length) {
cmdHistoryScroll += 1;
// back the cursor in order to type new cmd
let x = 0;
for (x = 0; x < cmdbuf.length; x++) print(String.fromCharCode(8));
cmdbuf = cmdHistory[cmdHistory.length - cmdHistoryScroll];
// re-type the new command
print(cmdbuf);
}
// down arrow
else if (key === 20) {
if (cmdHistoryScroll > 0) {
// back the cursor in order to type new cmd
let x = 0;
for (x = 0; x < cmdbuf.length; x++) print(String.fromCharCode(8));
cmdbuf = cmdHistory[cmdHistory.length - cmdHistoryScroll];
// re-type the new command
print(cmdbuf);
cmdHistoryScroll -= 1;
}
else {
// back the cursor in order to type new cmd
let x = 0;
for (x = 0; x < cmdbuf.length; x++) print(String.fromCharCode(8));
cmdbuf = "";
}
}
}
}

View File

@@ -0,0 +1,131 @@
graphics.setBackground(3,3,3);
graphics.resetPalette();
function captureUserInput() {
sys.poke(-40, 1);
}
function getKeyPushed(keyOrder) {
return sys.peek(-41 - keyOrder);
}
var _fsh = {};
_fsh.titlebarTex = new GL.Texture(2, 14, base64.atob("/u/+/v3+/f39/f39/f39/f39/P39/Pz8/Pv7+w=="));
_fsh.scrdim = con.getmaxyx();
_fsh.scrwidth = _fsh.scrdim[1];
_fsh.scrheight = _fsh.scrdim[0];
_fsh.brandName = "f\xb3Sh";
_fsh.brandLogoTexSmall = new GL.Texture(24, 14, gzip.decomp(base64.atob(
"H4sIAAAAAAAAAPv/Hy/4Qbz458+fIeILQQBIwoSh6qECuMVBukCmIJkDVQ+RQNgLE0MX/w+1lyhxqIUwTLJ/sQMAcIXsbVABAAA="
)));
_fsh.scrlayout = ["com.fsh.clock","com.fsh.calendar","com.fsh.apps_n_files"];
_fsh.drawTitlebar = function(titletext) {
GL.drawTexPattern(_fsh.titlebarTex, 0, 0, 560, 14);
if (titletext === undefined || titletext.length == 0) {
con.move(1,1);
print(" ".repeat(_fsh.scrwidth));
GL.drawTexImageOver(_fsh.brandLogoTexSmall, 268, 0);
}
else {
con.color_pair(240, 255);
GL.drawTexPattern(_fsh.titlebarTex, 268, 0, 24, 14);
con.move(1, 1 + (_fsh.scrwidth - titletext.length) / 2);
print(titletext);
}
con.color_pair(254, 255);
};
_fsh.Widget = function(id, w, h) {
this.identifier = id;
this.width = w;
this.height = h;
if (!this.identifier) {
this.identifier = "";
}
//this.update = function() {};
/**
* Params charXoff and charYoff are ZERO-BASED!
*/
this.draw = function(charXoff, charYoff) {};
}
_fsh.widgets = {}
_fsh.registerNewWidget = function(widget) {
_fsh.widgets[widget.identifier] = widget;
}
var clockWidget = new _fsh.Widget("com.fsh.clock", _fsh.scrwidth - 8, 7);
clockWidget.numberSheet = new GL.SpriteSheet(19, 22, new GL.Texture(190, 22, gzip.decomp(base64.atob(
"H4sIAAAAAAAAAMWVW3LEMAgE739aHcFJJV5ZMD2I9ToVfcl4GBr80HF8r/FaR1ozMuIyoUu87lEXI0al5qVR5AebSwchSaNE6Nyo1Nw5HXF3SfPT4Bshl"+
"EycA8RD96mLlHbuhTgOrfLnUDZspafbSQWk56WEGvQEtWaWwgb8iz7a8AOXhsraO/q9Qw2/GnXovfVN+q2wM/p/oddn2cjF239GX3y11+SWCtc6FTHC1v"+
"TVPkDPWWn0w+DDz93UX9v9mF5KIsQ6OdN2KJoB4ui1bXXr0AMp0YfiQo//4XhpK8555dsNehAqVS5uhb5iHn3Kko769J59KmLBe/TSR7hcsd+hr+HnrwR"+
"9uvRF9+D3MP14gN7lqx+8OuNT+uqt3NFX3SN9fTbeeHNq+C29pRWzX5+Rcm7SZyjOKJ/2hkSPqul4xN279DrSYvCrNu2NI7ZMp1ouBxK3KBVVnEeAUWbK"+
"MUDn5DPsPxmUqHZQjGpy2hergM3EVBAAAA=="
))));
clockWidget.clockColon = new GL.Texture(4, 3, base64.atob("7+/v7+/v7+/v7+/v"));
clockWidget.monthNames = ["Spring", "Summer", "Autumn", "Winter"];
clockWidget.dayNames = ["Mondag ", "Tysdag ", "Midtveke", "Torsdag ", "Fredag ", "Laurdag ", "Sundag ", "Verddag "];
clockWidget.draw = function(charXoff, charYoff) {
con.color_pair(254, 255);
var xoff = charXoff * 7;
var yoff = charYoff * 14 + 3;
var timeInMinutes = ((sys.currentTimeInMills() / 60000)|0);
var mins = timeInMinutes % 60;
var hours = ((timeInMinutes / 60)|0) % 24;
var ordinalDay = ((timeInMinutes / (60*24))|0) % 120;
var visualDay = (ordinalDay % 30) + 1;
var months = ((timeInMinutes / (60*24*30))|0) % 4;
var dayName = ordinalDay % 7; // 0 for Mondag
if (ordinalDay == 119) dayName = 7; // Verddag
var years = ((timeInMinutes / (60*24*30*120))|0) + 125;
// draw timepiece
GL.drawSprite(clockWidget.numberSheet, (hours / 10)|0, 0, xoff, yoff);
GL.drawSprite(clockWidget.numberSheet, hours % 10, 0, xoff + 24, yoff);
GL.drawTexImage(clockWidget.clockColon, xoff + 48, yoff + 5);
GL.drawTexImage(clockWidget.clockColon, xoff + 48, yoff + 14);
GL.drawSprite(clockWidget.numberSheet, (mins / 10)|0, 0, xoff + 57, yoff);
GL.drawSprite(clockWidget.numberSheet, mins % 10, 0, xoff + 81, yoff);
// print month and date
con.move(1 + charYoff, 17 + charXoff);
print(clockWidget.monthNames[months]+" "+visualDay);
// print year and dayname
con.mvaddch(2 + charYoff, 17 + charXoff, 5);
con.move(2 + charYoff, 18 + charXoff);
print(years+" "+clockWidget.dayNames[dayName]);
};
// register widgets
_fsh.registerNewWidget(clockWidget);
// screen init
con.color_pair(254, 255);
con.clear();
con.curs_set(0);
_fsh.drawTitlebar();
// TEST
con.move(2,1);
print("Hit backspace to exit");
while (true) {
captureUserInput();
if (getKeyPushed(0) == 67) break;
_fsh.widgets["com.fsh.clock"].draw(25, 2);
}
con.move(3,1);
con.color_pair(201,255);
print("cya!");
let konsht = 3412341241;
println(konsht);
let pppp = graphics.getCursorYX();
println(pppp.toString());

View File

@@ -0,0 +1,20 @@
if (exec_args[1] === undefined) {
printerrln("Usage: hexdump <file>")
return 1;
}
let fileOpened = filesystem.open(_G.shell.getCurrentDrive(), _G.shell.resolvePathInput(exec_args[1]).string, "R");
if (!fileOpened) {
printerrln(_G.shell.resolvePathInput(exec_args[1]).string+": cannot open");
return 1;
}
let fileContent = filesystem.readAll(_G.shell.getCurrentDrive());
let visible = "";
for (let k = 0; k < fileContent.length; k++) {
if (k > 0 && k % 16 == 0) visible += "\n";
visible += `${fileContent.charCodeAt(k).toString(16).toUpperCase().padStart(2, '0')} `;
}
println(visible);
return 0;

View File

@@ -0,0 +1,143 @@
if (exec_args[1] === undefined) {
println('Missing filename ("less -?" for help)');
return 0;
}
/*let help = "\n
SUMMARY OF COMMANDS\n
\n
h H Display this help\n
q Q Exit
\n"*/
if (exec_args[1].startsWith("-?")) {
println("less <filename>");
return 0;
}
let fileOpened = filesystem.open(_G.shell.getCurrentDrive(), _G.shell.resolvePathInput(exec_args[1]).string, "R");
if (!fileOpened) {
printerrln(_G.shell.resolvePathInput(exec_args[1]).string+": cannot open");
return 1;
}
let scroll = 0;
let pan = -1;
let termW = con.getmaxyx()[1];
let termH = con.getmaxyx()[0] - 1;
let buf = "";
let fileContent = filesystem.readAll(_G.shell.getCurrentDrive());
let key = -1;
let panSize = termW >> 1;
let scrollSize = termH >> 3;
// initialise some helper variables
let lineToBytes = [0];
let maxPan = 0;
let maxPanCur = 0;
for (let i = 0; i < fileContent.length; i++) {
let char = fileContent.charCodeAt(i);
maxPanCur += 1;
if (char == 10 || char == 13) {
lineToBytes.push(i + 1);
if (maxPanCur > maxPan) maxPan = maxPanCur;
maxPanCur = 0;
}
}
let startAddr = -1;
let paintCur = 0;
let cy = 1;
let cx = 1;
let char = -1;
let numbuf = 0;
let resetKeyReadStatus = function() {
numbuf = 0;
}
let repaint = function() {
con.move(1,1); con.clear();
startAddr = lineToBytes[scroll];
cy = 1; cx = -pan; paintCur = 0;
while (cy <= termH) {
char = fileContent.charCodeAt(startAddr + paintCur);
if (isNaN(char)) break;
if (cy <= termH) {
if (cx >= 0 && cx < termW) {
con.move(cy, cx);
if (char != 10 && char != 13)
con.addch(char);
}
cx += 1;
}
if (char == 10 || char == 13) {
cy += 1;
cx = -pan;
}
paintCur += 1;
}
}
repaint();
con.move(termH + 1, 1);
print(":"+" ".repeat(termW - 2));
con.move(termH + 1, 2);
while (true) {
// read a key
key = con.getch();
// do something with key read
/*Q*/if (key == 113 || key == 81) break;
/*R*/else if (key == 114 || key == 82) repaint();
/*up*/else if (key == 19) {
scroll -= scrollSize;
if (scroll < 0) scroll = 0;
repaint();
}
/*down*/else if (key == 20) {
scroll += scrollSize;
if (scroll > lineToBytes.length - termH) scroll = lineToBytes.length - termH;
repaint();
}
/*left*/else if (key == 21 && pan > 0) {
pan -= panSize;
repaint();
}
/*right*/else if (key == 22 && pan < maxPan - termW) {
pan += panSize;
repaint();
}
/*0-9*/else if (key >= 48 && key <= 57) {
print(String.fromCharCode(key));
numbuf = (numbuf * 10) + (key - 48);
}
/*bksp*/else if (key == 8) {
if (numbuf > 0) print(String.fromCharCode(key));
numbuf = (numbuf / 10)|0;
}
/*u*/else if (key == 117) {
scroll -= numbuf;
if (scroll < 0) scroll = 0;
repaint();
}
/*d*/else if (key == 100) {
scroll += numbuf;
if (scroll > lineToBytes.length - termH) scroll = lineToBytes.length - termH;
repaint();
}
if (!(key >= 48 && key <= 57 || key == 8)) {
resetKeyReadStatus();
con.move(termH + 1, 1);
print(":"+" ".repeat(termW - 2));
con.move(termH + 1, 2);
}
serial.println("numbuf = "+numbuf);
}
con.move(termH + 1, 1);
return 0;

View File

@@ -0,0 +1,30 @@
if (exec_args[1] === undefined) {
println("TOUCH - TVDOS file date and time setting utility");
println()
println("SYNOPSIS")
println(" TOUCH [/C] path")
println()
println("/C = don't create files that do not already exist")
return 1;
}
let path = _G.shell.resolvePathInput(exec_args[2] || exec_args[1]).string;
let driveLetter = _G.shell.getCurrentDrive();
let noNewFile = (exec_args[1] == "/c" || exec_args[1] == "/C");
let fileOpened = filesystem.open(driveLetter, path, "W");
if (!fileOpened) {
printerrln("TOUCH: Can't open "+driveLetter+":\\"+path+" due to IO error");
return 1;
}
if (!noNewFile) {
filesystem.mkFile(driveLetter);
}
let touched = filesystem.touch(driveLetter);
if (!touched) {
printerrln("TOUCH: Can't touch "+driveLetter+":\\"+path+" due to IO error");
return 1;
}
return 0;

View File

@@ -0,0 +1 @@
return 0;

View File

@@ -0,0 +1,45 @@
# Syntax
## Reserved directories
* `$DEVICE<device_number>`
## Reserved files
* `$DEVICE<device_number>/$BOOT` — associates to bootloader, exact filename depends on the filesystem the device uses
# Drivers
Filesystem driver is just an executable that can do file I/O to one specific filesystem it supports.
Filesystem drivers, just as regular TVDOS drivers, resides in `<root>/TVDOS/DRIVERS/`
# Commands
## cp
`[cp|copy] <source> <destination>`
Executes following command:
```
<filesystem>.fs cp <source> <dest>
```
## mv
`[mv|move] <from> <to>`
Executes following command:
```
<filesystem>.fs mv <from> <to>
```
## touch
`touch <path>`
Executes following command:
```
<filesystem>.fs touch <path>
```
## format
`format -f [tsvm|flat|tree] [ -b <path_to_bootloader> ] <device_number>`
Executes following command:
```
<filesystem>.fs format <device_number> [ <path_to_bootloader> ]
```

104
assets/disk0/tvdos/gl.js Normal file
View File

@@ -0,0 +1,104 @@
/*
TVDOS Graphics Library
Has no affiliation with OpenGL by Khronos Group
*/
var GL = {};
// bytes should be able to handle both JSArray and Java ByteArray (toString = "[B")?
GL.Texture = function(w, h, bytes) {
this.width = w;
this.height = h;
this.texData = bytes;
if (!Array.isArray(bytes) && !bytes.toString().startsWith("[B")) {
throw "Texture data is not an instance of array";
}
};
GL.SpriteSheet = function(tilew, tileh, tex) {
this.tileWidth = tilew;
this.tileHeight = tileh;
this.texture = tex;
if (!tex instanceof GL.Texture) {
throw "Texture is not an instance of GL.Texture";
}
this.getOffX = function(x) { // THIS, or: GL.SpriteSheet.prototype.getOffX
var tx = this.tileWidth * x;
if (tx + this.tileWidth > this.texture.width) throw "Sprite x-offset of "+tx+" is greater than sprite width "+this.texture.width;
return tx;
};
this.getOffY = function(y) {
var ty = this.tileHeight * y;
if (ty + this.tileHeight > this.texture.height) throw "Sprite y-offset of "+ty+" is greater than sprite height "+this.texture.height;
return ty;
};
};
GL.drawTexPattern = function(texture, x, y, width, height) {
for (var yy = 0; yy < height; yy++) {
for (var xx = 0; xx < width; xx++) {
var tx = xx % texture.width;
var ty = yy % texture.height;
var c = texture.texData[ty * texture.width + tx];
graphics.plotPixel(x + xx, y + yy, c);
}
}
};
GL.drawTexPatternOver = function(texture, x, y, width, height) {
for (var yy = 0; yy < height; yy++) {
for (var xx = 0; xx < width; xx++) {
var tx = xx % texture.width;
var ty = yy % texture.height;
var c = texture.texData[ty * texture.width + tx];
if ((c & 255) != 255) {
graphics.plotPixel(x + xx, y + yy, c);
}
}
}
};
/*
* Draws a texture verbatim - color of 255 will be written to the screen buffer
*/
GL.drawTexImage = function(texture, x, y) {
GL.drawTexPattern(texture, x, y, texture.width, texture.height);
};
/*
* Draws texture with blitting - color of 255 will pass-thru what's already on the screen buffer
*/
GL.drawTexImageOver = function(texture, x, y) {
for (var ty = 0; ty < texture.height; ty++) {
for (var tx = 0; tx < texture.width; tx++) {
var c = texture.texData[ty * texture.width + tx];
if ((c & 255) != 255) {
graphics.plotPixel(x + tx, y + ty, c);
}
}
}
};
GL.drawSprite = function(sheet, xi, yi, x, y) {
var offx = sheet.getOffX(xi);
var offy = sheet.getOffY(yi);
for (var ty = 0; ty < sheet.tileHeight; ty++) {
for (var tx = 0; tx < sheet.tileWidth; tx++) {
var c = sheet.texture.texData[(ty + offy) * sheet.texture.width + (tx + offx)];
graphics.plotPixel(x + tx, y + ty, c);
}
}
};
GL.drawSpriteOver = function(sheet, xi, yi, x, y) {
var offx = sheet.getOffX(xi);
var offy = sheet.getOffY(yi);
for (var ty = 0; ty < sheet.tileHeight; ty++) {
for (var tx = 0; tx < sheet.tileWidth; tx++) {
var c = sheet.texture.texData[(ty + offy) * sheet.texture.width + (tx + offx)];
if ((c & 255) != 255) {
graphics.plotPixel(x + tx, y + ty, c);
}
}
}
};
Object.freeze(GL); // this returns frozen 'GL'