diff --git a/presets/basic/23match.bas b/presets/basic/23match.bas new file mode 100644 index 00000000..1f240479 --- /dev/null +++ b/presets/basic/23match.bas @@ -0,0 +1,42 @@ +100 REM***23 MATCHES +110 LET M=23 +115 PRINT +120 PRINT "WE START WITH 23 MATCHES. WHEN IT IS YOUR" +130 PRINT "TURN, YOU MAY TAKE 1, 2, OR 3 MATCHES. THE" +140 PRINT "ONE WHO MUST TAKE THE LAST MATCH LOSES." +150 PRINT +200 REM***THE HUMAN MOVES +205 PRINT +210 PRINT "THERE ARE NOW";M;"MATCHES." +215 PRINT +220 PRINT "HOW MANY MATCHES DO YOU TAKE"; +230 INPUT H +240 IF H>M THEN 260 +250 IF H=INT(H) THEN 252 +251 GOTO 260 +252 IF H>0 THEN 254 +253 GOTO 260 +254 IF H<4 THEN 280 +260 PRINT "YOU CHEATED! I'LL GIVE YOU ANOTHER CHANCE." +270 GOTO 215 +280 LET M=M-H +290 IF M=0 THEN 410 +300 REM***THE COMPUTER MOVES +310 LET R=M-4*INT(M/4) +320 IF R<>1 THEN 350 +330 LET C=INT(3*RND(0))+1 +340 GOTO 360 +350 LET C=(R+3)-4*INT((R+3)/4) +360 LET M=M-C +370 IF M=0 THEN 440 +375 PRINT +380 PRINT "I TOOK";C;"MATCHES." +390 GOTO 200 +400 REM***SOMEBODY ONE(SEE LINES 290 AND 370) +410 PRINT +420 PRINT "I WON!!! BETTER LUCK NEXT TIME." +430 GOTO 100 +440 PRINT +450 PRINT "O.K. SO YOU WON. LET'S PLAY AGAIN." +460 GOTO 100 +999 END diff --git a/presets/basic/hamurabi.bas b/presets/basic/hamurabi.bas new file mode 100644 index 00000000..5e740067 --- /dev/null +++ b/presets/basic/hamurabi.bas @@ -0,0 +1,121 @@ +10 REM *** CONVERTED FROM THE ORIGINAL FOCAL PROGRAM AND MODIFIED +20 REM *** FOR EDUSYSTEM 70 BY DAVID AHL, DIGITAL +30 REM *** MODIFIED FOR 8K MICROSOFT BASIC BY PETER TURNBULL +80 PRINT "TRY YOUR HAND AT GOVERNING ANCIENT SUMERIA" +85 PRINT "SUCCESSFULLY FOR A 10-YR TERM OF OFFICE.":PRINT +90 REM RANDOMIZE REMOVED +95 D1=0:P1=0 +110 Z=0:P=95:S=2800:H=3000:E=H-S +120 Y=3:A=H/Y:I=5:Q=1 +210 D=0 +215 PRINT:PRINT:PRINT "HAMURABI: I BEG TO REPORT TO YOU,":Z=Z+1 +217 PRINT "IN YEAR"Z","D"PEOPLE STARVED,"I"CAME TO THE CITY." +218 P=P+I +227 IF Q>0 THEN 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 "RATS ATE"E"BUSHELS." +260 PRINT "YOU NOW HAVE"S"BUSHELS IN STORE.":PRINT +270 IF Z=11 THEN 860 +310 C=INT(10*RND(1)):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:IF Q<0 THEN 850 +322 IF Y*Q<=S THEN 330 +323 GOSUB 710 +324 GOTO 320 +330 IF Q=0 THEN 340 +331 A=A+Q:S=S-Y*Q:C=0 +334 GOTO 400 +340 PRINT "HOW MANY ACRES DO YOU WISH TO SELL"; +341 INPUT Q:IF Q<0 THEN 850 +342 IF QC/2 THEN 530 +523 REM *** THE 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 *** HORRORS, A 15% CHANCE OF PLAGUE +542 Q=INT(10*(2*RND(1)-.3)) +550 IF P.45*P THEN 560 +553 P1=((Z-1)*P1+D*100/P)/Z +555 P=C:D1=D1+D:GOTO 215 +560 PRINT: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' !!":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: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 AVERAGE, I.E., A TOTAL OF" +865 PRINT D1"PEOPLE DIED!!":L=A/P +870 PRINT "YOU STARTED WITH 10 ACRES PER PERSON AND ENDED WITH" +875 PRINT L"ACRES PER PERSON.":PRINT +880 IF P1>33 THEN 565 +885 IF L<7 THEN 565 +890 IF P1>10 THEN 940 +892 IF L<9 THEN 940 +895 IF P1>3 THEN 960 +896 IF L<10 THEN 960 +900 PRINT "A FANTASTIC PERFORMANCE!!! CHARLEMANGE, DISRAELI, AND" +905 PRINT "JEFFERSON COMBINED COULD NOT HAVE DONE BETTER!":GOTO 990 +940 PRINT "YOUR HEAVY-HANDED PERFORMANCE SMACKS OF NERO AND IVAN IV." +945 PRINT "THE PEOPLE (REMAINING) FIND YOU AN UNPLEASANT RULER, AND," +950 PRINT "FRANKLY, HATE YOUR GUTS!":GOTO 990 +960 PRINT "YOUR PERFORMANCE COULD HAVE BEEN SOMEWHAT BETTER, BUT" +965 PRINT "REALLY WASN'T TOO BAD AT ALL. "; +966 PRINT INT(P*.8*RND(1));"PEOPLE WOULD" +970 PRINT "DEARLY LIKE TO SEE YOU ASSASSINATED BUT WE ALL HAVE OUR" +975 PRINT "TRIVIAL PROBLEMS." +990 PRINT:FOR N=1 TO 10:PRINT CHR$(7);:NEXT N +995 PRINT "SO LONG FOR NOW.":PRINT +999 END diff --git a/presets/basic/sieve.bas b/presets/basic/sieve.bas new file mode 100644 index 00000000..a4ed4c7b --- /dev/null +++ b/presets/basic/sieve.bas @@ -0,0 +1,16 @@ +005 OPTION CPUSPEED MAX +010 REM***A PRIME NUMBER SIEVE BENCHMARK +020 T = TIMER +025 N = 8191 +030 DIM F(N+3) +040 FOR I = 0 TO N : F(I) = 1 : NEXT I +050 S = N +060 FOR I = 0 TO S +070 IF F(I) = 0 THEN GOTO 110 +080 P = I+I+3 +090 FOR K = I+P TO S STEP P : F(K) = 0 : NEXT K +100 C = C+1 +110 NEXT I +120 T = TIMER-T +130 PRINT C;" PRIMES FOUND IN ";T;" SECONDS" +150 END diff --git a/presets/basic/wumpus.bas b/presets/basic/wumpus.bas new file mode 100644 index 00000000..650312f0 --- /dev/null +++ b/presets/basic/wumpus.bas @@ -0,0 +1,221 @@ +5 REM *** HUNT THE WUMPUS *** +10 DIM P(5) +15 PRINT "INSTRUCTIONS (Y-N)"; +20 INPUT I$ +25 IF (I$ = "N") OR (I$ = "N") THEN 35 +30 GOSUB 375 +35 GOTO 80 +80 REM *** SET UP CAVE (DODECAHEDRAL NODE LIST) *** +85 DIM S(20,3) +90 FOR J = 1 TO 20 +95 FOR K = 1 TO 3 +100 READ S(J,K) +105 NEXT K +110 NEXT J +115 DATA 2,5,8,1,3,10,2,4,12,3,5,14,1,4,6 +120 DATA 5,7,15,6,8,17,1,7,9,8,10,18,2,9,11 +125 DATA 10,12,19,3,11,13,12,14,20,4,13,15,6,14,16 +130 DATA 15,17,20,7,16,18,9,17,19,11,18,20,13,16,19 +135 DEF FNA(X)=INT(20*RND(1))+1 +140 DEF FNB(X)=INT(3*RND(1))+1 +145 DEF FNC(X)=INT(4*RND(1))+1 +150 REM *** LOCATE L ARRAY ITEMS *** +155 REM *** 1-YOU, 2-WUMPUS, 3&4-PITS, 5&6-BATS *** +160 DIM L(6) +165 DIM M(6) +170 FOR J = 1 TO 6 +175 L(J) = FNA(0) +180 M(J) = L(J) +185 NEXT J +190 REM *** CHECK FOR CROSSOVERS (IE L(1)=L(2), ETC) *** +195 FOR J = 1 TO 6 +200 FOR K = 1 TO 6 +205 IF J = K THEN 215 +210 IF L(J) = L(K) THEN 170 +215 NEXT K +220 NEXT J +225 REM *** SET NO. OF ARROWS *** +230 A = 5 +235 L = L(1) +240 REM *** RUN THE GAME *** +245 PRINT "HUNT THE WUMPUS" +250 REM *** HAZARD WARNING AND LOCATION *** +255 GOSUB 585 +260 REM *** MOVE OR SHOOT *** +265 GOSUB 670 +270 ON O GOTO 280,300 +275 REM *** SHOOT *** +280 GOSUB 715 +285 IF F = 0 THEN 255 +290 GOTO 310 +295 REM *** MOVE *** +300 GOSUB 975 +305 IF F = 0 THEN 255 +310 IF F > 0 THEN 335 +315 REM *** LOSE *** +320 PRINT "HA HA HA - YOU LOSE!" +325 GOTO 340 +330 REM *** WIN *** +335 PRINT "HEE HEE HEE - THE WUMPUS'LL GET YOU NEXT TIME!!" +340 FOR J = 1 TO 6 +345 L(J) = M(J) +350 NEXT J +355 PRINT "SAME SETUP (Y-N)"; +360 INPUT I$ +365 IF (I$ <> "Y") AND (I$ <> "Y") THEN 170 +370 GOTO 230 +375 REM *** INSTRUCTIONS *** +380 PRINT "WELCOME TO 'HUNT THE WUMPUS'" +385 PRINT " THE WUMPUS LIVES IN A CAVE OF 20 ROOMS. EACH ROOM" +390 PRINT "HAS 3 TUNNELS LEADING TO OTHER ROOMS. (LOOK AT A" +395 PRINT "DODECAHEDRON TO SEE HOW THIS WORKS-IF YOU DON'T KNOW" +400 PRINT "WHAT A DODECAHEDRON IS, ASK SOMEONE)" +405 PRINT +410 PRINT " HAZARDS:" +415 PRINT " BOTTOMLESS PITS - TWO ROOMS HAVE BOTTOMLESS PITS IN THEM" +420 PRINT " IF YOU GO THERE, YOU FALL INTO THE PIT (& LOSE!)" +425 PRINT " SUPER BATS - TWO OTHER ROOMS HAVE SUPER BATS. IF YOU" +430 PRINT " GO THERE, A BAT GRABS YOU AND TAKES YOU TO SOME OTHER" +435 PRINT " ROOM AT RANDOM. (WHICH MAY BE TROUBLESOME)" +440 INPUT "HIT RETURN TO CONTINUE";A$ +445 PRINT " WUMPUS:" +450 PRINT " THE WUMPUS IS NOT BOTHERED BY HAZARDS (HE HAS SUCKER" +455 PRINT " FEET AND IS TOO BIG FOR A BAT TO LIFT). USUALLY" +460 PRINT " HE IS ASLEEP. TWO THINGS WAKE HIM UP: YOU SHOOTING AN" +465 PRINT "ARROW OR YOU ENTERING HIS ROOM." +470 PRINT " IF THE WUMPUS WAKES HE MOVES (P=.75) ONE ROOM" +475 PRINT " OR STAYS STILL (P=.25). AFTER THAT, IF HE IS WHERE YOU" +480 PRINT " ARE, HE EATS YOU UP AND YOU LOSE!" +485 PRINT +490 PRINT " YOU:" +495 PRINT " EACH TURN YOU MAY MOVE OR SHOOT A CROOKED ARROW" +500 PRINT " MOVING: YOU CAN MOVE ONE ROOM (THRU ONE TUNNEL)" +505 PRINT " ARROWS: YOU HAVE 5 ARROWS. YOU LOSE WHEN YOU RUN OUT" +510 PRINT " EACH ARROW CAN GO FROM 1 TO 5 ROOMS. YOU AIM BY TELLING" +515 PRINT " THE COMPUTER THE ROOM#S YOU WANT THE ARROW TO GO TO." +520 PRINT " IF THE ARROW CAN'T GO THAT WAY (IF NO TUNNEL) IT MOVES" +525 PRINT " AT RANDOM TO THE NEXT ROOM." +530 PRINT " IF THE ARROW HITS THE WUMPUS, YOU WIN." +535 PRINT " IF THE ARROW HITS YOU, YOU LOSE." +540 INPUT "HIT RETURN TO CONTINUE";A$ +545 PRINT " WARNINGS:" +550 PRINT " WHEN YOU ARE ONE ROOM AWAY FROM A WUMPUS OR HAZARD," +555 PRINT " THE COMPUTER SAYS:" +560 PRINT " WUMPUS: 'I SMELL A WUMPUS'" +565 PRINT " BAT : 'BATS NEARBY'" +570 PRINT " PIT : 'I FEEL A DRAFT'" +575 PRINT +580 RETURN +585 REM *** PRINT LOCATION & HAZARD WARNINGS *** +590 PRINT +595 FOR J = 2 TO 6 +600 FOR K = 1 TO 3 +605 IF S(L(1),K) <> L(J) THEN 640 +610 ON J-1 GOTO 615,625,625,635,635 +615 PRINT "I SMELL A WUMPUS!" +620 GOTO 640 +625 PRINT "I FEEL A DRAFT" +630 GOTO 640 +635 PRINT "BATS NEARBY!" +640 NEXT K +645 NEXT J +650 PRINT "YOU ARE IN ROOM ";L(1) +655 PRINT "TUNNELS LEAD TO ";S(L,1);" ";S(L,2);" ";S(L,3) +660 PRINT +665 RETURN +670 REM *** CHOOSE OPTION *** +675 PRINT "SHOOT OR MOVE (S-M)"; +680 INPUT I$ +685 IF (I$ <> "S") AND (I$ <> "S") THEN 700 +690 O = 1 +695 RETURN +700 IF (I$ <> "M") AND (I$ <> "M") THEN 675 +705 O = 2 +710 RETURN +715 REM *** ARROW ROUTINE *** +720 F = 0 +725 REM *** PATH OF ARROW *** +735 PRINT "NO. OF ROOMS (1-5)"; +740 INPUT J9 +745 IF J9 < 1 THEN 735 +750 IF J9 > 5 THEN 735 +755 FOR K = 1 TO J9 +760 PRINT "ROOM #"; +765 INPUT P(K) +770 IF K <= 2 THEN 790 +775 IF P(K) <> P(K-2) THEN 790 +780 PRINT "ARROWS AREN'T THAT CROOKED - TRY ANOTHER ROOM" +785 GOTO 760 +790 NEXT K +795 REM *** SHOOT ARROW *** +800 L = L(1) +805 FOR K = 1 TO J9 +810 FOR K1 = 1 TO 3 +815 IF S(L,K1) = P(K) THEN 895 +820 NEXT K1 +825 REM *** NO TUNNEL FOR ARROW *** +830 L = S(L,FNB(1)) +835 GOTO 900 +840 NEXT K +845 PRINT "MISSED" +850 L = L(1) +855 REM *** MOVE WUMPUS *** +860 GOSUB 935 +865 REM *** AMMO CHECK *** +870 A = A-1 +875 IF A > 0 THEN 885 +880 F = -1 +885 RETURN +890 REM *** SEE IF ARROW IS AT L(1) OR AT L(2) +895 L = P(K) +900 IF L <> L(2) THEN 920 +905 PRINT "AHA! YOU GOT THE WUMPUS!" +910 F = 1 +915 RETURN +920 IF L <> L(1) THEN 840 +925 PRINT "OUCH! ARROW GOT YOU!" +930 GOTO 880 +935 REM *** MOVE WUMPUS ROUTINE *** +940 K = FNC(0) +945 IF K = 4 THEN 955 +950 L(2) = S(L(2),K) +955 IF L(2) <> L THEN 970 +960 PRINT "TSK TSK TSK - WUMPUS GOT YOU!" +965 F = -1 +970 RETURN +975 REM *** MOVE ROUTINE *** +980 F = 0 +985 PRINT "WHERE TO"; +990 INPUT L +995 IF L < 1 THEN 985 +1000 IF L > 20 THEN 985 +1005 FOR K = 1 TO 3 +1010 REM *** CHECK IF LEGAL MOVE *** +1015 IF S(L(1),K) = L THEN 1045 +1020 NEXT K +1025 IF L = L(1) THEN 1045 +1030 PRINT "NOT POSSIBLE -"; +1035 GOTO 985 +1040 REM *** CHECK FOR HAZARDS *** +1045 L(1) = L +1050 REM *** WUMPUS *** +1055 IF L <> L(2) THEN 1090 +1060 PRINT "... OOPS! BUMPED A WUMPUS!" +1065 REM *** MOVE WUMPUS *** +1070 GOSUB 940 +1075 IF F = 0 THEN 1090 +1080 RETURN +1085 REM *** PIT *** +1090 IF L = L(3) THEN 1100 +1095 IF L <> L(4) THEN 1120 +1100 PRINT "YYYYIIIIEEEE . . . FELL IN PIT" +1105 F = -1 +1110 RETURN +1115 REM *** BATS *** +1120 IF L = L(5) THEN 1130 +1125 IF L <> L(6) THEN 1145 +1130 PRINT "ZAP--SUPER BAT SNATCH! ELSEWHEREVILLE FOR YOU!" +1135 L = FNA(1) +1140 GOTO 1045 +1145 RETURN +1150 END diff --git a/src/codemirror/basic.js b/src/codemirror/basic.js index 0039acbf..10bedb99 100644 --- a/src/codemirror/basic.js +++ b/src/codemirror/basic.js @@ -95,7 +95,7 @@ CodeMirror.defineMode("basic", function(conf, parserConf) { // Octal else if (stream.match(/^&O[0-7]+/i)) { intLiteral = true; } // Decimal - else if (stream.match(/^[1-9]\d*F?/)) { + else if (stream.match(/^\d+F?/)) { // Decimal literals may be "imaginary" stream.eat(/J/i); // TODO - Can you have imaginary longs? diff --git a/src/common/basic/compiler.ts b/src/common/basic/compiler.ts index e9ab587c..9caf2d76 100644 --- a/src/common/basic/compiler.ts +++ b/src/common/basic/compiler.ts @@ -240,6 +240,7 @@ export interface BASICOptions { sparseArrays : boolean; // true == don't require DIM for arrays (TODO) printZoneLength : number; // print zone length numericPadding : boolean; // " " or "-" before and " " after numbers? + outOfOrderNext : boolean; // can we skip a NEXT statement? (can't interleave tho) multipleNextVars : boolean; // NEXT Y,X (TODO) ifElse : boolean; // IF...ELSE construct bitwiseLogic : boolean; // -1 = TRUE, 0 = FALSE, AND/OR/NOT done with bitwise ops @@ -249,6 +250,7 @@ export interface BASICOptions { validKeywords : string[]; // valid keywords (or null for accept all) validFunctions : string[]; // valid functions (or null for accept all) validOperators : string[]; // valid operators (or null for accept all) + commandsPerSec? : number; // how many commands per second? } ///// BASIC PARSER @@ -743,17 +745,22 @@ export class BASICParser { return stmt; } parseOptions(stmt: OPTION_Statement) { + var arg = stmt.optargs[0]; switch (stmt.optname) { case 'BASE': - let base = parseInt(stmt.optargs[0]); + let base = parseInt(arg); if (base == 0 || base == 1) this.opts.defaultArrayBase = base; else this.compileError("OPTION BASE can only be 0 or 1."); break; case 'DIALECT': - let dname = stmt.optargs[0] || ""; + let dname = arg || ""; let dialect = DIALECTS[dname.toUpperCase()]; if (dialect) this.opts = dialect; - else this.compileError(`The dialect named "${dname}" is not supported by this compiler.`); + else this.compileError(`OPTION DIALECT ${dname} is not supported by this compiler.`); + break; + case 'CPUSPEED': + if (!(this.opts.commandsPerSec = Math.min(1e7, arg=='MAX' ? Infinity : parseFloat(arg)))) + this.compileError(`OPTION CPUSPEED takes a positive number or MAX.`); break; default: this.compileError(`OPTION ${stmt.optname} is not supported by this compiler.`); @@ -825,6 +832,7 @@ export const ECMA55_MINIMAL : BASICOptions = { printZoneLength : 15, numericPadding : true, checkOverflow : true, + outOfOrderNext : false, multipleNextVars : false, ifElse : false, bitwiseLogic : false, @@ -853,6 +861,7 @@ export const ALTAIR_BASIC40 : BASICOptions = { printZoneLength : 15, numericPadding : true, checkOverflow : true, + outOfOrderNext : true, multipleNextVars : true, // TODO: not supported ifElse : true, bitwiseLogic : true, @@ -883,6 +892,7 @@ export const APPLESOFT_BASIC : BASICOptions = { printZoneLength : 16, numericPadding : false, checkOverflow : true, + outOfOrderNext : true, multipleNextVars : false, ifElse : false, bitwiseLogic : false, @@ -911,6 +921,7 @@ export const MAX8_BASIC : BASICOptions = { printZoneLength : 15, numericPadding : false, checkOverflow : true, + outOfOrderNext : true, multipleNextVars : true, ifElse : true, bitwiseLogic : true, diff --git a/src/common/basic/runtime.ts b/src/common/basic/runtime.ts index adc5c62f..759c512d 100644 --- a/src/common/basic/runtime.ts +++ b/src/common/basic/runtime.ts @@ -42,7 +42,7 @@ export class BASICRuntime { vars : {}; arrays : {}; defs : {}; - forLoops : {next:(name:string) => void}[]; + forLoops : {varname:string, next:(name:string) => void}[]; returnStack : number[]; column : number; @@ -79,7 +79,7 @@ export class BASICRuntime { this.datums.push({value:value}); }); }); - // TODO: compile statements? + // compile statements ahead of time line.stmts.forEach((stmt) => this.compileStatement(stmt)); }); // try to resume where we left off after loading @@ -105,7 +105,7 @@ export class BASICRuntime { // if no valid function list, look for ABC...() functions in prototype if (!fnames) fnames = Object.keys(BASICRuntime.prototype).filter((name) => /^[A-Z]{3,}[$]?$/.test(name)); var dict = {}; - for (var fn of fnames) dict[fn] = this[fn].bind(this); + for (var fn of fnames) if (this[fn]) dict[fn] = this[fn].bind(this); return dict; } @@ -341,6 +341,7 @@ export class BASICRuntime { this.vars[forname] = init; if (this.trace) console.log(`FOR ${forname} = ${init} TO ${targ} STEP ${step}`); this.forLoops.unshift({ + varname: forname, next: (nextname:string) => { if (nextname && forname != nextname) this.runtimeError(`I executed NEXT "${nextname}", but the last FOR was for "${forname}".`) @@ -358,8 +359,16 @@ export class BASICRuntime { nextForLoop(name) { var fl = this.forLoops[0]; + if (fl != null && name != null && fl.varname != name) { + if (!this.opts.outOfOrderNext) this.dialectError(`execute out-of-order NEXT statements`); + while (fl) { + if (fl.varname == name) break; + this.forLoops.shift(); + fl = this.forLoops[0]; + } + } if (!fl) this.runtimeError(`I couldn't find a FOR for this NEXT.`) - else fl.next(name); + fl.next(name); } // converts a variable to string/number based on var name @@ -557,7 +566,7 @@ export class BASICRuntime { } do__RESTORE() { - this.dataptr = 0; + return `this.dataptr = 0`; } do__END() { @@ -806,6 +815,9 @@ export class BASICRuntime { TAN(arg : number) : number { return this.checkNum(Math.tan(arg)); } + TIMER() : number { + return Date.now() / 1000; + } VAL(arg : string) : number { var n = parseFloat(this.checkString(arg)); return isNaN(n) ? 0 : n; // TODO? altair works this way diff --git a/src/ide/views.ts b/src/ide/views.ts index 898266ef..ac6414d5 100644 --- a/src/ide/views.ts +++ b/src/ide/views.ts @@ -74,6 +74,10 @@ const MODEDEFS = { basic: { noLineNumbers: true, noGutters: true }, // TODO: not used? } +export var textMapFunctions = { + input: null +}; + export class SourceEditor implements ProjectView { constructor(path:string, mode:string) { this.path = path; @@ -150,6 +154,10 @@ export class SourceEditor implements ProjectView { }); // set editor mode for highlighting, etc this.editor.setOption("mode", this.mode); + // change text? + this.editor.on('beforeChange', (cm, chgobj) => { + if (textMapFunctions.input && chgobj.text) chgobj.text = chgobj.text.map(textMapFunctions.input); + }); } diff --git a/src/platform/basic.ts b/src/platform/basic.ts index e450fe48..36b748a3 100644 --- a/src/platform/basic.ts +++ b/src/platform/basic.ts @@ -2,12 +2,17 @@ import { Platform, BreakpointCallback } from "../common/baseplatform"; import { PLATFORMS, AnimationTimer, EmuHalt } from "../common/emu"; import { loadScript } from "../ide/ui"; +import * as views from "../ide/views"; import { BASICRuntime } from "../common/basic/runtime"; import { BASICProgram } from "../common/basic/compiler"; import { TeleTypeWithKeyboard } from "../common/teletype"; const BASIC_PRESETS = [ - { id: 'hello.bas', name: 'Hello World' } + { id: 'hello.bas', name: 'Hello World' }, + { id: 'sieve.bas', name: 'Sieve Benchmark' }, + { id: '23match.bas', name: '23 Matches' }, + { id: 'wumpus.bas', name: 'Hunt The Wumpus' }, + { id: 'hamurabi.bas', name: 'Hammurabi' }, ]; class BASICPlatform implements Platform { @@ -16,7 +21,6 @@ class BASICPlatform implements Platform { runtime: BASICRuntime; timer: AnimationTimer; tty: TeleTypeWithKeyboard; - ips: number = 1000; clock: number = 0; hotReload: boolean = false; debugstop: boolean = false; // TODO: should be higher-level support @@ -75,7 +79,8 @@ class BASICPlatform implements Platform { animate() { if (this.tty.isBusy()) return; - this.clock += this.ips/60; + var ips = this.program.opts.commandsPerSec || 1000; + this.clock += ips / 60; while (!this.runtime.exited && this.clock-- > 0) { this.advance(); } @@ -112,6 +117,7 @@ class BASICPlatform implements Platform { this.program = data; this.runtime.load(data); this.tty.uppercaseOnly = this.program.opts.uppercaseOnly; + views.textMapFunctions.input = this.program.opts.uppercaseOnly ? (s) => s.toUpperCase() : null; // only reset if we exited, otherwise we try to resume if (!this.hotReload || didExit) this.reset(); }