Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ The interpreter uses a tree-walk evaluator on the syntax tree to execute a progr

Please don't.

## Running programs locally

If you just can't resist, there's a tiny CLI runner in `run-tabloid.js` that executes Tabloid source files with Node.js. Point it at any `.tabloid` file (see `examples/` for the Fibonacci and Mandelbrot demos we added) and it'll shout the output just like the website does.

```
node run-tabloid.js examples/fib.tabloid
```

The script wires `YOU WON'T WANT TO MISS` to stdout (always uppercased with a trailing `!`) and `LATEST NEWS ON` to a blocking stdin prompt, so you can pipe input or type it interactively.

## Limitations

Tabloid's syntax has some (soft) limitations for now, because I had ~8 hours to finish it. Here are some of them:
Expand Down
38 changes: 38 additions & 0 deletions examples/calculator.tabloid
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
YOU WON'T WANT TO MISS 'TABLOID FOUR FUNCTION CALCULATOR'

DISCOVER HOW TO toNumber WITH raw
RUMOR HAS IT
SHOCKING DEVELOPMENT raw TIMES 1
END OF STORY

DISCOVER HOW TO compute WITH left, right, op
RUMOR HAS IT
WHAT IF op IS ACTUALLY 'PLUS' RUMOR HAS IT
SHOCKING DEVELOPMENT left PLUS right
END OF STORY
LIES! WHAT IF op IS ACTUALLY 'MINUS' RUMOR HAS IT
SHOCKING DEVELOPMENT left MINUS right
END OF STORY
LIES! WHAT IF op IS ACTUALLY 'TIMES' RUMOR HAS IT
SHOCKING DEVELOPMENT left TIMES right
END OF STORY
LIES! WHAT IF op IS ACTUALLY 'DIVIDED BY' RUMOR HAS IT
SHOCKING DEVELOPMENT left DIVIDED BY right
END OF STORY
LIES!
SHOCKING DEVELOPMENT 'UNSUPPORTED OPERATOR'
END OF STORY

EXPERTS CLAIM firstInput TO BE LATEST NEWS ON 'Enter the first number:'
EXPERTS CLAIM secondInput TO BE LATEST NEWS ON 'Enter the second number:'
EXPERTS CLAIM opInput TO BE LATEST NEWS ON 'Type PLUS, MINUS, TIMES, or DIVIDED BY:'

EXPERTS CLAIM firstNumber TO BE toNumber OF firstInput
EXPERTS CLAIM secondNumber TO BE toNumber OF secondInput

EXPERTS CLAIM result TO BE compute OF firstNumber, secondNumber, opInput

YOU WON'T WANT TO MISS 'Result'
YOU WON'T WANT TO MISS result

PLEASE LIKE AND SUBSCRIBE
16 changes: 16 additions & 0 deletions examples/fib.tabloid
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
DISCOVER HOW TO fibonacci WITH a, b, n
RUMOR HAS IT
WHAT IF n SMALLER THAN 1
SHOCKING DEVELOPMENT b
LIES! RUMOR HAS IT
YOU WON'T WANT TO MISS b
SHOCKING DEVELOPMENT
fibonacci OF b, a PLUS b, n MINUS 1
END OF STORY
END OF STORY

EXPERTS CLAIM limit TO BE 10
YOU WON'T WANT TO MISS 'First 10 Fibonacci numbers'
EXPERTS CLAIM nothing TO BE fibonacci OF 0, 1, limit

PLEASE LIKE AND SUBSCRIBE
77 changes: 77 additions & 0 deletions examples/mandelbrot.tabloid
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
YOU WON'T WANT TO MISS 'TABLOID MANDELBROT SET'

DISCOVER HOW TO mandelbrot WITH cx, cy, zx, zy, iteration, maxIterations
RUMOR HAS IT
EXPERTS CLAIM lastIteration TO BE maxIterations MINUS 1
WHAT IF iteration BEATS lastIteration
SHOCKING DEVELOPMENT TOTALLY RIGHT
LIES! RUMOR HAS IT
EXPERTS CLAIM zxSquared TO BE zx TIMES zx
EXPERTS CLAIM zySquared TO BE zy TIMES zy
EXPERTS CLAIM magnitude TO BE zxSquared PLUS zySquared
WHAT IF magnitude BEATS 4
SHOCKING DEVELOPMENT COMPLETELY WRONG
LIES! RUMOR HAS IT
EXPERTS CLAIM difference TO BE zxSquared MINUS zySquared
EXPERTS CLAIM product TO BE zx TIMES zy
EXPERTS CLAIM doubleProduct TO BE 2 TIMES product
EXPERTS CLAIM nextZx TO BE difference PLUS cx
EXPERTS CLAIM nextZy TO BE doubleProduct PLUS cy
SHOCKING DEVELOPMENT
mandelbrot OF cx, cy, nextZx, nextZy, iteration PLUS 1, maxIterations
END OF STORY
END OF STORY
END OF STORY

DISCOVER HOW TO pixel WITH inside
RUMOR HAS IT
WHAT IF inside
SHOCKING DEVELOPMENT '#'
LIES!
SHOCKING DEVELOPMENT '.'
END OF STORY

