mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-09-27 23:55:04 +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
|
OPTION DIALECT DARTMOUTH
|
||||||
10 PRINT "HELLO! LET'S PROGRAM IN BASIC."
|
001 REM THIS IS A PROGRAM WRITTEN IN BASIC.
|
||||||
15 PRINT
|
002 REM YOU CAN WRITE ONE STATEMENT PER LINE.
|
||||||
20 INPUT "WOULD YOU MIND TYPING IN YOUR NAME";A$
|
003 REM EACH STATEMENT MUST HAVE A LINE NUMBER.
|
||||||
25 PRINT
|
004 REM THE "REM" LINES ARE COMMENTS, WHICH ARE IGNORED
|
||||||
30 PRINT "THANKS, ";A$;"! THIS WILL BE FUN!"
|
005 ' YOU CAN ALSO DO COMMENTS WITH APOSTROPHES
|
||||||
35 PRINT
|
006 ' LET'S START WITH A "PRINT"...
|
||||||
40 INPUT "NOW TELL ME YOUR FAVORITE NUMBER";N
|
100 PRINT "HELLO! LET'S PROGRAM IN BASIC."
|
||||||
45 PRINT
|
101 PRINT ' <-- THIS PRINTS A BLANK LINE, IT LOOKS NICER
|
||||||
50 PRINT "THAT'S A GOOD ONE! I LIKE";N^2;"MYSELF."
|
105 ' "INPUT" WAITS FOR USER INPUT FROM THE KEYBOARD
|
||||||
60 PRINT "NICE MEETING YOU, ";A$;"."
|
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
|
999 END
|
||||||
|
@ -8,6 +8,7 @@ export interface BASICOptions {
|
|||||||
uppercaseOnly : boolean; // convert everything to uppercase?
|
uppercaseOnly : boolean; // convert everything to uppercase?
|
||||||
optionalLabels : boolean; // can omit line numbers and use labels?
|
optionalLabels : boolean; // can omit line numbers and use labels?
|
||||||
optionalWhitespace : boolean; // can "crunch" keywords? also, eat extra ":" delims
|
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
|
varNaming : 'A'|'A1'|'AA'|'*'; // only allow A0-9 for numerics, single letter for arrays/strings
|
||||||
squareBrackets : boolean; // "[" and "]" interchangable with "(" and ")"?
|
squareBrackets : boolean; // "[" and "]" interchangable with "(" and ")"?
|
||||||
tickComments : boolean; // support 'comments?
|
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 type ScopeEndStatement = NEXT_Statement | WEND_Statement;
|
||||||
|
|
||||||
export interface BASICLine {
|
|
||||||
label: string;
|
|
||||||
stmts: Statement[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BASICProgram {
|
export interface BASICProgram {
|
||||||
opts: BASICOptions;
|
opts: BASICOptions;
|
||||||
lines: BASICLine[];
|
stmts: Statement[];
|
||||||
|
labels: { [label: string]: number }; // label -> PC
|
||||||
}
|
}
|
||||||
|
|
||||||
class Token {
|
class Token {
|
||||||
@ -326,9 +323,10 @@ export class BASICParser {
|
|||||||
opts : BASICOptions = DIALECTS['DEFAULT'];
|
opts : BASICOptions = DIALECTS['DEFAULT'];
|
||||||
optionCount : number; // how many OPTION stmts so far?
|
optionCount : number; // how many OPTION stmts so far?
|
||||||
maxlinelen : number = 255; // maximum line length
|
maxlinelen : number = 255; // maximum line length
|
||||||
|
stmts : Statement[];
|
||||||
errors: WorkerError[];
|
errors: WorkerError[];
|
||||||
listings: CodeListingMap;
|
listings: CodeListingMap;
|
||||||
labels: { [label: string]: BASICLine };
|
labels: { [label: string]: number }; // label -> PC
|
||||||
targets: { [targetlabel: string]: SourceLocation };
|
targets: { [targetlabel: string]: SourceLocation };
|
||||||
varrefs: { [name: string]: SourceLocation }; // references
|
varrefs: { [name: string]: SourceLocation }; // references
|
||||||
fnrefs: { [name: string]: string[] }; // DEF FN call graph
|
fnrefs: { [name: string]: string[] }; // DEF FN call graph
|
||||||
@ -343,11 +341,12 @@ export class BASICParser {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.optionCount = 0;
|
this.optionCount = 0;
|
||||||
|
this.lineno = 0;
|
||||||
|
this.curlabel = null;
|
||||||
|
this.stmts = [];
|
||||||
this.labels = {};
|
this.labels = {};
|
||||||
this.targets = {};
|
this.targets = {};
|
||||||
this.errors = [];
|
this.errors = [];
|
||||||
this.lineno = 0;
|
|
||||||
this.curlabel = null;
|
|
||||||
this.listings = {};
|
this.listings = {};
|
||||||
this.varrefs = {};
|
this.varrefs = {};
|
||||||
this.fnrefs = {};
|
this.fnrefs = {};
|
||||||
@ -394,7 +393,8 @@ export class BASICParser {
|
|||||||
pushbackToken(tok: Token) {
|
pushbackToken(tok: Token) {
|
||||||
this.tokens.unshift(tok);
|
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();
|
let tok = this.consumeToken();
|
||||||
switch (tok.type) {
|
switch (tok.type) {
|
||||||
case TokenType.Ident:
|
case TokenType.Ident:
|
||||||
@ -410,8 +410,9 @@ export class BASICParser {
|
|||||||
} else
|
} else
|
||||||
this.dialectError(`Each line must begin with a line number`);
|
this.dialectError(`Each line must begin with a line number`);
|
||||||
case TokenType.Int:
|
case TokenType.Int:
|
||||||
this.setCurrentLabel(line, tok.str);
|
this.addLabel(tok.str);
|
||||||
break;
|
return;
|
||||||
|
// label added, return from function... other cases add default label
|
||||||
case TokenType.HexOctalInt:
|
case TokenType.HexOctalInt:
|
||||||
case TokenType.Float:
|
case TokenType.Float:
|
||||||
this.compileError(`Line numbers must be positive integers.`);
|
this.compileError(`Line numbers must be positive integers.`);
|
||||||
@ -429,30 +430,41 @@ export class BASICParser {
|
|||||||
case TokenType.Remark:
|
case TokenType.Remark:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// add default label
|
||||||
|
this.addLabel('#'+this.lineno);
|
||||||
}
|
}
|
||||||
setCurrentLabel(line: BASICLine, str: string) {
|
getPC() : number {
|
||||||
if (this.labels[str] != null) this.compileError(`There's a duplicated label "${str}".`);
|
return this.stmts.length;
|
||||||
this.labels[str] = line;
|
}
|
||||||
line.label = str;
|
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.curlabel = str;
|
||||||
this.tokens.forEach((tok) => tok.$loc.label = str);
|
this.tokens.forEach((tok) => tok.$loc.label = str);
|
||||||
}
|
}
|
||||||
parseFile(file: string, path: string) : BASICProgram {
|
parseFile(file: string, path: string) : BASICProgram {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
var txtlines = file.split(/\n|\r\n?/);
|
var txtlines = file.split(/\n|\r\n?/);
|
||||||
var pgmlines = txtlines.map((line) => this.parseLine(line));
|
txtlines.forEach((line) => this.parseLine(line));
|
||||||
var program = { opts: this.opts, lines: pgmlines };
|
var program = { opts: this.opts, stmts: this.stmts, labels: this.labels };
|
||||||
this.checkAll(program);
|
this.checkAll(program);
|
||||||
this.listings[path] = this.generateListing(file, program);
|
this.listings[path] = this.generateListing(file, program);
|
||||||
return program;
|
return program;
|
||||||
}
|
}
|
||||||
parseLine(line: string) : BASICLine {
|
parseLine(line: string) : void {
|
||||||
try {
|
try {
|
||||||
this.tokenize(line);
|
this.tokenize(line);
|
||||||
return this.parse();
|
this.parse();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!(e instanceof CompileError)) throw e; // ignore compile errors since errors[] list captures them
|
if (!(e instanceof CompileError)) throw e; // ignore compile errors since errors[] list captures them
|
||||||
return {label:null, stmts:[]};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_tokenize(line: string) : void {
|
_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.`);
|
if (line.length > this.maxlinelen) this.compileError(`A line should be no more than ${this.maxlinelen} characters long.`);
|
||||||
this._tokenize(line);
|
this._tokenize(line);
|
||||||
}
|
}
|
||||||
parse() : BASICLine {
|
parse() : void {
|
||||||
var line = {label: null, stmts: []};
|
|
||||||
// not empty line?
|
// not empty line?
|
||||||
if (this.tokens.length) {
|
if (this.tokens.length) {
|
||||||
this.parseOptLabel(line);
|
this.parseOptLabel();
|
||||||
if (this.tokens.length) {
|
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;
|
this.curlabel = null;
|
||||||
}
|
}
|
||||||
return line;
|
|
||||||
}
|
}
|
||||||
parseCompoundStatement(): Statement[] {
|
parseCompoundStatement() : void {
|
||||||
var list = this.parseList(this.parseStatement, ':');
|
if (this.opts.multipleStmtsPerLine) {
|
||||||
var next = this.peekToken();
|
this.parseList(this.parseStatement, ':');
|
||||||
if (!isEOS(next))
|
} else {
|
||||||
this.compileError(`Expected end of line or ':'`, next.$loc);
|
this.parseList(this.parseStatement, '\0');
|
||||||
if (next.str == 'ELSE')
|
if (this.peekToken().str == ':') this.dialectErrorNoSupport(`multiple statements on a line`);
|
||||||
return list.concat(this.parseCompoundStatement());
|
}
|
||||||
else
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
validKeyword(keyword: string) : string {
|
validKeyword(keyword: string) : string {
|
||||||
return (this.opts.validKeywords && this.opts.validKeywords.indexOf(keyword) < 0) ? null : keyword;
|
return (this.opts.validKeywords && this.opts.validKeywords.indexOf(keyword) < 0) ? null : keyword;
|
||||||
@ -578,7 +588,6 @@ export class BASICParser {
|
|||||||
if (this.validKeyword(cmd) == null)
|
if (this.validKeyword(cmd) == null)
|
||||||
this.dialectErrorNoSupport(`the ${cmd} statement`);
|
this.dialectErrorNoSupport(`the ${cmd} statement`);
|
||||||
stmt = fn.bind(this)();
|
stmt = fn.bind(this)();
|
||||||
this.modifyScope(stmt);
|
|
||||||
break;
|
break;
|
||||||
} else if (this.peekToken().str == '=' || this.peekToken().str == '(') {
|
} else if (this.peekToken().str == '=' || this.peekToken().str == '(') {
|
||||||
if (!this.opts.optionalLet)
|
if (!this.opts.optionalLet)
|
||||||
@ -596,7 +605,8 @@ export class BASICParser {
|
|||||||
this.compileError(`There should be a command here.`);
|
this.compileError(`There should be a command here.`);
|
||||||
return null;
|
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;
|
return stmt;
|
||||||
}
|
}
|
||||||
// check scope stuff (if compiledBlocks is true)
|
// check scope stuff (if compiledBlocks is true)
|
||||||
@ -898,30 +908,39 @@ export class BASICParser {
|
|||||||
return { command: cmd, label: expr };
|
return { command: cmd, label: expr };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stmt__IF(): IF_Statement {
|
stmt__IF(): void {
|
||||||
|
var cmdtok = this.lasttoken;
|
||||||
var cond = this.parseExpr();
|
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)
|
// we accept GOTO or THEN if line number provided (DEC accepts GO TO)
|
||||||
var thengoto = this.expectTokens(['THEN','GOTO','GO']);
|
var thengoto = this.expectTokens(['THEN','GOTO','GO']);
|
||||||
if (thengoto.str == 'GO') this.expectToken('TO');
|
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();
|
var lineno = this.peekToken();
|
||||||
// assume GOTO if number given after THEN
|
// assume GOTO if number given after THEN
|
||||||
if (lineno.type == TokenType.Int) {
|
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 {
|
stmt__FOR() : FOR_Statement {
|
||||||
var lexpr = this.parseForNextLexpr();
|
var lexpr = this.parseForNextLexpr();
|
||||||
@ -1124,16 +1143,13 @@ export class BASICParser {
|
|||||||
// for workermain
|
// for workermain
|
||||||
generateListing(file: string, program: BASICProgram) {
|
generateListing(file: string, program: BASICProgram) {
|
||||||
var srclines = [];
|
var srclines = [];
|
||||||
var pc = 0;
|
|
||||||
var laststmt : Statement;
|
var laststmt : Statement;
|
||||||
program.lines.forEach((line, idx) => {
|
program.stmts.forEach((stmt, idx) => {
|
||||||
line.stmts.forEach((stmt) => {
|
|
||||||
laststmt = stmt;
|
laststmt = stmt;
|
||||||
var sl = stmt.$loc;
|
var sl = stmt.$loc;
|
||||||
sl.offset = pc++; // TODO: could Statement have offset field?
|
sl.offset = idx;
|
||||||
srclines.push(sl);
|
srclines.push(sl);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
if (this.opts.endStmtRequired && (laststmt == null || laststmt.command != 'END'))
|
if (this.opts.endStmtRequired && (laststmt == null || laststmt.command != 'END'))
|
||||||
this.dialectError(`All programs must have a final END statement`);
|
this.dialectError(`All programs must have a final END statement`);
|
||||||
return { lines: srclines };
|
return { lines: srclines };
|
||||||
@ -1172,6 +1188,7 @@ export const ECMA55_MINIMAL : BASICOptions = {
|
|||||||
uppercaseOnly : true,
|
uppercaseOnly : true,
|
||||||
optionalLabels : false,
|
optionalLabels : false,
|
||||||
optionalWhitespace : false,
|
optionalWhitespace : false,
|
||||||
|
multipleStmtsPerLine : false,
|
||||||
varNaming : "A1",
|
varNaming : "A1",
|
||||||
staticArrays : true,
|
staticArrays : true,
|
||||||
sharedArrayNamespace : true,
|
sharedArrayNamespace : true,
|
||||||
@ -1220,6 +1237,7 @@ export const DARTMOUTH_4TH_EDITION : BASICOptions = {
|
|||||||
uppercaseOnly : true,
|
uppercaseOnly : true,
|
||||||
optionalLabels : false,
|
optionalLabels : false,
|
||||||
optionalWhitespace : false,
|
optionalWhitespace : false,
|
||||||
|
multipleStmtsPerLine : false,
|
||||||
varNaming : "A1",
|
varNaming : "A1",
|
||||||
staticArrays : true,
|
staticArrays : true,
|
||||||
sharedArrayNamespace : false,
|
sharedArrayNamespace : false,
|
||||||
@ -1271,6 +1289,7 @@ export const TINY_BASIC : BASICOptions = {
|
|||||||
uppercaseOnly : true,
|
uppercaseOnly : true,
|
||||||
optionalLabels : false,
|
optionalLabels : false,
|
||||||
optionalWhitespace : false,
|
optionalWhitespace : false,
|
||||||
|
multipleStmtsPerLine : false,
|
||||||
varNaming : "A",
|
varNaming : "A",
|
||||||
staticArrays : false,
|
staticArrays : false,
|
||||||
sharedArrayNamespace : true,
|
sharedArrayNamespace : true,
|
||||||
@ -1317,6 +1336,7 @@ export const HP_TIMESHARED_BASIC : BASICOptions = {
|
|||||||
uppercaseOnly : false, // the terminal is usually uppercase
|
uppercaseOnly : false, // the terminal is usually uppercase
|
||||||
optionalLabels : false,
|
optionalLabels : false,
|
||||||
optionalWhitespace : false,
|
optionalWhitespace : false,
|
||||||
|
multipleStmtsPerLine : true,
|
||||||
varNaming : "A1",
|
varNaming : "A1",
|
||||||
staticArrays : true,
|
staticArrays : true,
|
||||||
sharedArrayNamespace : false,
|
sharedArrayNamespace : false,
|
||||||
@ -1370,6 +1390,7 @@ export const DEC_BASIC_11 : BASICOptions = {
|
|||||||
uppercaseOnly : true, // translates all lower to upper
|
uppercaseOnly : true, // translates all lower to upper
|
||||||
optionalLabels : false,
|
optionalLabels : false,
|
||||||
optionalWhitespace : false,
|
optionalWhitespace : false,
|
||||||
|
multipleStmtsPerLine : false, // actually "\"
|
||||||
varNaming : "A1",
|
varNaming : "A1",
|
||||||
staticArrays : true,
|
staticArrays : true,
|
||||||
sharedArrayNamespace : false,
|
sharedArrayNamespace : false,
|
||||||
@ -1424,6 +1445,7 @@ export const DEC_BASIC_PLUS : BASICOptions = {
|
|||||||
uppercaseOnly : false,
|
uppercaseOnly : false,
|
||||||
optionalLabels : false,
|
optionalLabels : false,
|
||||||
optionalWhitespace : false,
|
optionalWhitespace : false,
|
||||||
|
multipleStmtsPerLine : true,
|
||||||
varNaming : "A1",
|
varNaming : "A1",
|
||||||
staticArrays : true,
|
staticArrays : true,
|
||||||
sharedArrayNamespace : false,
|
sharedArrayNamespace : false,
|
||||||
@ -1485,6 +1507,7 @@ export const BASICODE : BASICOptions = {
|
|||||||
uppercaseOnly : false,
|
uppercaseOnly : false,
|
||||||
optionalLabels : false,
|
optionalLabels : false,
|
||||||
optionalWhitespace : true,
|
optionalWhitespace : true,
|
||||||
|
multipleStmtsPerLine : true,
|
||||||
varNaming : "AA",
|
varNaming : "AA",
|
||||||
staticArrays : true,
|
staticArrays : true,
|
||||||
sharedArrayNamespace : false,
|
sharedArrayNamespace : false,
|
||||||
@ -1535,6 +1558,7 @@ export const ALTAIR_BASIC41 : BASICOptions = {
|
|||||||
uppercaseOnly : true,
|
uppercaseOnly : true,
|
||||||
optionalLabels : false,
|
optionalLabels : false,
|
||||||
optionalWhitespace : true,
|
optionalWhitespace : true,
|
||||||
|
multipleStmtsPerLine : true,
|
||||||
varNaming : "*", // or AA
|
varNaming : "*", // or AA
|
||||||
staticArrays : false,
|
staticArrays : false,
|
||||||
sharedArrayNamespace : true,
|
sharedArrayNamespace : true,
|
||||||
@ -1593,6 +1617,7 @@ export const APPLESOFT_BASIC : BASICOptions = {
|
|||||||
uppercaseOnly : false,
|
uppercaseOnly : false,
|
||||||
optionalLabels : false,
|
optionalLabels : false,
|
||||||
optionalWhitespace : true,
|
optionalWhitespace : true,
|
||||||
|
multipleStmtsPerLine : true,
|
||||||
varNaming : "*", // or AA
|
varNaming : "*", // or AA
|
||||||
staticArrays : false,
|
staticArrays : false,
|
||||||
sharedArrayNamespace : false,
|
sharedArrayNamespace : false,
|
||||||
@ -1652,6 +1677,7 @@ export const BASIC80 : BASICOptions = {
|
|||||||
uppercaseOnly : false,
|
uppercaseOnly : false,
|
||||||
optionalLabels : false,
|
optionalLabels : false,
|
||||||
optionalWhitespace : true,
|
optionalWhitespace : true,
|
||||||
|
multipleStmtsPerLine : true,
|
||||||
varNaming : "*",
|
varNaming : "*",
|
||||||
staticArrays : false,
|
staticArrays : false,
|
||||||
sharedArrayNamespace : true,
|
sharedArrayNamespace : true,
|
||||||
@ -1712,6 +1738,7 @@ export const MODERN_BASIC : BASICOptions = {
|
|||||||
uppercaseOnly : false,
|
uppercaseOnly : false,
|
||||||
optionalLabels : true,
|
optionalLabels : true,
|
||||||
optionalWhitespace : false,
|
optionalWhitespace : false,
|
||||||
|
multipleStmtsPerLine : true,
|
||||||
varNaming : "*",
|
varNaming : "*",
|
||||||
staticArrays : false,
|
staticArrays : false,
|
||||||
sharedArrayNamespace : false,
|
sharedArrayNamespace : false,
|
||||||
@ -1749,6 +1776,7 @@ export const MODERN_BASIC : BASICOptions = {
|
|||||||
// TODO: integer vars
|
// TODO: integer vars
|
||||||
// TODO: DEFINT/DEFSTR
|
// TODO: DEFINT/DEFSTR
|
||||||
// TODO: excess INPUT ignored, error msg
|
// TODO: excess INPUT ignored, error msg
|
||||||
|
// TODO: out of order line numbers
|
||||||
|
|
||||||
export const DIALECTS = {
|
export const DIALECTS = {
|
||||||
"DEFAULT": MODERN_BASIC,
|
"DEFAULT": MODERN_BASIC,
|
||||||
|
@ -72,8 +72,6 @@ export class BASICRuntime {
|
|||||||
|
|
||||||
program : basic.BASICProgram;
|
program : basic.BASICProgram;
|
||||||
allstmts : basic.Statement[];
|
allstmts : basic.Statement[];
|
||||||
line2pc : number[];
|
|
||||||
pc2line : Map<number,number>;
|
|
||||||
pc2label : Map<number,string>;
|
pc2label : Map<number,string>;
|
||||||
label2pc : {[label : string] : number};
|
label2pc : {[label : string] : number};
|
||||||
label2dataptr : {[label : string] : number};
|
label2dataptr : {[label : string] : number};
|
||||||
@ -103,7 +101,7 @@ export class BASICRuntime {
|
|||||||
let prevlabel = null;
|
let prevlabel = null;
|
||||||
let prevpcofs = 0;
|
let prevpcofs = 0;
|
||||||
if (this.pc2label != null) {
|
if (this.pc2label != null) {
|
||||||
var pc = this.curpc;
|
let pc = this.curpc;
|
||||||
while (pc > 0 && (prevlabel = this.pc2label.get(pc)) == null) {
|
while (pc > 0 && (prevlabel = this.pc2label.get(pc)) == null) {
|
||||||
pc--;
|
pc--;
|
||||||
}
|
}
|
||||||
@ -114,27 +112,18 @@ export class BASICRuntime {
|
|||||||
this.program = program;
|
this.program = program;
|
||||||
this.opts = program.opts;
|
this.opts = program.opts;
|
||||||
if (!this.opts.maxArrayElements) this.opts.maxArrayElements = DEFAULT_MAX_ARRAY_ELEMENTS;
|
if (!this.opts.maxArrayElements) this.opts.maxArrayElements = DEFAULT_MAX_ARRAY_ELEMENTS;
|
||||||
this.label2pc = {};
|
this.allstmts = program.stmts;
|
||||||
|
this.label2pc = program.labels;
|
||||||
this.label2dataptr = {};
|
this.label2dataptr = {};
|
||||||
this.allstmts = [];
|
|
||||||
this.line2pc = [];
|
|
||||||
this.pc2line = new Map();
|
|
||||||
this.pc2label = new Map();
|
this.pc2label = new Map();
|
||||||
this.datums = [];
|
this.datums = [];
|
||||||
this.builtins = this.getBuiltinFunctions();
|
this.builtins = this.getBuiltinFunctions();
|
||||||
// TODO: detect undeclared vars
|
// TODO: detect undeclared vars
|
||||||
program.lines.forEach((line, idx) => {
|
// build PC -> label lookup
|
||||||
// make lookup tables
|
for (var label in program.labels) {
|
||||||
var pc = this.allstmts.length;
|
var targetpc = program.labels[label];
|
||||||
if (line.label != null) {
|
this.pc2label.set(targetpc, label);
|
||||||
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));
|
|
||||||
});
|
|
||||||
// compile statements ahead of time
|
// compile statements ahead of time
|
||||||
this.allstmts.forEach((stmt, pc) => {
|
this.allstmts.forEach((stmt, pc) => {
|
||||||
this.curpc = pc + 1; // for error reporting
|
this.curpc = pc + 1; // for error reporting
|
||||||
@ -278,10 +267,11 @@ export class BASICRuntime {
|
|||||||
stmt.$run();
|
stmt.$run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this only works because each line has a label
|
||||||
skipToEOL() {
|
skipToEOL() {
|
||||||
do {
|
do {
|
||||||
this.curpc++;
|
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() {
|
skipToElse() {
|
||||||
@ -292,7 +282,7 @@ export class BASICRuntime {
|
|||||||
if (cmd == 'ELSE') { this.curpc++; break; }
|
if (cmd == 'ELSE') { this.curpc++; break; }
|
||||||
else if (cmd == 'IF') return this.skipToEOL();
|
else if (cmd == 'IF') return this.skipToEOL();
|
||||||
this.curpc++;
|
this.curpc++;
|
||||||
if (this.pc2line.get(this.curpc))
|
if (this.pc2label.get(this.curpc))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -380,9 +370,10 @@ export class BASICRuntime {
|
|||||||
this.column = 0;
|
this.column = 0;
|
||||||
str = obj;
|
str = obj;
|
||||||
} else if (obj == '\t') {
|
} 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;
|
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 = this.TAB(nextcol); // next column
|
||||||
} else {
|
} else {
|
||||||
str = `${obj}`;
|
str = `${obj}`;
|
||||||
|
@ -8,7 +8,7 @@ import { BASICProgram } from "../common/basic/compiler";
|
|||||||
import { TeleTypeWithKeyboard } from "../common/teletype";
|
import { TeleTypeWithKeyboard } from "../common/teletype";
|
||||||
|
|
||||||
const BASIC_PRESETS = [
|
const BASIC_PRESETS = [
|
||||||
{ id: 'hello.bas', name: 'Hello World' },
|
{ id: 'hello.bas', name: 'Tutorial' },
|
||||||
{ id: 'sieve.bas', name: 'Sieve Benchmark' },
|
{ id: 'sieve.bas', name: 'Sieve Benchmark' },
|
||||||
{ id: 'mortgage.bas', name: 'Interest Calculator' },
|
{ id: 'mortgage.bas', name: 'Interest Calculator' },
|
||||||
{ id: '23match.bas', name: '23 Matches' },
|
{ id: '23match.bas', name: '23 Matches' },
|
||||||
@ -27,7 +27,7 @@ class BASICPlatform implements Platform {
|
|||||||
clock: number = 0;
|
clock: number = 0;
|
||||||
timer: AnimationTimer;
|
timer: AnimationTimer;
|
||||||
tty: TeleTypeWithKeyboard;
|
tty: TeleTypeWithKeyboard;
|
||||||
hotReload: boolean = true;
|
hotReload: boolean = false;
|
||||||
animcount: number = 0;
|
animcount: number = 0;
|
||||||
|
|
||||||
constructor(mainElement: HTMLElement) {
|
constructor(mainElement: HTMLElement) {
|
||||||
|
Loading…
Reference in New Issue
Block a user