-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCodeInterpreter.cs
1720 lines (1687 loc) · 56.3 KB
/
CodeInterpreter.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using System.Collections.Generic;
using System.Linq;
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.PortableExecutable;
using static System.Runtime.InteropServices.JavaScript.JSType;
using System.Runtime.Intrinsics.X86;
using System.ComponentModel.Design;
using System.Text.RegularExpressions;
using static System.Net.Mime.MediaTypeNames;
using System.Runtime.Intrinsics.Arm;
using System.Xml.Linq;
namespace NewPaloAltoTB;
/**
* Creating a Windows version of Palo Alto Tiny Basic
*/
internal partial class CodeInterpreter {
internal int LineNumber;
internal int CurrentLineOrd;
internal int NewLineOrd = -1;
internal string ImmediateLine = "";
//internal int LinePosition;
internal string? CurrentLine;
//internal short LineLabel;
internal bool OutputSwitch = true;
internal List<(int linenum, string src)> ProgramSource = [];
internal Dictionary<int, int> LineLocations = []; //looks up ordinal position of a basic line# in Program
internal CodeParser Parser = CodeParser.Shared;
internal Expression ExpressionService = Expression.Shared;
internal bool StopRequested = false;
internal bool ImmediateMode = false;
internal string CurrentFile => ImmediateMode ? "" : "FILE"; //in future will contain actual filename.bas path
internal static CodeInterpreter Shared => shared.Value;
private static readonly Lazy<CodeInterpreter> shared = new(() => new CodeInterpreter());
/// <summary>
/// Delete a given line from the program buffer (by vb line#).
/// To do: implement deletion of range of lines.
/// </summary>
/// <param name="lineNumber"></param>
/// <returns></returns>
internal bool DeleteLine(int lineNumber) {
int ordinalLinePos;
if (LineLocations.TryGetValue(lineNumber, out ordinalLinePos)) {
ProgramSource.RemoveAt(ordinalLinePos);
LineLocations.Remove(lineNumber);
foreach (var kv in LineLocations.Where(e => e.Key > lineNumber)) {
LineLocations[kv.Key] -= 1;
}
return true;
} else {
return false;
}
}
/// <summary>
/// Add or update a line in the program buffer.
/// </summary>
/// <param name="lineNumber"></param>
/// <param name="lineContents"></param>
internal void StoreLine(short lineNumber, string lineContents) {
//insert or update line
var ListPosition = LineLocations.Where(e => e.Key >= lineNumber).AsQueryable().FirstOrDefault(new KeyValuePair<int, int>(-1, (short)-1));
if (ListPosition.Key == lineNumber) {
//replace found
ProgramSource[ListPosition.Value] = (lineNumber, lineContents);
} else if (ListPosition.Key == -1) {
//at end
LineLocations[lineNumber] = ProgramSource.Count;
ProgramSource.Add((lineNumber, lineContents));
} else {
//insert before found
ProgramSource.Insert(ListPosition.Value, (lineNumber, lineContents));
foreach (var kv in LineLocations.Where(e => e.Key > lineNumber)) {
LineLocations[kv.Key] += 1;
}
}
}
/* internal void CrLf() {
// OutChar('\r');
// OutChar('\n');
//} */
/* internal void OutChar(char c) {
// if (OutputSwitch == false) {
// return;
// }
// Console.Write(c);
// if (c == '\r') {
// Console.Write('\n');
// }
//} */
internal bool RunLine(string line, int lineNumber) {
StopRequested = false;
NewLineOrd = -1;
Parser.SetLine(line: line, linePosition: 0, lineNumber: lineNumber);
//TODO: figure out how to handle 1st return in case like '100 GOSUB 2000; GOSUB 3000'
var statementSucceeded = true;
while (statementSucceeded && !Parser.EoL() && NewLineOrd == -1) {
statementSucceeded = RunStatement();
_ = Parser.ScanRegex("^\\s*;");
if (StopRequested) {
break;
}
}
return statementSucceeded;
}
internal bool Run(bool Immediate) {
var rslt = true;
ImmediateMode = Immediate;
StopRequested = false;
CurrentLineOrd = Immediate ? -1 : 0;
NewLineOrd = -1;
try {
while (true) {
if (CurrentLineOrd >= 0) {
(LineNumber, CurrentLine) = ProgramSource[CurrentLineOrd];
} else {
(LineNumber, CurrentLine) = (0, ImmediateLine);
}
var oldCurrentLineOrd = CurrentLineOrd;
rslt = RunLine(CurrentLine, LineNumber);
if (LineNumber == 0) {
break;
}
if (NewLineOrd == -1) {
//advance to next line
CurrentLineOrd++;
if (CurrentLineOrd >= ProgramSource.Count) {
break;
}
} else {
// RunLine caused a new next line, i.e. GoTo, GoSub, Next, Return or similar.
// (certain statements, when run, will directly change the current line and lineposition)
// so do nothing to NewLineOrd. This is to jump into mid-line, after a ';'.
CurrentLineOrd = NewLineOrd;
}
if (StopRequested) {
break;
}
}
//for now, retain these - may want some debug dump or retry/resume command
//LineNumber = 0;
//LinePosition = 0;
} catch (RuntimeException ex) {
Console.WriteLine(ex.MessageDetail + "\\n" + ex.ToString());
throw;
} catch (Exception ex) {
Console.WriteLine(ex.Message + "\\n" + ex.ToString());
throw;
}
return rslt;
}
internal enum StatementCode {
LetStatement,
InputStatement,
PrintStatement,
RemStatement,
WaitStatement,
StopStatement,
//OutStatement,
//PokeStatement,
IfStatement,
ForStatement,
NextStatement,
GotoStatement,
GosubStatement,
ReturnStatement,
DimStatement,
/* future possibles:
dim, global, sub, function, import/inherit/etc to access caller-scoped vars
graphics subs/funcs
file subs/funcs
debug tools (trace on/off, dump vars, watch vars, break, save state?)
*/
}
internal bool RunStatement() {
var rslt = false;
var whichStmt = Parser.ScanStringTableEntry([
"LET",
"INPUT",
"PRINT",
"REM",
"WAIT",
"STOP",
//"OUT",
//"POKE",
"IF",
"FOR",
"NEXT",
"GOTO",
"GOSUB",
"RETURN",
"DIM",
]);
switch ((StatementCode?)whichStmt) {
case StatementCode.LetStatement:
rslt = RunAssignmentStatement();
break;
case StatementCode.InputStatement:
rslt = RunInputStatement();
break;
case StatementCode.PrintStatement:
rslt = RunPrintStatement();
break;
case StatementCode.RemStatement:
Parser.LinePosition = Parser.Line.Length;
rslt = true;
break;
case StatementCode.WaitStatement:
rslt = RunWaitStatement();
break;
case StatementCode.StopStatement:
StopRequested = true;
rslt = true;
break;
case StatementCode.IfStatement:
rslt = RunIfStatement();
break;
case StatementCode.ForStatement:
rslt = RunForStatement();
break;
case StatementCode.NextStatement:
rslt = RunNextStatement();
break;
case StatementCode.GotoStatement:
rslt = RunGotoStatement();
break;
case StatementCode.GosubStatement:
rslt = RunGosubStatement();
break;
case StatementCode.ReturnStatement:
rslt = RunReturnStatement();
break;
case StatementCode.DimStatement:
rslt = RunDimStatement();
break;
default:
rslt = RunAssignmentStatement();
break;
}
return rslt;
}
internal bool RunAssignmentStatement() {
var rslt = true;
while (true) {
if (!RunOneAssignment()) {
rslt = false;
break;
}
if (Parser.ScanRegex("^\\s*,\\s*") == null) {
break;
}
}
return rslt;
}
internal bool RunOneAssignment() {
var rslt = false;
var oldPos = Parser.LinePosition;
var lVal = Parser.ScanLValue();
if (lVal != null) {
if (Parser.ScanRegex("^\\s*=") != null) {
short value;
if (ExpressionService.TryEvaluateExpr(out value)) {
lVal!.Value = value;
rslt = true;
} else {
throw new RuntimeException("Expression expected.");
}
} else {
Parser.LinePosition = oldPos;
}
}
return rslt;
}
private int PrintNumWidth;
private bool DidPrintSomething;
internal bool RunPrintStatement() {
// PRINT (EXPR | #FORMAT | '/"STRINGLITERAL'/") [,another] [,?]
var rslt = true;
PrintNumWidth = 6; //changed by #formatexpression arguments
DidPrintSomething = false; //when true, a trailing comma is OK
while (true) {
if (TryPrintFormat() || TryPrintStringLiteral() || TryPrintNumber()) {
if (TrySkipComma()) {
//ready for next term, just repeat do loop
} else {
//no comma so no more terms expected, do output final crlf
Console.WriteLine();
break;
}
} else {
if (DidPrintSomething) {
//we only get here with Print term [,term...], (trailing ',' AFTER printing something)
break;
} else {
//trailing ',' without printing anything (or only processing format terms)!
throw new RuntimeException("Print statement will not print anything!");
}
}
}
return rslt;
}
internal bool TryPrintFormat() {
Parser.SkipSpaces();
if (Parser.ScanString("#")) {
short newWidth;
if (ExpressionService.TryEvaluateExpr(out newWidth)) {
PrintNumWidth = newWidth;
return true;
}
}
return false;
}
internal bool TryPrintNumber() {
short pValue;
if (ExpressionService.TryEvaluateExpr(out pValue)) {
Console.Write(pValue.ToString().PadLeft(PrintNumWidth)); // pValue.ToString($"{PrintNumWidth}:D"));
DidPrintSomething = true;
return true;
} else {
return false;
}
}
internal bool TryPrintStringLiteral() {
var sValue = Parser.ScanStringLiteral();
if (sValue != null) {
Console.Write(sValue);
DidPrintSomething = true;
return true;
}
return false;
}
internal short GetUserInputShort() {
short rslt;
while (true) {
var s = Console.ReadLine();
if (s == null) {
throw new RuntimeException("Unexpected end of file on console input.");
}
var val = CodeParser.StrToShort(s);
if (val.HasValue) {
rslt = val.Value;
break;
}
Console.WriteLine("Expected: number. Please try again.");
}
return rslt;
}
private bool RunInputStatement() {
var rslt = false;
while (true) {
var prompt = Parser.ScanStringLiteral();
if (prompt == null) {
prompt = "?";
} else {
Parser.SkipSpaces();
Parser.ScanString(","); //accept comma between prompt and variable name, i.e. INPUT "AGE?" A or INPUT "AGE?", A
}
var lVal = Parser.ScanLValue();
if (lVal == null) {
throw new RuntimeException("Variable name or array element expected.");
}
Console.WriteLine(prompt);
var inputVal = GetUserInputShort();
lVal!.Value = inputVal;
if (Parser.ScanChar(',', true) == null) {
break;
}
}
return true;
}
internal bool TrySkipComma() {
Parser.SkipSpaces();
return Parser.ScanString(",");
}
internal bool RunWaitStatement() {
var prompt = Parser.ScanStringLiteral() ?? "Press a key...";
Console.WriteLine(prompt);
Console.ReadKey();
return true;
}
internal bool RunIfStatement() {
var rslt = false;
short cond;
if (ExpressionService.TryEvaluateExpr(out cond)) {
rslt = true;
_ = Parser.ScanString("THEN"); // THEN is optional
if (cond == 0) {
Parser.LinePosition = Parser.Line.Length; //if condition is false, ignore rest of line
}
} else {
throw new RuntimeException("Invalid If expression.");
}
return rslt;
}
internal bool RunForStatement() {
//var = expr [down]to expr [step expr]
var varLValue = Parser.ScanLValue();
if (varLValue == null) {
throw new RuntimeException("For loop variable name not found.");
}
if (Parser.ScanRegex("^\\s*=") == null) {
throw new RuntimeException("For loop syntax: expected '='.");
}
short initValue;
if (!ExpressionService.TryEvaluateExpr(out initValue)) {
throw new RuntimeException("For loop: error in initial value expression.");
}
if (Parser.ScanRegex("^\\s*to") == null) {
throw new RuntimeException("For loop syntax: expected \" TO \".");
}
short limitValue;
if (!ExpressionService.TryEvaluateExpr(out limitValue)) {
throw new RuntimeException("For loop: error in limit value expression.");
}
short stepValue = 1;
if (Parser.ScanRegex("^\\s*step") != null) {
if (!ExpressionService.TryEvaluateExpr(out stepValue)) {
throw new RuntimeException("For loop: error in step value expression.");
}
} else {
if (limitValue < initValue) {
stepValue = -1;
}
}
//got a valid for statement!
ControlStack.Shared.ForLoopBegin(lValue: varLValue, initialVal: initValue, stepVal: stepValue, limitVal: limitValue);
return true;
}
internal bool RunNextStatement() {
var varName = Parser.ScanName();
if (varName == null) {
throw new RuntimeException("Expected: for loop variable name.");
}
ControlStack.Shared.ForLoopNext(varName: varName);
return true;
}
internal bool RunGotoStatement() {
var rslt = false;
short newLineNum;
if (ExpressionService.TryEvaluateExpr(out newLineNum)) {
int newOrd;
if (LineLocations.TryGetValue(newLineNum, out newOrd)) {
NewLineOrd = newOrd; //run loop will transfer to this line
Parser.LinePosition = Parser.Line.Length; //ignore rest of line (could complain if not empty or REM...)
rslt = true;
} else {
throw new RuntimeException($"Goto target line {newLineNum} not found.");
}
} else {
throw new RuntimeException("Goto target line number/expression not understood.");
}
return rslt;
}
internal bool RunGosubStatement() {
short newLineNum;
if (ExpressionService.TryEvaluateExpr(out newLineNum)) {
int newOrd;
if (LineLocations.TryGetValue(newLineNum, out newOrd)) {
NewLineOrd = newOrd; //run loop will transfer to this line
//Parser.LinePosition = Parser.Line.Length; //ignore rest of line (could complain if not empty or REM...)
Parser.ScanRegex("^\\s*;"); //skip statement separator
ControlStack.Shared.Gosub(newLineNum, newOrd); //push return address onto control stack
} else {
throw new RuntimeException($"Gosubtarget line {newLineNum} not found.");
}
} else {
throw new RuntimeException("Gosub target line number/expression not understood.");
}
return true;
}
internal bool RunReturnStatement() {
ControlStack.Shared.Return();
return true;
}
internal void JumpToLine(int newLineOrder) {
LineNumber = ProgramSource[newLineOrder].linenum;
CurrentLineOrd = newLineOrder;
CurrentLine = ProgramSource[newLineOrder].src;
Parser.SetLine(CurrentLine, 0, LineNumber);
}
internal enum ScalarType {
NoMatch,
ShortType,
IntType,
LongType,
DoubleType,
BoolType,
StringType,
};
internal static string[] ScalarTypes = ["short", "int", "long", "double", "bool", "string"];
internal bool RunDimStatement() {
//DIM has been encountered; expect these:
// DIM I|ARR\[arrayranges\] [,...] ;REM DEFAULT TYPE INT?
// DIM AS type VAR|ARR\[arrayranges\] [,...] ;REM do not allow multiple AS type phrases in one DIM instruction
// e.g. DIM AS INT I, J, COUNT=100, MYARRAY1[0..COUNT-1], ANOTHER_ARRAY[1 TO 5]
var positionSave = Parser.LinePosition;
var whichType = ScalarType.NoMatch;
if (Parser.ScanString("AS")) {
whichType = (ScalarType)Parser.ScanStringTableEntry(ScalarTypes);
} else {
whichType = ScalarType.ShortType;
}
do {
var vName = Parser.ScanName();
if (vName == null) {
throw new RuntimeException("Expected: variable name");
}
var vRanges = Parser.ScanArrayDimensions();
if (vRanges == null) {
//scalar variable
if (Parser.ScanChar('=', true) != null) {
short vVal;
if (Expression.Shared.TryEvaluateExpr(out vVal)) {
var vVar = Variable.FindVariable(vName);
if (vVar == null) {
vVar = new Variable(vName, vVal, true);
} else {
throw new RuntimeException($"Variable '{vName}' already exists!");
}
} else {
throw new RuntimeException($"Expected: expression to initialize variable {vName}.");
}
}
} else {
//array variable
var vVar = Variable.FindVariable(vName);
if (vVar != null) {
throw new RuntimeException($"Variable '{vName}' already exists!");
} else {
var arrType = whichType switch {
ScalarType.ShortType => VariableType.ShortArray,
_ => throw new RuntimeException("Unknown or unsupported array element type"),
};
vVar = new Variable(vName: vName, vType: arrType, vDimensionRanges: vRanges, 0, true);
}
}
} while (Parser.ScanChar(',',true) != null);
return true;
}
}
/*
JP TN1 ;DIGIT. S SAYS OVERFLOW
QHOW PUSH D ;*** ERROR: "HOW?" ***
AHOW LXI D,HOW
JMP ERROR
HOW .ASCII "HOW?\r"
OK .ASCII "OK\r"
WHAT .ASCII "WHAT?\r"
SORRY .ASCII "SORRY\r"
;*
;**************************************************************
;*
;* *** MAIN ***
;*
;* THIS IS THE MAIN LOOP THAT COLLECTS THE TINY BASIC PROGRAM
;* AND STORES IT IN THE MEMORY.
;*
;* AT START, IT PRINTS OUT "(CR)OK(CR)", AND INITIALIZES THE
;* STACK AND SOME OTHER INTERNAL VARIABLES. THEN IT PROMPTS
;* ">" AND READS A LINE. IFF THE LINE STARTS WITH A NON-ZERO
;* NUMBER, THIS NUMBER IS THE LINE NUMBER. THE LINE NUMBER
;* (IN 16 BIT BINARY) AND THE REST OF THE LINE (INCLUDING CR)
;* IS STORED IN THE MEMORY. IFF A LINE WITH THE SAME LINE
;* NUMBER IS ALREDY THERE, IT IS REPLACED BY THE NEW ONE. IF
;* THE REST OF THE LINE CONSISTS OF A 0DHONLY, IT IS NOT STORED
;* AND ANY EXISTING LINE WITH THE SAME LINE NUMBER IS DELETED.
;*
;* AFTER A LINE IS INSERTED, REPLACED, OR DELETED, THE PROGRAM
;* LOOPS BACK AND ASK FOR ANOTHER LINE. THIS LOOP WILL BE
;* TERMINATED WHEN IT READS A LINE WITH ZERO OR NO LINE
;* NUMBER; AND CONTROL IS TRANSFERED TO "DIRCT".
;*
;* TINY BASIC PROGRAM SAVE AREA STARTS AT THE MEMORY LOCATION
;* LABELED "TXTBGN" AND ENDED AT "TXTEND". WE ALWAYS FILL THIS
;* AREA STARTING AT "TXTBGN", THE UNFILLED PORTION IS POINTED
;* BY THE CONTENT OF A MEMORY LOCATION LABELED "TXTUNF".
;*
;* THE MEMORY LOCATION "CURRNT" POINTS TO THE LINE NUMBER
;* THAT IS CURRENTLY BEING INTERPRETED. WHILE WE ARE IN
;* THIS LOOP OR WHILE WE ARE INTERPRETING A DIRECT COMMAND
;* (SEE NEXT SECTION), "CURRNT" SHOULD POINT TO A 0.
;*
RSTART LXI SP,STACK ;SET STACK POINTER
ST1 CALL CRLF ;AND JUMP TO HERE
LXI D,OK ;DE->STRING
SUB A ;A=0
CALL PRTSTG ;PRINT STRING UNTIL 0DH
LXI H,ST2+1 ;LITERAL 0
SHLD CURRNT ;CURRNT->LINE # = 0
ST2 LXI H,0
SHLD LOPVAR
SHLD STKGOS
ST3 MVI A,'>' ;PROMPT '>' AND
CALL GETLN ;READ A LINE
PUSH D ;DE->END OF LINE
ST3A LXI D,BUFFER ;DE->BEGINNING OF LINE
CALL TSTNUM ;TESt IFF IT IS A NUMBER
RST 5
MOV A,H ;HL=VALUE OF THE # OR
ORA L ;0 IFF NO # WAS FOUND
POP B ;BC->END OF LINE
JZ DIRECT
DCX D ;BACKUP DE AND SAVE
MOV A,H ;VALUE OF LINE # THERE
STAX D
DCX D
MOV A,L
STAX D
PUSH B ;BC,DE->BEGIN, END
PUSH D
MOV A,C
SUB E
PUSH PSW ;A=# OF BYTES IN LINE
CALL FNDLN ;FIND THIS LINE IN SAVE
PUSH D ;AREA, DE->SAVE AREA
JNZ ST4 ;NZ:NOT FOUND, INSERT
PUSH D ;Z:FOUND, DELETE IT
CALL FNDNXT ;FIND NEXT LINE
;* DE->NEXT LINE
POP B ;BC->LINE TO BE DELETED
LHLD TXTUNF ;HL->UNFILLED SAVE AREA
CALL MVUP ;MOVE UP TO DELETE
MOV H,B ;TXTUNF->UNFILLED AREA
MOV L,C
SHLD TXTUNF ;UPDATE
ST4 POP B ;GET READY TO INSERT
LHLD TXTUNF ;BUT FIRT CHECK IF
POP PSW ;THE LENGTH OF NEW LINE
PUSH H ;IS 3 (LINE # AND CR)
CPI 3 ;THEN DO NOT INSERT
JZ RSTART ;MUST CLEAR THE STACK
ADD L ;COMPUTE NEW TXTUNF
MOV L,A
MVI A,0
ADC H
MOV H,A ;HL->NEW UNFILLED AREA
ST4A LXI D,TXTEND ;CHECK TO SEE IF THERE
RST 4 ;IS ENOUGH SPACE
JNC QSORRY ;SORRY, NO ROOM FOR IT
SHLD TXTUNF ;OK, UPDATE TXTUNF
POP D ;DE->OLD UNFILLED AREA
CALL MVDOWN
POP D ;DE->BEGIN, HL->END
POP H
CALL MVUP ;MOVE NEW LINE TO SAVE
JMP ST3 ;AREA
;*
;**************************************************************
;*
;* *** TABLES *** DIRECT *** & EXEC ***
;*
;* THIS SECTION OF THE CODE TESTS A STRING AGAINST A TABLE.
;* WHEN A MATCH IS FOUND, CONTROL IS TRANSFERED TO THE SECTION
;* OF CODE ACCORDING TO THE TABLE.
;*
;* AT 'EXEC', DE SHOULD POINT TO THE STRING AD HL SHOULD POINT
;* TO THE TABLE-1. AT 'DIRECT', DE SHOULD POINT TO THE STRING,
;* HL WILL BE SET UP TO POINT TO TAB1-1, WHICH IS THE TABLE OF
;* ALL DIRECT AND STATEMENT COMMANDS.
;*
;* A '.' IN THE STRING WILL TERMINATE THE TEST AND THE PARTIAL
;* MATCH WILL BE CONSIDERED AS A MATCH. E.G., 'P.', 'PR.',
;* 'PRI.', 'PRIN.', OR 'PRINT' WILL ALL MATCH 'PRINT'.
;*
;* THE TABLE CONSISTS OF ANY NUMBER OF ITEMS. EACH ITEM
;* IS A STRING OF CHARACTERS WITH BIT 7 SET TO 0 AND
;* A JUMP ADDRESS STORED HI-LOW WITH BIT 7 OF THE HIGH
;* BYTE SET TO 1.
;*
;* END OF TABLE IS AN ITEM WITH A JUMP ADDRESS ONLY. IFF THE
;* STRING DOES NOT MATCH ANY OF THE OTHER ITEMS, IT WILL
;* MATCH THIS NULL ITEM AS DEFAULT.
;*
TAB1 = *
;DIRECT COMMANDS
.byte "LIST", <LIST | $80, >LIST
.byte "RUN", <RUN | $80, >RUN
.byte "NEW", <NEW | $80, >NEW
;.byte "LOAD", <DLOAD | $80, >DLOAD
;.byte "SAVE", <DSAVE | $80, >DSAVE
;.byte "BYE", $80, $0 ;GO BACK TO CPM
TAB2 = * ;DIRECT/STATEMENT
.byte "NEXT", <NEXT | $80, >NEXT
.byte "LET", <LET | $80, >LET
.byte "OUT", <OUTCMD | $80, >OUTCMD
.byte "POKE", <POKE | $80, >POKE
.byte "WAIT", <WAITCM | $80, >WAITCM
.byte "IF", <IFF | $80, >IFF
.byte "GOTO", <GOTO | $80, >GOTO
.byte "GOSUB", <GOSUB | $80, >GOSUB
.byte "RETURN", <RETURN | $80, >RETURN
.byte "REM", <REM | $80, >REM
.byte "FOR", <FOR | $80, >FOR
.byte "INPUT", <INPUT | $80, >INPUT
.byte "PRINT", <PRINT | $80, >PRINT
.byte "STOP", <STOP | $80, >STOP
.byte <DEFLT | $80, >DEFLT
.byte "YOU CAN ADD MORE" ;COMMANDS BUT
;REMEMBER TO MOVE
;DEFAULT DOWN.
TAB5 = * ;"TO" IN "FOR"
.byte "TO", <FR1 | $80, >FR1
.byte <QWHAT | $80, >QWHAT
TAB6 = * ;"STEP" IN "FOR"
.byte "STEP", <FR2 | $80, >FR2
.byte <FR3 | $80, >FR3
;*
DIRECT LXI H,TAB1-1 ;*** DIRECT ***
;*
EXEC = * ;*** EXEC ***
EX0 RST 5 ;IGNORE LEADING BLANKS
PUSH D ;SAVE POINTER
EX1 LDAX D ;IFF FOUND '.' IN STRING
INX D ;BEFORE ANY MISMATCH
CPI '.' ;WE DECLARE A MATCH
JZ EX3
INX H ;HL->TABLE
CMP M ;IFF MATCH, TEST NEXT
JZ EX1
MVI A,$7F ;ELSE, SEE IFF BIT 7
DCX D ;OF TABLE IS SET, WHICH
CMP M ;IS THE JUMP ADDR. (HI)
JC EX5 ;C:YES, MATCHED
EX2 INX H ;NC:NO, FIND JUMP ADDR.
CMP M
JNC EX2
INX H ;BUMP TO NEXT TAB. ITEM
POP D ;RESTORE STRING POINTER
JMP EX0 ;TEST AGAINST NEXT ITEM
EX3 MVI A,$7F ;PARTIAL MATCH, FIND
EX4 INX H ;JUMP ADDR., WHICH IS
CMP M ;FLAGGED BY BIT 7
JNC EX4
EX5 MOV A,M ;LOAD HL WITH THE JUMP
INX H ;ADDRESS FROM THE TABLE
MOV L,M
ANI $7F ;MASK OFF BIT 7
MOV H,A
POP PSW ;CLEAN UP THE GABAGE
PCHL ;AND WE GO DO IT
;*
;**************************************************************
;*
;* WHAT FOLLOWS IS THE CODE TO EXECUTE DIRECT AND STATEMENT
;* COMMANDS. CONTROL IS TRANSFERED TO THESE POINTS VIA THE
;* COMMAND TABLE LOOKUP CODE OF 'DIRECT' AND 'EXEC' IN LAST
;* SECTION. AFTER THE COMMAND IS EXECUTED, CONTROL IS
;* TANSFERED TO OTHER SECTIONS AS FOLLOWS:
;*
;* FOR 'LIST', 'NEW', AND 'STOP': GO BACK TO 'RSTART'
;* FOR 'RUN': GO EXECUTE THE FIRST STORED LINE IFF ANY; ELSE
;* GO BACK TO 'RSTART'.
;* FOR 'GOTO' AND 'GOSUB': GO EXECUTE THE TARGET LINE.
;* FOR 'RETURN' AND 'NEXT': GO BACK TO SAVED RETURN LINE.
;* FOR ALL OTHERS: IFF 'CURRNT' -> 0, GO TO 'RSTART', ELSE
;* GO EXECUTE NEXT COMMAND. (THIS IS DONE IN 'FINISH'.)
;*
;* 'RUN(CR)' FINDS THE FIRST STORED LINE, STORE ITS ADDRESS (IN
;* 'CURRNT'), AND START EXECUTE IT. NOTE THAT ONLY THOSE
;* COMMANDS IN TAB2 ARE LEGAL FOR STORED PROGRAM.
;*
;* THERE ARE 3 MORE ENTRIES IN 'RUN':
;* 'RUNNXL' FINDS NEXT LINE, STORES ITS ADDR. AND EXECUTES IT.
;* 'RUNTSL' STORES THE ADDRESS OF THIS LINE AND EXECUTES IT.
;* 'RUNSML' CONTINUES THE EXECUTION ON SAME LINE.
;*
;* 'GOTO EXPR(CR)' EVALUATES THE EXPRESSION, FIND THE TARGET
;* LINE, AND JUMP TO 'RUNTSL' TO DO IT.
;* 'DLOAD' LOADS A NAMED PROGRAM FROM DISK.
;* 'DSAVE' SAVES A NAMED PROGRAM ON DISK.
;* 'FCBSET' SETS UP THE FILE CONTROL BLOCK FOR SUBSEQUENT DISK I/O.
;*
RUNNXL LXI H,0 ;*** RUNNXL ***
CALL FNDLNP ;FIND WHATEVER LINE #
JC RSTART ;C:PASSED TXTUNF, QUIT
;*
RUNTSL XCHG ;*** RUNTSL ***
SHLD CURRNT ;SET 'CURRNT'->LINE #
XCHG
INX D ;BUMP PASS LINE #
INX D
;*
RUNSML CALL CHKIO ;*** RUNSML ***
LXI H,TAB2-1 ;FIND COMMAND IN TAB2
JMP EXEC ;AND EXECUTE IT
;*
;CPM = 5 ;DISK PARAMETERS
;FCB = $5C
;SETDMA = 26
;OPEN = 15
;READD = 20
;WRITED = 21
;CLOSE = 16
;MAKE = 22
;DELETE = 19
;*
;DLOAD RST 5 ;IGNORE BLANKS
; PUSH H ;SAVE H
; CALL FCBSET ;SET UP FILE CONTROL BLOCK
; PUSH D ;SAVE THE REST
; PUSH B
; LXI D,FCB ;GET FCB ADDRESS
; MVI C,OPEN ;PREPARE TO OPEN FILE
; CALL CPM ;OPEN IT
; CPI $FF ;IS IT THERE?
; JZ QHOW ;NO, SEND ERROR
; XRA A ;CLEAR A
; STA FCB+32 ;START AT RECORD 0
; LXI D,TXTUNF ;GET BEGINNING
;LOAD PUSH D ;SAVE DMA ADDRESS
; MVI C,SETDMA ;
; CALL CPM ;SET DMA ADDRESS
; MVI C,READD ;
; LXI D,FCB
; CALL CPM ;READ SECTOR
; CPI 1 ;DONE?
; JC RDMORE ;NO, READ MORE
; JNZ QHOW ;BAD READ
; MVI C,CLOSE
; LXI D,FCB
; CALL CPM ;CLOSE FILE
; POP D ;THROW AWAY DMA ADD.
; POP B ;GET OLD REGISTERS BACK
; POP D
; POP H
; RST 6 ;FINISH
;RDMORE POP D ;GET DMA ADDRESS
; LXI H,$80 ;GET 128
; DAD D ;ADD 128 TO DMA ADD.
; XCHG ;PUT IT BACK IN D
; JMP LOAD ;AND READ SOME MORE
;;*
;DSAVE RST 5 ;IGNORE BLANKS
; PUSH H ;SAVE H
; CALL FCBSET ;SETUP FCB
; PUSH D
; PUSH B ;SAVE OTHERS
; LXI D,FCB
; MVI C,DELETE
; CALL CPM ;ERASE FILE IF IT EXISTS
; LXI D,FCB
; MVI C,MAKE
; CALL CPM ;MAKE A NEW ONE
; CPI $FF ;IS THERE SPACE?
; JZ QHOW ;NO, ERROR
; XRA A ;CLEAR A
; STA FCB+32 ;START AT RECORD 0
; LXI D,TXTUNF ;GET BEGINNING
;SAVE PUSH D ;SAVE DMA ADDRESS
; MVI C,SETDMA ;
; CALL CPM ;SET DMA ADDRESS
; MVI C,WRITED
; LXI D,FCB
; CALL CPM ;WRITE SECTOR
; ORA A ;SET FLAGS
; JNZ QHOW ;IF NOT ZERO, ERROR
; POP D ;GET DMA ADD. BACK
; LDA TXTUNF+1 ;AND MSB OF LAST ADD.
; CMP D ;IS D SMALLER?
; JC SAVDON ;YES, DONE
; JNZ WRITMOR ;DONT TEST E IF NOT EQUAL
; LDA TXTUNF ;IS E SMALLER?
; CMP E
; JC SAVDON ;YES, DONE
;WRITMOR LXI H,$80
; DAD D ;ADD 128 TO DMA ADD.
; XCHG ;GET IT BACK IN D
; JMP SAVE ;WRITE SOME MORE
;SAVDON MVI C,CLOSE
; LXI D,FCB
; CALL CPM ;CLOSE FILE
; POP B ;GET REGISTERS BACK
; POP D
; POP H
; RST 6 ;FINISH
;;*
;FCBSET LXI H,FCB ;GET FILE CONTROL BLOCK ADDRESS
; MVI M,0 ;CLEAR ENTRY TYPE
;FNCLR INX H ;NEXT LOCATION
; MVI M,' ' ;CLEAR TO SPACE
; MVI A,<(FCB+8)
; CMP L ;DONE?
; JNZ FNCLR ;NO, DO IT AGAIN
; INX H ;NEXT
; MVI M,'T' ;SET FILE TYPE TO 'TBI'
; INX H
; MVI M,'B'
; INX H
; MVI M,'I'
;EXRC INX H ;CLEAR REST OF FCB
; MVI M,0
; MVI A,<(FCB+15)
; CMP L ;DONE?
; JNZ EXRC ;NO, CONTINUE
; LXI H,FCB+1 ;GET FILENAME START
;FN LDAX D ;GET CHARACTER
; CPI $0D ;IS IT A 'CR'
; RZ ;YES, DONE
; CPI '!' ;LEGAL CHARACTER?
; JC QWHAT ;NO, SEND ERROR
; CPI '[' ;AGAIN
; JNC QWHAT ;DITTO
; MOV M,A ;SAVE IT IN FCB
; INX H ;NEXT
; INX D
; MVI A,<(FCB+9)
; CMP L ;LAST?
; JNZ FN ;NO, CONTINUE
; RET ;TRUNCATE AT 8 CHARACTERS
;*
RND CALL PARN ;*** RND(EXPR) ***
MOV A,H ;EXPR MUST BE +
ORA A
JM QHOW
ORA L ;AND NON-ZERO
JZ QHOW
PUSH D ;SAVE BOTH
PUSH H
LHLD RANPNT ;GET MEMORY AS RANDOM
LXI D,LSTROM ;NUMBER
RST 4
JC RA1 ;WRAP AROUND IFF LAST
LXI H,START
RA1 MOV E,M
INX H
MOV D,M
SHLD RANPNT
POP H
XCHG
PUSH B
CALL DIVIDE ;RND(N)=MOD(M,N)+1
POP B
POP D
INX H
RET
;*
ABS CALL PARN ;*** ABS(EXPR) ***
CALL CHKSGN ;CHECK SIGN
MOV A,H ;NOTE THAT -32768
ORA H ;CANNOT CHANGE SIGN
JM QHOW ;SO SAY: "HOW?"
RET
SIZE LHLD TXTUNF ;*** SIZE ***
PUSH D ;GET THE NUMBER OF FREE
XCHG ;BYTES BETWEEN 'TXTUNF'
SIZEA LXI H,VARBGN ;AND 'VARBGN'
CALL SUBDE
POP D
RET
;*
;*********************************************************
;*
;* *** OUT *** INP *** WAIT *** POKE *** PEEK *** & USR
;*
;* OUT I,J(,K,L)
;*
;* OUTPUTS EXPRESSION 'J' TO PORT 'I', AND MAY BE REPEATED
;* AS IN DATA 'L' TO PORT 'K' AS MANY TIMES AS NEEDED
;* THIS COMMAND MODIFIES ;* THIS COMMAND MODIFIES
;* THIS COMMAND MODIFY'S A SMALL SECTION OF CODE LOCATED
;* JUST ABOVE ADDRESS 2K
;*
;* INP (I)
;*
;* THIS FUNCTION RETURNS DATA READ FROM INPUT PORT 'I' AS
;* IT'S VALUE.
;* IT ALSO MODIFIES CODE JUST ABOVE 2K.