mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2025-01-23 12:29:53 +00:00
basic: got rid of BASICLine in favor of list of statements, multipleStmtsPerLine
This commit is contained in:
parent
e5de701419
commit
74238b334f
@ -1,12 +1,99 @@
|
||||
OPTION DIALECT DARTMOUTH
|
||||
10 PRINT "HELLO! LET'S PROGRAM IN BASIC."
|
||||
15 PRINT
|
||||
20 INPUT "WOULD YOU MIND TYPING IN YOUR NAME";A$
|
||||
25 PRINT
|
||||
30 PRINT "THANKS, ";A$;"! THIS WILL BE FUN!"
|
||||
35 PRINT
|
||||
40 INPUT "NOW TELL ME YOUR FAVORITE NUMBER";N
|
||||
45 PRINT
|
||||
50 PRINT "THAT'S A GOOD ONE! I LIKE";N^2;"MYSELF."
|
||||
60 PRINT "NICE MEETING YOU, ";A$;"."
|
||||
001 REM THIS IS A PROGRAM WRITTEN IN BASIC.
|
||||
002 REM YOU CAN WRITE ONE STATEMENT PER LINE.
|
||||
003 REM EACH STATEMENT MUST HAVE A LINE NUMBER.
|
||||
004 REM THE "REM" LINES ARE COMMENTS, WHICH ARE IGNORED
|
||||
005 ' YOU CAN ALSO DO COMMENTS WITH APOSTROPHES
|
||||
006 ' LET'S START WITH A "PRINT"...
|
||||
100 PRINT "HELLO! LET'S PROGRAM IN BASIC."
|
||||
101 PRINT ' <-- THIS PRINTS A BLANK LINE, IT LOOKS NICER
|
||||
105 ' "INPUT" WAITS FOR USER INPUT FROM THE KEYBOARD
|
||||
106 ' THE NUMBER ENTERED WILL GO INTO THE VARIABLE "N"
|
||||
110 INPUT "FIRST, TELL ME YOUR FAVORITE NUMBER";N
|
||||
111 PRINT
|
||||
115 ' YOU CAN DO CALCULATIONS WITH THE + - / * OPERATORS
|
||||
116 ' THE ^ IS AN EXPONENT, SO N^2 IS N-SQUARED
|
||||
119 ' "LET" ASSIGNS THE RESULT TO A VARIABLE
|
||||
120 LET Z = N^2
|
||||
121 ' THE SEMICOLON ";" SEPARATES THINGS TO BE PRINTED
|
||||
122 PRINT "THAT'S A GOOD ONE! I LIKE";Z;"MYSELF."
|
||||
123 PRINT
|
||||
125 ' IF A VARIABLE ENDS IN "$" IT IS A STRING
|
||||
126 ' AND CONTAINS CHARACTERS INSTEAD OF A NUMBER
|
||||
127 ' LET'S READ FROM THE KEYBOARD INTO THE A$ VARIABLE
|
||||
130 INPUT "WOULD YOU MIND TYPING IN YOUR NAME";A$
|
||||
131 PRINT
|
||||
135 ' WE CAN PRINT MULTIPLE THINGS PER LINE
|
||||
136 ' IN THIS BASIC DIALECT, NUMBERS ARE PRINTED
|
||||
137 ' WITH SPACES BEFORE AND AFTER
|
||||
140 PRINT "GOOD TO MEET YOU, ";A$;" WHO LOVES";N;"!"
|
||||
141 PRINT
|
||||
150 INPUT "WOULD YOU LIKE TO SEE ME CALCULATE";B$
|
||||
151 PRINT
|
||||
155 ' "IF" CAN TEST A CONDITION
|
||||
156 ' AND JUMP TO A DIFFERENT LINE IF IT IS TRUE
|
||||
160 IF B$ = "Y" THEN 180
|
||||
161 IF B$ = "YES" THEN 180
|
||||
170 PRINT "TOO BAD, ";A$;"! WE'RE DOING THIS."
|
||||
180 PRINT
|
||||
190 PRINT "WATCH ME COUNT..."
|
||||
195 ' THIS "FOR" STATEMENT STARTS A LOOP WHICH SETS
|
||||
196 ' VARIABLE I TO 1 AND COUNTS TO 50
|
||||
200 FOR I = 1 TO 50
|
||||
210 PRINT I, ' USE A COMMA FOR NEAT PRINT ZONES
|
||||
220 NEXT I ' REPEAT LOOP
|
||||
225 ' USING THE COMMA (OR SEMICOLON) IN THE PRINT STATEMENT
|
||||
226 ' KEEPS THE CURSOR ON THE SAME LINE, SO WE PRINT
|
||||
227 ' A BLANK LINE TO RETURN TO THE LEFT MARGIN
|
||||
230 PRINT
|
||||
240 PRINT "I CAN ALSO GRAPH TRIG FUNCTIONS!"
|
||||
245 ' YOU CAN DEFINE YOUR OWN FUNCTIONS WITH "DEF"
|
||||
246 ' FUNCTIONS MUST START WITH "FN"
|
||||
250 DEF FNY(X) = (SIN(X*0.3)+1)*30+2
|
||||
260 FOR I = 1 TO 25
|
||||
270 PRINT TAB(FNY(I));"*"
|
||||
280 NEXT I
|
||||
300 ' YOU CAN DEFINE AN ARRAY WITH "DIM"
|
||||
301 ' YOU CAN HAVE ARRAYS OF EITHER NUMBERS OR STRINGS
|
||||
310 DIM T$(15),A$(15),W(15)
|
||||
315 ' "READ" LOADS FROM "DATA" STATEMENTS
|
||||
316 ' DATA CAN BE ANYWHERE, OURS IS AT LINE 900
|
||||
320 FOR I = 1 TO 15
|
||||
330 READ T$(I),A$(I),W(I)
|
||||
340 NEXT I
|
||||
350 ' NOW THE ARRAYS ARE FILLED WITH DATA
|
||||
351 ' SO LET'S PICK A VALUE AT RANDOM
|
||||
352 ' THE RND FUNCTION RETURNS A RANDOM NUMBER
|
||||
353 ' FIRST WE USE "RANDOMIZE" TO INITIALIZE THE
|
||||
354 ' RANDOM NUMBER GENERATOR
|
||||
360 RANDOMIZE
|
||||
362 ' "LET" ASSIGNS A VARIABLE FROM AN EXPRESSION
|
||||
370 LET J = INT(15*RND)
|
||||
380 PRINT "MY FAVORITE SONG IS '";T$(J);"'"
|
||||
390 PRINT "BY ";A$(J);", DO YOU KNOW IT?"
|
||||
400 PRINT "IT SPENT";W(J);"WEEKS ON THE CHARTS."
|
||||
410 PRINT
|
||||
890 ' HERE'S THE DATA WE READ EARLIER
|
||||
891 ' WE CAN USE NUMBERS, QUOTED OR UNQUOTED STRINGS
|
||||
900 DATA "HELLO, GOODBYE",THE BEATLES,2
|
||||
901 DATA "JUDY IN DISGUISE",JOHN FRED AND HIS PLAYBOY BAND,2
|
||||
902 DATA "GREEN TAMBOURINE",THE LEMON PIPERS,1
|
||||
903 DATA "LOVE IS BLUE",PAUL MAURIAT,5
|
||||
904 DATA "(SITTIN' ON) THE DOCK OF THE BAY",OTIS REDDING,4
|
||||
905 DATA "HONEY",BOBBY GOLDSBORO,5
|
||||
906 DATA "TIGHTEN UP",ARCHIE BELL & THE DRELLS,3
|
||||
907 DATA "MRS. ROBINSON",SIMON & GARFUNKEL,3
|
||||
908 DATA "THIS GUY'S IN LOVE WITH YOU",HERB ALPERT,4
|
||||
909 DATA "GRAZING IN THE GRASS",HUGH MASEKELA,2
|
||||
910 DATA "HELLO, I LOVE YOU",THE DOORS,2
|
||||
911 DATA "PEOPLE GOT TO BE FREE",THE RASCALS,5
|
||||
912 DATA "HARPER VALLEY PTA",JEANNIE C. RILEY,1
|
||||
913 DATA "HEY JUDE",THE BEATLES,9
|
||||
914 DATA "LOVE CHILD",DIANA ROSS & THE SUPREMES,2
|
||||
915 DATA "I HEARD IT THROUGH THE GRAPEVINE",MARVIN GAYE,3
|
||||
979 ' WE'RE DONE, SAY GOODBYE TO THE USER
|
||||
980 PRINT "I APOLOGIZE, BUT I AM FATIGUED FROM ALL OF THIS"
|
||||
981 PRINT "CALCULATING. I SHALL TAKE MY LEAVE OF YOU NOW."
|
||||
990 PRINT "NICE MEETING YOU, ";A$;"."
|
||||
998 ' PROGRAMS MUST END WITH "END" IN THIS DIALECT
|
||||
999 END
|
||||
|
@ -8,6 +8,7 @@ export interface BASICOptions {
|
||||
uppercaseOnly : boolean; // convert everything to uppercase?
|
||||
optionalLabels : boolean; // can omit line numbers and use labels?
|
||||
optionalWhitespace : boolean; // can "crunch" keywords? also, eat extra ":" delims
|
||||
multipleStmtsPerLine : boolean; // multiple statements separated by ":"
|
||||
varNaming : 'A'|'A1'|'AA'|'*'; // only allow A0-9 for numerics, single letter for arrays/strings
|
||||
squareBrackets : boolean; // "[" and "]" interchangable with "(" and ")"?
|
||||
tickComments : boolean; // support 'comments?
|
||||
@ -234,14 +235,10 @@ export type ScopeStartStatement = (IF_Statement | FOR_Statement | WHILE_Statemen
|
||||
|
||||
export type ScopeEndStatement = NEXT_Statement | WEND_Statement;
|
||||
|
||||
export interface BASICLine {
|
||||
label: string;
|
||||
stmts: Statement[];
|
||||
}
|
||||
|
||||
export interface BASICProgram {
|
||||
opts: BASICOptions;
|
||||
lines: BASICLine[];
|
||||
stmts: Statement[];
|
||||
labels: { [label: string]: number }; // label -> PC
|
||||
}
|
||||
|
||||
class Token {
|
||||
@ -326,9 +323,10 @@ export class BASICParser {
|
||||
opts : BASICOptions = DIALECTS['DEFAULT'];
|
||||
optionCount : number; // how many OPTION stmts so far?
|
||||
maxlinelen : number = 255; // maximum line length
|
||||
stmts : Statement[];
|
||||
errors: WorkerError[];
|
||||
listings: CodeListingMap;
|
||||
labels: { [label: string]: BASICLine };
|
||||
labels: { [label: string]: number }; // label -> PC
|
||||
targets: { [targetlabel: string]: SourceLocation };
|
||||
varrefs: { [name: string]: SourceLocation }; // references
|
||||
fnrefs: { [name: string]: string[] }; // DEF FN call graph
|
||||
@ -343,11 +341,12 @@ export class BASICParser {
|
||||
|
||||
constructor() {
|
||||
this.optionCount = 0;
|
||||
this.lineno = 0;
|
||||
this.curlabel = null;
|
||||
this.stmts = [];
|
||||
this.labels = {};
|
||||
this.targets = {};
|
||||
this.errors = [];
|
||||
this.lineno = 0;
|
||||
this.curlabel = null;
|
||||
this.listings = {};
|
||||
this.varrefs = {};
|
||||
this.fnrefs = {};
|
||||
@ -394,7 +393,8 @@ export class BASICParser {
|
||||
pushbackToken(tok: Token) {
|
||||
this.tokens.unshift(tok);
|
||||
}
|
||||
parseOptLabel(line: BASICLine) {
|
||||
// this parses either a line number or "label:" -- or adds a default label to a line
|
||||
parseOptLabel() {
|
||||
let tok = this.consumeToken();
|
||||
switch (tok.type) {
|
||||
case TokenType.Ident:
|
||||
@ -410,8 +410,9 @@ export class BASICParser {
|
||||
} else
|
||||
this.dialectError(`Each line must begin with a line number`);
|
||||
case TokenType.Int:
|
||||
this.setCurrentLabel(line, tok.str);
|
||||
break;
|
||||
this.addLabel(tok.str);
|
||||
return;
|
||||
// label added, return from function... other cases add default label
|
||||
case TokenType.HexOctalInt:
|
||||
case TokenType.Float:
|
||||
this.compileError(`Line numbers must be positive integers.`);
|
||||
@ -429,30 +430,41 @@ export class BASICParser {
|
||||
case TokenType.Remark:
|
||||
break;
|
||||
}
|
||||
// add default label
|
||||
this.addLabel('#'+this.lineno);
|
||||
}
|
||||
setCurrentLabel(line: BASICLine, str: string) {
|
||||
if (this.labels[str] != null) this.compileError(`There's a duplicated label "${str}".`);
|
||||
this.labels[str] = line;
|
||||
line.label = str;
|
||||
getPC() : number {
|
||||
return this.stmts.length;
|
||||
}
|
||||
addStatement(stmt: Statement, cmdtok: Token, endtok?: Token) {
|
||||
// check IF/THEN WHILE/WEND FOR/NEXT etc
|
||||
this.modifyScope(stmt);
|
||||
// set location for statement
|
||||
if (endtok == null) endtok = this.peekToken();
|
||||
stmt.$loc = { path: cmdtok.$loc.path, line: cmdtok.$loc.line, start: cmdtok.$loc.start, end: endtok.$loc.start, label: this.curlabel, offset: null };
|
||||
this.stmts.push(stmt);
|
||||
}
|
||||
addLabel(str: string) {
|
||||
if (this.labels[str] != null) this.compileError(`There's a duplicated label named "${str}".`);
|
||||
this.labels[str] = this.getPC();
|
||||
this.curlabel = str;
|
||||
this.tokens.forEach((tok) => tok.$loc.label = str);
|
||||
}
|
||||
parseFile(file: string, path: string) : BASICProgram {
|
||||
this.path = path;
|
||||
var txtlines = file.split(/\n|\r\n?/);
|
||||
var pgmlines = txtlines.map((line) => this.parseLine(line));
|
||||
var program = { opts: this.opts, lines: pgmlines };
|
||||
txtlines.forEach((line) => this.parseLine(line));
|
||||
var program = { opts: this.opts, stmts: this.stmts, labels: this.labels };
|
||||
this.checkAll(program);
|
||||
this.listings[path] = this.generateListing(file, program);
|
||||
return program;
|
||||
}
|
||||
parseLine(line: string) : BASICLine {
|
||||
parseLine(line: string) : void {
|
||||
try {
|
||||
this.tokenize(line);
|
||||
return this.parse();
|
||||
this.parse();
|
||||
} catch (e) {
|
||||
if (!(e instanceof CompileError)) throw e; // ignore compile errors since errors[] list captures them
|
||||
return {label:null, stmts:[]};
|
||||
}
|
||||
}
|
||||
_tokenize(line: string) : void {
|
||||
@ -517,27 +529,25 @@ export class BASICParser {
|
||||
if (line.length > this.maxlinelen) this.compileError(`A line should be no more than ${this.maxlinelen} characters long.`);
|
||||
this._tokenize(line);
|
||||
}
|
||||
parse() : BASICLine {
|
||||
var line = {label: null, stmts: []};
|
||||
parse() : void {
|
||||
// not empty line?
|
||||
if (this.tokens.length) {
|
||||
this.parseOptLabel(line);
|
||||
this.parseOptLabel();
|
||||
if (this.tokens.length) {
|
||||
line.stmts = this.parseCompoundStatement();
|
||||
this.parseCompoundStatement();
|
||||
}
|
||||
var next = this.peekToken();
|
||||
if (!isEOS(next)) this.compileError(`Expected end of line or ':'`, next.$loc);
|
||||
this.curlabel = null;
|
||||
}
|
||||
return line;
|
||||
}
|
||||
parseCompoundStatement(): Statement[] {
|
||||
var list = this.parseList(this.parseStatement, ':');
|
||||
var next = this.peekToken();
|
||||
if (!isEOS(next))
|
||||
this.compileError(`Expected end of line or ':'`, next.$loc);
|
||||
if (next.str == 'ELSE')
|
||||
return list.concat(this.parseCompoundStatement());
|
||||
else
|
||||
return list;
|
||||
parseCompoundStatement() : void {
|
||||
if (this.opts.multipleStmtsPerLine) {
|
||||
this.parseList(this.parseStatement, ':');
|
||||
} else {
|
||||
this.parseList(this.parseStatement, '\0');
|
||||
if (this.peekToken().str == ':') this.dialectErrorNoSupport(`multiple statements on a line`);
|
||||
}
|
||||
}
|
||||
validKeyword(keyword: string) : string {
|
||||
return (this.opts.validKeywords && this.opts.validKeywords.indexOf(keyword) < 0) ? null : keyword;
|
||||
@ -578,7 +588,6 @@ export class BASICParser {
|
||||
if (this.validKeyword(cmd) == null)
|
||||
this.dialectErrorNoSupport(`the ${cmd} statement`);
|
||||
stmt = fn.bind(this)();
|
||||
this.modifyScope(stmt);
|
||||
break;
|
||||
} else if (this.peekToken().str == '=' || this.peekToken().str == '(') {
|
||||
if (!this.opts.optionalLet)
|
||||
@ -596,7 +605,8 @@ export class BASICParser {
|
||||
this.compileError(`There should be a command here.`);
|
||||
return null;
|
||||
}
|
||||
if (stmt) stmt.$loc = { path: cmdtok.$loc.path, line: cmdtok.$loc.line, start: cmdtok.$loc.start, end: this.peekToken().$loc.start, label: this.curlabel, offset: null };
|
||||
// add statement to list
|
||||
if (stmt != null) this.addStatement(stmt, cmdtok);
|
||||
return stmt;
|
||||
}
|
||||
// check scope stuff (if compiledBlocks is true)
|
||||
@ -898,30 +908,39 @@ export class BASICParser {
|
||||
return { command: cmd, label: expr };
|
||||
}
|
||||
}
|
||||
stmt__IF(): IF_Statement {
|
||||
stmt__IF(): void {
|
||||
var cmdtok = this.lasttoken;
|
||||
var cond = this.parseExpr();
|
||||
var iftrue: Statement[];
|
||||
var ifstmt = { command: "IF", cond: cond };
|
||||
this.addStatement(ifstmt, cmdtok);
|
||||
// we accept GOTO or THEN if line number provided (DEC accepts GO TO)
|
||||
var thengoto = this.expectTokens(['THEN','GOTO','GO']);
|
||||
if (thengoto.str == 'GO') this.expectToken('TO');
|
||||
// parse line number or statement clause
|
||||
this.parseGotoOrStatements();
|
||||
// is the next statement an ELSE?
|
||||
// gotta parse it now because it's an end-of-statement token
|
||||
if (this.peekToken().str == 'ELSE') {
|
||||
this.expectToken('ELSE');
|
||||
this.stmt__ELSE();
|
||||
}
|
||||
}
|
||||
stmt__ELSE(): void {
|
||||
this.addStatement({ command: "ELSE" }, this.lasttoken);
|
||||
// parse line number or statement clause
|
||||
this.parseGotoOrStatements();
|
||||
}
|
||||
parseGotoOrStatements() {
|
||||
var lineno = this.peekToken();
|
||||
// assume GOTO if number given after THEN
|
||||
if (lineno.type == TokenType.Int) {
|
||||
this.pushbackToken({type:TokenType.Ident, str:'GOTO', $loc:lineno.$loc});
|
||||
this.parseLabel();
|
||||
var gotostmt : GOTO_Statement = { command:'GOTO', label: {value:lineno.str} }
|
||||
this.addStatement(gotostmt, lineno);
|
||||
} else {
|
||||
// parse rest of IF clause
|
||||
this.parseCompoundStatement();
|
||||
}
|
||||
// add fake ":"
|
||||
this.pushbackToken({type:TokenType.Operator, str:':', $loc:lineno.$loc});
|
||||
return { command: "IF", cond: cond };
|
||||
}
|
||||
stmt__ELSE(): ELSE_Statement {
|
||||
var lineno = this.peekToken();
|
||||
// assume GOTO if number given after ELSE
|
||||
if (lineno.type == TokenType.Int) {
|
||||
this.pushbackToken({type:TokenType.Ident, str:'GOTO', $loc:lineno.$loc});
|
||||
}
|
||||
// add fake ":"
|
||||
this.pushbackToken({type:TokenType.Operator, str:':', $loc:lineno.$loc});
|
||||
return { command: "ELSE" };
|
||||
}
|
||||
stmt__FOR() : FOR_Statement {
|
||||
var lexpr = this.parseForNextLexpr();
|
||||
@ -1124,15 +1143,12 @@ export class BASICParser {
|
||||
// for workermain
|
||||
generateListing(file: string, program: BASICProgram) {
|
||||
var srclines = [];
|
||||
var pc = 0;
|
||||
var laststmt : Statement;
|
||||
program.lines.forEach((line, idx) => {
|
||||
line.stmts.forEach((stmt) => {
|
||||
laststmt = stmt;
|
||||
var sl = stmt.$loc;
|
||||
sl.offset = pc++; // TODO: could Statement have offset field?
|
||||
srclines.push(sl);
|
||||
});
|
||||
program.stmts.forEach((stmt, idx) => {
|
||||
laststmt = stmt;
|
||||
var sl = stmt.$loc;
|
||||
sl.offset = idx;
|
||||
srclines.push(sl);
|
||||
});
|
||||
if (this.opts.endStmtRequired && (laststmt == null || laststmt.command != 'END'))
|
||||
this.dialectError(`All programs must have a final END statement`);
|
||||
@ -1172,6 +1188,7 @@ export const ECMA55_MINIMAL : BASICOptions = {
|
||||
uppercaseOnly : true,
|
||||
optionalLabels : false,
|
||||
optionalWhitespace : false,
|
||||
multipleStmtsPerLine : false,
|
||||
varNaming : "A1",
|
||||
staticArrays : true,
|
||||
sharedArrayNamespace : true,
|
||||
@ -1220,6 +1237,7 @@ export const DARTMOUTH_4TH_EDITION : BASICOptions = {
|
||||
uppercaseOnly : true,
|
||||
optionalLabels : false,
|
||||
optionalWhitespace : false,
|
||||
multipleStmtsPerLine : false,
|
||||
varNaming : "A1",
|
||||
staticArrays : true,
|
||||
sharedArrayNamespace : false,
|
||||
@ -1271,6 +1289,7 @@ export const TINY_BASIC : BASICOptions = {
|
||||
uppercaseOnly : true,
|
||||
optionalLabels : false,
|
||||
optionalWhitespace : false,
|
||||
multipleStmtsPerLine : false,
|
||||
varNaming : "A",
|
||||
staticArrays : false,
|
||||
sharedArrayNamespace : true,
|
||||
@ -1317,6 +1336,7 @@ export const HP_TIMESHARED_BASIC : BASICOptions = {
|
||||
uppercaseOnly : false, // the terminal is usually uppercase
|
||||
optionalLabels : false,
|
||||
optionalWhitespace : false,
|
||||
multipleStmtsPerLine : true,
|
||||
varNaming : "A1",
|
||||
staticArrays : true,
|
||||
sharedArrayNamespace : false,
|
||||
@ -1370,6 +1390,7 @@ export const DEC_BASIC_11 : BASICOptions = {
|
||||
uppercaseOnly : true, // translates all lower to upper
|
||||
optionalLabels : false,
|
||||
optionalWhitespace : false,
|
||||
multipleStmtsPerLine : false, // actually "\"
|
||||
varNaming : "A1",
|
||||
staticArrays : true,
|
||||
sharedArrayNamespace : false,
|
||||
@ -1424,6 +1445,7 @@ export const DEC_BASIC_PLUS : BASICOptions = {
|
||||
uppercaseOnly : false,
|
||||
optionalLabels : false,
|
||||
optionalWhitespace : false,
|
||||
multipleStmtsPerLine : true,
|
||||
varNaming : "A1",
|
||||
staticArrays : true,
|
||||
sharedArrayNamespace : false,
|
||||
@ -1485,6 +1507,7 @@ export const BASICODE : BASICOptions = {
|
||||
uppercaseOnly : false,
|
||||
optionalLabels : false,
|
||||
optionalWhitespace : true,
|
||||
multipleStmtsPerLine : true,
|
||||
varNaming : "AA",
|
||||
staticArrays : true,
|
||||
sharedArrayNamespace : false,
|
||||
@ -1535,6 +1558,7 @@ export const ALTAIR_BASIC41 : BASICOptions = {
|
||||
uppercaseOnly : true,
|
||||
optionalLabels : false,
|
||||
optionalWhitespace : true,
|
||||
multipleStmtsPerLine : true,
|
||||
varNaming : "*", // or AA
|
||||
staticArrays : false,
|
||||
sharedArrayNamespace : true,
|
||||
@ -1593,6 +1617,7 @@ export const APPLESOFT_BASIC : BASICOptions = {
|
||||
uppercaseOnly : false,
|
||||
optionalLabels : false,
|
||||
optionalWhitespace : true,
|
||||
multipleStmtsPerLine : true,
|
||||
varNaming : "*", // or AA
|
||||
staticArrays : false,
|
||||
sharedArrayNamespace : false,
|
||||
@ -1652,6 +1677,7 @@ export const BASIC80 : BASICOptions = {
|
||||
uppercaseOnly : false,
|
||||
optionalLabels : false,
|
||||
optionalWhitespace : true,
|
||||
multipleStmtsPerLine : true,
|
||||
varNaming : "*",
|
||||
staticArrays : false,
|
||||
sharedArrayNamespace : true,
|
||||
@ -1712,6 +1738,7 @@ export const MODERN_BASIC : BASICOptions = {
|
||||
uppercaseOnly : false,
|
||||
optionalLabels : true,
|
||||
optionalWhitespace : false,
|
||||
multipleStmtsPerLine : true,
|
||||
varNaming : "*",
|
||||
staticArrays : false,
|
||||
sharedArrayNamespace : false,
|
||||
@ -1749,6 +1776,7 @@ export const MODERN_BASIC : BASICOptions = {
|
||||
// TODO: integer vars
|
||||
// TODO: DEFINT/DEFSTR
|
||||
// TODO: excess INPUT ignored, error msg
|
||||
// TODO: out of order line numbers
|
||||
|
||||
export const DIALECTS = {
|
||||
"DEFAULT": MODERN_BASIC,
|
||||
|
@ -72,8 +72,6 @@ export class BASICRuntime {
|
||||
|
||||
program : basic.BASICProgram;
|
||||
allstmts : basic.Statement[];
|
||||
line2pc : number[];
|
||||
pc2line : Map<number,number>;
|
||||
pc2label : Map<number,string>;
|
||||
label2pc : {[label : string] : number};
|
||||
label2dataptr : {[label : string] : number};
|
||||
@ -103,7 +101,7 @@ export class BASICRuntime {
|
||||
let prevlabel = null;
|
||||
let prevpcofs = 0;
|
||||
if (this.pc2label != null) {
|
||||
var pc = this.curpc;
|
||||
let pc = this.curpc;
|
||||
while (pc > 0 && (prevlabel = this.pc2label.get(pc)) == null) {
|
||||
pc--;
|
||||
}
|
||||
@ -114,27 +112,18 @@ export class BASICRuntime {
|
||||
this.program = program;
|
||||
this.opts = program.opts;
|
||||
if (!this.opts.maxArrayElements) this.opts.maxArrayElements = DEFAULT_MAX_ARRAY_ELEMENTS;
|
||||
this.label2pc = {};
|
||||
this.allstmts = program.stmts;
|
||||
this.label2pc = program.labels;
|
||||
this.label2dataptr = {};
|
||||
this.allstmts = [];
|
||||
this.line2pc = [];
|
||||
this.pc2line = new Map();
|
||||
this.pc2label = new Map();
|
||||
this.datums = [];
|
||||
this.builtins = this.getBuiltinFunctions();
|
||||
// TODO: detect undeclared vars
|
||||
program.lines.forEach((line, idx) => {
|
||||
// make lookup tables
|
||||
var pc = this.allstmts.length;
|
||||
if (line.label != null) {
|
||||
this.label2pc[line.label] = pc;
|
||||
this.pc2label.set(pc, line.label);
|
||||
}
|
||||
this.line2pc.push(pc);
|
||||
this.pc2line.set(pc, idx);
|
||||
// combine all statements into single list
|
||||
line.stmts.forEach((stmt) => this.allstmts.push(stmt));
|
||||
});
|
||||
// build PC -> label lookup
|
||||
for (var label in program.labels) {
|
||||
var targetpc = program.labels[label];
|
||||
this.pc2label.set(targetpc, label);
|
||||
}
|
||||
// compile statements ahead of time
|
||||
this.allstmts.forEach((stmt, pc) => {
|
||||
this.curpc = pc + 1; // for error reporting
|
||||
@ -278,10 +267,11 @@ export class BASICRuntime {
|
||||
stmt.$run();
|
||||
}
|
||||
|
||||
// TODO: this only works because each line has a label
|
||||
skipToEOL() {
|
||||
do {
|
||||
this.curpc++;
|
||||
} while (this.curpc < this.allstmts.length && !this.pc2line.get(this.curpc));
|
||||
} while (this.curpc < this.allstmts.length && !this.pc2label.get(this.curpc));
|
||||
}
|
||||
|
||||
skipToElse() {
|
||||
@ -292,7 +282,7 @@ export class BASICRuntime {
|
||||
if (cmd == 'ELSE') { this.curpc++; break; }
|
||||
else if (cmd == 'IF') return this.skipToEOL();
|
||||
this.curpc++;
|
||||
if (this.pc2line.get(this.curpc))
|
||||
if (this.pc2label.get(this.curpc))
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -380,9 +370,10 @@ export class BASICRuntime {
|
||||
this.column = 0;
|
||||
str = obj;
|
||||
} else if (obj == '\t') {
|
||||
var curgroup = Math.floor(this.column / this.opts.printZoneLength);
|
||||
var l = this.opts.printZoneLength;
|
||||
var curgroup = Math.floor(this.column / l);
|
||||
var nextcol = (curgroup + 1) * this.opts.printZoneLength;
|
||||
if (nextcol >= this.margin) { this.column = 0; str = "\n"; } // return to left margin
|
||||
if (nextcol+l > this.margin) { this.column = 0; str = "\n"; } // return to left margin
|
||||
else str = this.TAB(nextcol); // next column
|
||||
} else {
|
||||
str = `${obj}`;
|
||||
|
@ -8,7 +8,7 @@ import { BASICProgram } from "../common/basic/compiler";
|
||||
import { TeleTypeWithKeyboard } from "../common/teletype";
|
||||
|
||||
const BASIC_PRESETS = [
|
||||
{ id: 'hello.bas', name: 'Hello World' },
|
||||
{ id: 'hello.bas', name: 'Tutorial' },
|
||||
{ id: 'sieve.bas', name: 'Sieve Benchmark' },
|
||||
{ id: 'mortgage.bas', name: 'Interest Calculator' },
|
||||
{ id: '23match.bas', name: '23 Matches' },
|
||||
@ -27,7 +27,7 @@ class BASICPlatform implements Platform {
|
||||
clock: number = 0;
|
||||
timer: AnimationTimer;
|
||||
tty: TeleTypeWithKeyboard;
|
||||
hotReload: boolean = true;
|
||||
hotReload: boolean = false;
|
||||
animcount: number = 0;
|
||||
|
||||
constructor(mainElement: HTMLElement) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user