Skip to content

Commit

Permalink
feat: 2.08 IF and ELSE IF support
Browse files Browse the repository at this point in the history
  • Loading branch information
remy committed Feb 28, 2024
1 parent a9f57c3 commit 92dd9a9
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 30 deletions.
34 changes: 34 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Error if `DIM z(size)` isn't called before usage

```
100 PROC x(a(),size,2) TO result:PRINT result:STOP
110 DEFPROC x(inp(),size,y)
120 LOCAL z(),tot
130 DIM z(size)
140 tot=0
150 FOR i=1 TO size:z(i)=inp(i)*y:NEXT i
160 FOR i=1 TO size:tot=tot+z(i):NEXT i
170 ENDPROC = tot
```



---


Test defaults in defproc:

```
100 PROC x(12,,"bob"):STOP
110 DEFPROC x(a=1,b$="alice",c$,d=-5)
120 LOCAL e=3
130 PRINT a,b$,c$,d,e
140 ENDPROC
```

---

## Validation

- Validate missing `ENDIF`
- `IF 0` alone should fail … right?
23 changes: 23 additions & 0 deletions __tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,26 @@ test('validateTxt', (t) => {
const res = validateTxt(src);
t.is(res.length, 0, 'no errors');
});

test('exact matches', (t) => {
const tests = [
`10 %a=1
20 PRINT (%a)+1`,
`100 REPEAT
110 INPUT n
120 IF n=33 THEN EXIT 150
130 REPEAT UNTIL n < 0
140 PRINT "Loop completed normally": STOP
150 PRINT "Loop ended early": STOP`,
];

tests.forEach((src) => {
src = src
.split('\n')
.map((_) => _.trim())
.join('\n');
const bytes = file2bas(src);
const txt = file2txt(bytes);
t.is(txt.trim(), src.trim(), 'matches');
});
});
22 changes: 11 additions & 11 deletions __tests__/txt2bas.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,7 @@ test('comments', (t) => {
test('end with $', (t) => {
let src = '202 IF INKEY$="s"';
let expect = [
0x00,
0xca,
0x07,
0x00,
0xfa,
0xa6,
0x3d,
0x22,
0x73,
0x22,
0x0d,
0x00, 0xca, 0x07, 0x00, 0xfa, 0xa6, 0x3d, 0x22, 0x73, 0x22, 0x0d,
];

const res = Array.from(parseLine(src));
Expand Down Expand Up @@ -303,6 +293,16 @@ test('INT function', (t) => {
t.is(token.name, 'NUMBER');
});

test('ELSE IF', (t) => {
let src, res;
src = '20 ELSE IF 1 < 2 PRINT "ELSE"';
res = parseLines(src).statements[0];

t.is(res.tokens[0].text, 'ELSE', 'has else');
t.is(res.tokens[1].text, 'IF', 'then if');
t.is(res.tokens[1].value, 0x83, 'correct IF value for following ELSE');
});

test('in the wild', (t) => {
let src, res;

Expand Down
37 changes: 25 additions & 12 deletions __tests__/validate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@ function contains(str) {
};
}

test('test bad if', (t) => {
const src = '10 IF 0';
const line = asBasic(src);
t.throws(
() => {
validateStatement(line);
},
contains('IF statement must have THEN'),
src
);
});
// test('test bad if', (t) => {
// const src = '10 IF 0';
// const line = asBasic(src);
// t.throws(
// () => {
// validateStatement(line);
// },
// contains('IF statement must have THEN'),
// src
// );
// });

test('validator works with autoline', async (t) => {
const fixture = await readFile(__dirname + '/fixtures/autoline.txt');
Expand Down Expand Up @@ -184,9 +184,22 @@ notThrows(
);
notThrows('10 %a = 10');
notThrows('10 PRINT ("TRUE" AND b)+("FALSE" AND NOT b)');
notThrows('100 PROC x(12,,”bob”):STOP');
notThrows('120 PRINT name$');
notThrows('10 DEF FN ian$(REF jenny$(),index)=jenny$(index)');
notThrows('10 LET x,y = y,x');
notThrows('10 a,b,c,d$,e$,f = 1,2,3,"xyz","zzz",g*h');
notThrows('10 y$ *= 2');
notThrows('120 PRINT "Press a key for left":x=INPUT -2');
notThrows('10 " Hello There! "[<+->]');
notThrows('10 a$(5)[-](3 TO 7)[<]');
notThrows('30 PRINT "perftest() took ";TIME;" frames"');
notThrows('20 PRINT %a+1');
notThrows('20 %a=1');

/********************************************/

// throws('20 PRINT (%a)+1');
throws(
'330 REPEAT UNTIL %(c=13) AND %(j > 8)',
'Cannot redeclare integer expression'
Expand All @@ -202,7 +215,7 @@ throws('10 DEFPROC _foo()', 'Function names can only contain letters');
throws('10 DEFPROC 5foo()', 'Function names can only contain letters');

throws('760 ', 'Empty line');
throws('945 IF %i = 20 ENDPROC', 'IF statement must have THEN');
// throws('945 IF %i = 20 ENDPROC', 'IF statement must have THEN');
throws('10 % sprite continue %', 'Cannot redeclare integer expression whilst');
throws('10 IF %b=%c THEN ENDPROC', 'Cannot redeclare integer expression', {
message: 'integer expression function on either side of IF comparator',
Expand Down
4 changes: 2 additions & 2 deletions codes.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export default {
// new in 2.08
0x81: 'TIME',
0x82: 'PRIVATE',
0x83: 'ELSEIF',
0x84: 'ENDIF', // internal use only: displays as IF, but indicates ELSE is present on same line
0x83: 'IF', // internal use only: displays as IF (which is managed here), but is converted from 0x83 via the aliases in op-table
0x84: 'ENDIF',
0x85: 'EXIT',
0x86: 'REF',

Expand Down
44 changes: 43 additions & 1 deletion txt2bas/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ import {
* @property {Statement} value
*/

/**
* Where a bank starts
*
* @typedef BankSplit
* @property {string} bankFile the filename
* @property {number} line the starting line
*/

/**
* Auto increment line
*
Expand Down Expand Up @@ -202,6 +210,7 @@ export function parseLineWithData(line, autoline = null) {
* @property {string} filename
* @property {Autoline} autoline
* @property {Define[]} defines
* @property {BankSplit[]} bankSplits
*/

/**
Expand Down Expand Up @@ -229,6 +238,8 @@ export function parseLines(
let filename = null;
const defines = [];

const bankSplits = [];

const autoline = new Autoline();

for (let i = 0; i < lines.length; i++) {
Expand All @@ -242,6 +253,11 @@ export function parseLines(
filename = line.split(' ')[1];
}

if (line.startsWith('#bankfile ')) {
const bankFile = line.split(' ')[1];
bankSplits.push({ bank: bankFile, line: i + 1 });
}

if (line.startsWith('#autoline')) {
autoline.parse(line);
}
Expand Down Expand Up @@ -311,6 +327,7 @@ export function parseLines(
filename,
autoline,
defines,
bankSplits,
};
}

Expand Down Expand Up @@ -758,6 +775,23 @@ export class Statement {
return this.line.substring(start, pos);
}

peekPrevToken(at = this.pos) {
let pos = at - 1;
const end = pos;
let allowSpace = true;
while (pos >= 0 || tests._isDigit(this.line.charAt(pos))) {
if (tests._isSpace(this.line.charAt(pos)) && !allowSpace) {
pos++;
break;
}
if (tests._isSpace(this.line.charAt(pos))) {
allowSpace = false;
}
pos--;
}
return this.line.substring(pos, end);
}

findOpCode(endPos) {
const peek = this.peek(endPos);
const moreToken = tests._isAlpha(peek);
Expand All @@ -771,8 +805,16 @@ export class Statement {
return false;
}

if (curr === 'IF') {
// look for ELSE before hand
const prev = this.peekPrevToken();
if (prev === 'ELSE') {
curr = 'ELSE IF';
}
}

// be wary that this could be something like `DEF FN`
if (peek === ' ' && !opTable[curr]) {
else if (peek === ' ' && !opTable[curr]) {
const next = this.peekToken(endPos + 1).toUpperCase();
const test = `${curr} ${next}`;

Expand Down
1 change: 1 addition & 0 deletions txt2bas/op-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ export const opTable = Object.entries(codes).reduce(
GOSUB: 0xed,
RAND: 0xf9,
CONT: 0xe8,
'ELSE IF': 0x83,
}
);
21 changes: 17 additions & 4 deletions txt2bas/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,11 @@ class Scope {
}

/**
* @returns {Token}
* @returns {Token|null}
*/
peekNext() {
let next = this.tokens[0];
if (!next) return null;
if (next.name === WHITE_SPACE) next = this.tokens[1];
return next;
}
Expand Down Expand Up @@ -343,6 +344,8 @@ export function validateStatement(tokens, debug = {}) {
// scope state
scope.push(OUTER_IF);
scope.push(IF);

// should I look ahead for a THEN to detect the type of IF?
}

if (value === opTable.UNTIL) {
Expand All @@ -358,8 +361,14 @@ export function validateStatement(tokens, debug = {}) {
scope.resetExpression();
}

if (value === opTable.ELSE && scope.includes(OUTER_IF)) {
throw new Error('Statement separator (:) expected before ELSE');
if (value === opTable.ELSE) {
const next = scope.peekNext();

if (next && next.text === IF) {
// change the token to ELSEIF and drop the IF
} else if (scope.includes(OUTER_IF)) {
throw new Error('Statement separator (:) expected before ELSE');
}
}

if (
Expand Down Expand Up @@ -675,7 +684,11 @@ export function validateStatementStarters(token, scope) {
*/
export function validateEndOfStatement(scope) {
if (scope.includes(IF)) {
throw new Error('IF statement must have THEN');
if (parser.getParser() === parser.v207) {
throw new Error('IF statement must have THEN');
} else {
// we need to stack up an open IF statement
}
}

const open = scope.findIndex((_) => _.startsWith('OPEN_'));
Expand Down

0 comments on commit 92dd9a9

Please sign in to comment.