DISCOVER HOW TO rowPixels WITH row, col, maxCols, maxRows, minX, maxX, minY, maxY, maxIterations
RUMOR HAS IT
EXPERTS CLAIM lastCol TO BE maxCols MINUS 1
WHAT IF col BEATS lastCol
SHOCKING DEVELOPMENT ''
LIES! RUMOR HAS IT
EXPERTS CLAIM xRange TO BE maxX MINUS minX
EXPERTS CLAIM yRange TO BE maxY MINUS minY
EXPERTS CLAIM colRatio TO BE col DIVIDED BY maxCols
EXPERTS CLAIM rowRatio TO BE row DIVIDED BY maxRows
EXPERTS CLAIM xOffset TO BE xRange TIMES colRatio
EXPERTS CLAIM yOffset TO BE yRange TIMES rowRatio
EXPERTS CLAIM xCoord TO BE minX PLUS xOffset
EXPERTS CLAIM yCoord TO BE minY PLUS yOffset
EXPERTS CLAIM inside TO BE mandelbrot OF xCoord, yCoord, 0, 0, 0, maxIterations
EXPERTS CLAIM char TO BE pixel OF inside
EXPERTS CLAIM remainder TO BE rowPixels OF row, col PLUS 1, maxCols, maxRows, minX, maxX, minY, maxY, maxIterations
SHOCKING DEVELOPMENT char PLUS remainder
END OF STORY
END OF STORY

DISCOVER HOW TO renderRows WITH row, maxRows, maxCols, minX, maxX, minY, maxY, maxIterations
RUMOR HAS IT
EXPERTS CLAIM lastRow TO BE maxRows MINUS 1
WHAT IF row BEATS lastRow
SHOCKING DEVELOPMENT TOTALLY RIGHT
LIES! RUMOR HAS IT
EXPERTS CLAIM pixels TO BE rowPixels OF row, 0, maxCols, maxRows, minX, maxX, minY, maxY, maxIterations
YOU WON'T WANT TO MISS pixels
SHOCKING DEVELOPMENT renderRows OF row PLUS 1, maxRows, maxCols, minX, maxX, minY, maxY, maxIterations
END OF STORY
END OF STORY

EXPERTS CLAIM maxCols TO BE 60
EXPERTS CLAIM maxRows TO BE 24
EXPERTS CLAIM minX TO BE -2.5
EXPERTS CLAIM maxX TO BE 1
EXPERTS CLAIM minY TO BE -1
EXPERTS CLAIM maxY TO BE 1
EXPERTS CLAIM maxIterations TO BE 30

EXPERTS CLAIM done TO BE renderRows OF 0, maxRows, maxCols, minX, maxX, minY, maxY, maxIterations

PLEASE LIKE AND SUBSCRIBE
98 changes: 98 additions & 0 deletions run-tabloid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env node

/**
* Minimal CLI runner for Tabloid programs.
*
* Usage:
* node run-tabloid.js path/to/program.tabl
*
* The runner wires Tabloid's `YOU WON'T WANT TO MISS` output to stdout,
* and `LATEST NEWS ON` prompts to stdin/stdout synchronously.
*/

const fs = require('fs');
const path = require('path');
const vm = require('vm');

function usageAndExit() {
console.error('Usage: node run-tabloid.js <path-to-program>');
process.exit(1);
}

if (process.argv.length < 3) {
usageAndExit();
}

const progPath = path.resolve(process.argv[2]);
if (!fs.existsSync(progPath) || !fs.statSync(progPath).isFile()) {
console.error(`Program not found: ${progPath}`);
process.exit(1);
}

const langSource = fs.readFileSync(path.join(__dirname, 'static/js/lang.js'), 'utf8');
const programSource = fs.readFileSync(progPath, 'utf8');

const sandbox = {};
vm.createContext(sandbox);
vm.runInContext(
`${langSource}
this.tokenize = tokenize;
this.Parser = Parser;
this.Environment = Environment;
`,
sandbox
);

function createLineReader() {
let buffer = '';
return function readLine(promptText) {
if (promptText) {
process.stdout.write(promptText);
if (!promptText.trim().endsWith(':')) {
process.stdout.write(' ');
}
}
while (true) {
const newlineIndex = buffer.indexOf('\n');
if (newlineIndex >= 0) {
const line = buffer.slice(0, newlineIndex);
buffer = buffer.slice(newlineIndex + 1);
return line.replace(/\r$/, '');
}
const chunk = Buffer.alloc(1024);
const bytesRead = fs.readSync(0, chunk, 0, chunk.length, null);
if (bytesRead === 0) {
const remaining = buffer;
buffer = '';
return remaining;
}
buffer += chunk.toString('utf8', 0, bytesRead);
}
};
}

const readLine = createLineReader();

const env = new sandbox.Environment({
print: s => {
let out = s;
if (out === true) out = 'TOTALLY RIGHT';
if (out === false) out = 'COMPLETELY WRONG';
process.stdout.write(out.toString().toUpperCase() + '!\n');
},
input: promptText => {
let message = promptText;
if (message === true) message = 'TOTALLY RIGHT';
if (message === false) message = 'COMPLETELY WRONG';
return readLine(message.toString());
},
});

try {
const tokens = sandbox.tokenize(programSource);
const nodes = new sandbox.Parser(tokens).parse();
env.run(nodes);
} catch (err) {
console.error(err instanceof Error ? err.stack || err.message : err);
process.exit(1);
}