Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,13 @@ Matanuska can do the following:
2. Define variables
3. Check conditionals
4. Run loops
5. Edit files in a BASIC style REPL
5. Define functions
6. Edit files in a BASIC style REPL

However, some important capabilities are still missing, including (but not limited to):

1. System commands and shell jobs
2. Functions
2. Define closures
3. Arrays and other higher level types
4. Compiler level type assertions
5. Multi-line instructions
Expand All @@ -58,9 +59,14 @@ For more information, check out the backlog...

### Prioritized Backlog

- [ ] Implement `onward` in loops
- [x] Initial implementation
- [x] Fixture CLI tests
- [ ] Fixture/snapshot compiler tests
- [ ] Implement visiting `onward` inside conditionals
- [ ] Get fixture tests working
- [ ] Date/Time/Duration/TZData functions
- [ ] Closures
- [ ] Implement `onward` in loops
- [ ] Refactor command input
- `Cmd` in AST references a list of Instructions, not a "command" as in ADR 024
- Should probably just use a `Line`
Expand Down
92 changes: 71 additions & 21 deletions compiler/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { showChunk } from '../debug';
import { errorType } from '../errors';
import {
AssertionError,
NotImplementedError,
SyntaxError,
ParseError,
ParseWarning,
Expand Down Expand Up @@ -130,8 +131,23 @@ export function isRootBlock(block: Block): boolean {
// relevant blocks are defined.
let ElseBlock: any = null;
let ElseIfBlock: any = null;
let ForBlock: any = null;
let WhileBlock: any = null;
let RepeatBlock: any = null;

class IfBlock extends Block {
class ConditionalBlock extends Block {
visitOnwardInstr(onward: Onward): void {
const block = this.find(ForBlock, WhileBlock, RepeatBlock);
if (!block) {
this.mismatched(onward, 'onward');
Comment thread
jfhbrook marked this conversation as resolved.
Resolved
return;
}

block.visitOnwardInstr(onward);
}
}

class IfBlock extends ConditionalBlock {
kind = 'if';

constructor(public elseJump: Short) {
Expand All @@ -157,7 +173,7 @@ class IfBlock extends Block {
}
}

class _ElseBlock extends Block {
class _ElseBlock extends ConditionalBlock {
kind = 'else';

constructor(public endJump: Short) {
Expand All @@ -167,7 +183,10 @@ class _ElseBlock extends Block {
visitEndIfInstr(_endIf: EndIf): void {
this.compiler.endIf(this.endJump);
this.end();
this.unwind();
}

private unwind(): void {
let block: any = this.previous;
while (block instanceof ElseIfBlock) {
block.compiler.endIf(block.endJump);
Expand All @@ -190,7 +209,10 @@ class _ElseIfBlock extends IfBlock {
visitEndIfInstr(_endIf: EndIf): void {
const endJump = this.compiler.beginElse(this.elseJump);
this.compiler.endIf(endJump);
this.unwind();
}

private unwind(): void {
// eslint-disable-next-line @typescript-eslint/no-this-alias
let block: any = this;
while (block instanceof ElseIfBlock) {
Expand All @@ -201,15 +223,11 @@ class _ElseIfBlock extends IfBlock {
}
}

// Update the references here
ElseBlock = _ElseBlock;
ElseIfBlock = _ElseIfBlock;

//
// For, while, and repeat/until
// Looping blocks
//

class ForBlock extends Block {
class _ForBlock extends Block {
kind = 'for';

constructor(
Expand All @@ -219,9 +237,8 @@ class ForBlock extends Block {
super();
}

// TODO
visitOnwardInstr(onward: Onward): void {
super.visitOnwardInstr(onward);
visitOnwardInstr(_onward: Onward): void {
this.compiler.continueFor(this.incrStart);
}

visitNextInstr(_next: Next): void {
Expand All @@ -230,7 +247,7 @@ class ForBlock extends Block {
}
}

class WhileBlock extends Block {
class _WhileBlock extends Block {
kind = 'while';

constructor(
Expand All @@ -240,9 +257,8 @@ class WhileBlock extends Block {
super();
}

// TODO
visitOnwardInstr(onward: Onward): void {
super.visitOnwardInstr(onward);
visitOnwardInstr(_onward: Onward): void {
this.compiler.continueWhile(this.loopStart);
}

visitEndWhileInstr(_endWhile: EndWhile): void {
Expand All @@ -251,24 +267,33 @@ class WhileBlock extends Block {
}
}

class RepeatBlock extends Block {
class _RepeatBlock extends Block {
kind = 'repeat';

continues: Short[];

constructor(public startJump: Short) {
super();
this.continues = [];
}

// TODO
visitOnwardInstr(onward: Onward): void {
super.visitOnwardInstr(onward);
visitOnwardInstr(_onward: Onward): void {
this.compiler.continueRepeat(this.continues);
}

visitUntilInstr(until: Until): void {
this.compiler.endRepeat(until, this.startJump);
this.compiler.endRepeat(until, this.startJump, this.continues);
this.end();
}
}

// Update the references here
ElseBlock = _ElseBlock;
ElseIfBlock = _ElseIfBlock;
ForBlock = _ForBlock;
WhileBlock = _WhileBlock;
RepeatBlock = _RepeatBlock;

//
// Functions
//
Expand Down Expand Up @@ -918,6 +943,10 @@ export class LineCompiler implements InstrVisitor<void>, ExprVisitor<void> {
this.patchJump(exitJump);
}

continueFor(incrStart: Short): void {
this.emitLoop(incrStart);
}

visitWhileInstr(while_: While): void {
const [loopStart, exitJump] = this.beginWhile(while_.condition);
this.block.begin(while_, new WhileBlock(loopStart, exitJump));
Expand All @@ -943,6 +972,10 @@ export class LineCompiler implements InstrVisitor<void>, ExprVisitor<void> {
this.patchJump(exitJump);
}

continueWhile(loopStart: Short): void {
this.emitLoop(loopStart);
}

visitRepeatInstr(repeat: Repeat): void {
const loopStart = this.chunk.code.length;
this.block.begin(repeat, new RepeatBlock(loopStart));
Expand All @@ -952,15 +985,32 @@ export class LineCompiler implements InstrVisitor<void>, ExprVisitor<void> {
this.block.handle(until);
}

endRepeat(until: Until, start: Short): void {
endRepeat(until: Until, start: Short, continues: Short[]): void {
// Continues jump to the bottom
for (const cont of continues) {
this.patchJump(cont);
}

// Check the condition
until.condition.accept(this);

// Exit if false
const exitJump = this.emitJump(OpCode.JumpIfFalse);

// Finnegan begin again
this.emitByte(OpCode.Pop);
this.emitLoop(start);

// Exit here
this.patchJump(exitJump);
// Pop the conditional check
this.emitByte(OpCode.Pop);
}

continueRepeat(continues: Short[]): void {
// We'll jump to the conditional check later
const jump = this.emitJump(OpCode.Jump);
continues.push(jump);
}

visitOnwardInstr(onward: Onward): void {
Expand Down
19 changes: 18 additions & 1 deletion compiler/block.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { OpCode } from '../bytecode/opcodes';
import { AssertionError, RuntimeError } from '../exceptions';
import {
AssertionError,
NotImplementedError,
RuntimeError,
} from '../exceptions';
import { RuntimeFault } from '../faults';
import { formatter } from '../format';
import {
Expand Down Expand Up @@ -103,6 +107,19 @@ export abstract class Block implements InstrVisitor<void> {
this.compiler.block = this.parent;
}

public find(...types: any[]): Block | null {
let block: Block | null = this;
while (true) {
if (block === null) {
return block;
}
if (types.some((t) => block instanceof t)) {
return block;
}
block = block.parent || null;
}
}

public handle(instr: Instr): void {
instr.accept(this);
}
Expand Down
68 changes: 68 additions & 0 deletions test/__snapshots__/cli.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,71 @@ exports[`examples > 011-datetime.bas 1`] = `
",
}
`;

exports[`fixtures > 001-for-onward.bas 1`] = `
{
"exitCode": 0,
"stderr": "",
"stdout": "1
2
3
4
5
biggers
biggers
biggers
biggers
biggers
",
}
`;

exports[`fixtures > 002-while-onward.bas 1`] = `
{
"exitCode": 70,
"stderr": "TypeError: Invalid operand for < on undef: integer
Comment thread
jfhbrook marked this conversation as resolved.
Resolved
Value: 10
From: integer
To: invalid
",
"stdout": "",
}
`;

exports[`fixtures > 003-repeat-onward.bas 1`] = `
{
"exitCode": 70,
"stderr": "=== RUNTIME FAULT ===

--- Internal Stack Trace ---
RuntimeFault: Cannot cast nil to unknown
at castFault (/Users/josh/code/jfhbrook/matanuska/value/cast.ts:29:11)
at castNil (/Users/josh/code/jfhbrook/matanuska/value/cast.ts:260:10)
at cast (/Users/josh/code/jfhbrook/matanuska/value/cast.ts:368:14)
at Module.add (/Users/josh/code/jfhbrook/matanuska/operations.ts:76:24)
at Runtime.<anonymous> (/Users/josh/code/jfhbrook/matanuska/runtime.ts:335:34)
at Generator.next (<anonymous>)
at asyncGeneratorStep (/Users/josh/code/jfhbrook/matanuska/runtime.ts:18:28)
at _next (/Users/josh/code/jfhbrook/matanuska/runtime.ts:36:17)
at /Users/josh/code/jfhbrook/matanuska/runtime.ts:41:13
at new Promise (<anonymous>)

--- Versions ---
Matanuska BASIC: v0.0.1
swc: v1.7.26
vite: v1.7.26
Node.js: v25.2.1

This is a bug in Matanuska BASIC. If you copy this entire message and post it
to the issues tracker:

https://github.com/jfhbrook/matanuska/issues

the developers will do their best to fix it - and more importantly, they will
owe you a beer, coffee or beverage of your choice. 🍻


",
"stdout": "",
}
`;
39 changes: 22 additions & 17 deletions test/cli.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,27 @@ import { basename } from 'node:path';
import { describe, expect, test } from 'vitest';

import { run } from './helpers/cli';
import { EXAMPLES } from './helpers/files';
import { EXAMPLES, FIXTURES } from './helpers/files';

describe('examples', () => {
for (const p of Object.keys(EXAMPLES)) {
const name = basename(p);
switch (name) {
// TODO: Some scripts will need mocked input
default:
test(name, async () => {
const { exitCode, host } = await run([p], process.env);
expect({
exitCode,
stdout: host.stdout.output,
stderr: host.stderr.output,
}).toMatchSnapshot();
});
function scriptsSuite(files: Record<string, string>): () => void {
return () => {
for (const p of Object.keys(files)) {
const name = basename(p);
switch (name) {
// TODO: Some scripts will need mocked input
default:
test(name, async () => {
const { exitCode, host } = await run([p], process.env);
expect({
exitCode,
stdout: host.stdout.output,
stderr: host.stderr.output,
}).toMatchSnapshot();
});
}
}
}
});
};
}

describe('examples', scriptsSuite(EXAMPLES));
describe('fixtures', scriptsSuite(FIXTURES));
Loading