Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
87cb24c
Merge branch 'main' into dev
Heathcorp Oct 1, 2025
34d01f0
WIP: Refactor tokeniser and add broken test cases, refactor parser sl…
Heathcorp Oct 1, 2025
2d04f7c
Remove comments and fix broken test config
Heathcorp Oct 8, 2025
ea23688
Add and rename some tokeniser tests
Heathcorp Oct 8, 2025
200db2c
Reimplement character and string tokenisation
Heathcorp Oct 10, 2025
aeec20b
Add explicit tape cell type and change existing code to suit
Heathcorp Oct 11, 2025
279a8b1
Remove None token hack
Heathcorp Oct 12, 2025
844c186
Rename Opcode type to Opcode2D and tweak tape origin logic
Heathcorp Oct 13, 2025
06a00c0
Change optimisation injection for bf optimiser
Heathcorp Oct 13, 2025
f15cb74
Add tests for parser +
Heathcorp Oct 14, 2025
e468fb8
Make parser generic for tape cells
Heathcorp Oct 15, 2025
fee5820
Make compiler frontend generic and add TapeCellVariant trait
Heathcorp Oct 16, 2025
81376e0
Change 2d location specifiers to be tuples
Heathcorp Oct 16, 2025
a0ddd99
Tweak extended opcode enum
Heathcorp Oct 17, 2025
7665aed
added thing
MSMissing Oct 18, 2025
64fd405
update also oops wrong folder
MSMissing Oct 18, 2025
fb1f4eb
Delete programs/stack
MSMissing Oct 18, 2025
9b21954
updated the thing
MSMissing Oct 20, 2025
1eefbf3
Added stack32 data type
MSMissing Oct 20, 2025
fec786a
WIP: make backend generic and implement variants
Heathcorp Oct 22, 2025
afbd34b
Fix allocator lifetime issue and refactor black box tests
Heathcorp Oct 23, 2025
07f1ce0
Fix variable scope bug and tweak tests
Heathcorp Oct 26, 2025
1785626
Fix constants optimiser and some bf2d tests
Heathcorp Oct 26, 2025
f3d4fdd
Updated the stack module
MSMissing Nov 3, 2025
a4f85f4
WIP: start parser rewrite
Heathcorp Nov 7, 2025
cf1a616
WIP: fix errors relating to partially completed parser
Heathcorp Nov 7, 2025
60b957e
Add tests for expression parsing
Heathcorp Nov 7, 2025
0bbd3f8
Fix errors in new parser
Heathcorp Nov 8, 2025
6e7f270
Implement parse_block and add tests for it
Heathcorp Nov 8, 2025
a456533
Add more tests for parsing and tokenising
Heathcorp Nov 8, 2025
b44b188
Add tests for tokeniser and increment
Heathcorp Nov 9, 2025
7d54f46
Update Token names and tokeniser tests
Heathcorp Nov 9, 2025
30ca125
Add character literals to new tokeniser
Heathcorp Nov 9, 2025
59852ad
Add tokeniser tests for numbers
Heathcorp Nov 10, 2025
0eb3055
Implement number tokeniser
Heathcorp Nov 10, 2025
dc86986
Fix infinite loop in number parser
Heathcorp Nov 10, 2025
d9ca92f
Fix tokeniser tests, implement word parser
Heathcorp Nov 10, 2025
948e9a6
Fix infinite loop in expression array parser
Heathcorp Nov 10, 2025
2a74e5a
Refactor parser tests and fix expressions bugs
Heathcorp Nov 10, 2025
9a6ebc5
Fix incorrect parser tests
Heathcorp Nov 10, 2025
7a4c54f
Implement drain/copy loop parsing
Heathcorp Nov 10, 2025
c375a0a
Fix parsing bug with assignments
Heathcorp Nov 10, 2025
fec9257
Fix loop issues in parser
Heathcorp Nov 11, 2025
6776b62
Add comment stripping, assertion parsing
Heathcorp Nov 11, 2025
93f811e
Add function call parsing and change how frontend handles function ar…
Heathcorp Nov 11, 2025
e4c362e
Fix issue with struct definition parsing
Heathcorp Nov 11, 2025
6f2f236
Fix all tests with new parser
Heathcorp Nov 11, 2025
a2f76a7
Update variables, conditionals, and rewrite loops docos
Heathcorp Nov 11, 2025
5c9a00b
Update functions and inline brainfuck docos
Heathcorp Nov 11, 2025
49498b5
Update optimisation and variants docos
Heathcorp Nov 12, 2025
df1ce84
Remove unimplemented optimisation configs, collate and move documenta…
Heathcorp Nov 12, 2025
38b4338
Fix issue with reference contents
Heathcorp Nov 12, 2025
52f7fac
Merge pull request #23 from Heathcorp/refactor-oct-2025
Heathcorp Nov 12, 2025
002224b
Tweak colours of commit badges
Heathcorp Nov 12, 2025
2c94533
Refactor compiler directory structure
Heathcorp Dec 18, 2025
d595907
Merge branch 'refactor-nov-2025' into dev
Heathcorp Dec 18, 2025
fbefb4f
Revise and test stack type and add to file loader
Heathcorp Dec 18, 2025
de43a49
Merge pull request #25 from MSMissing/main
Heathcorp Dec 18, 2025
1386d7c
Add stack type to reference and restructure readmes
Heathcorp Dec 18, 2025
5de57b3
Add note to brainfuck example
Heathcorp Dec 18, 2025
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?

.env
59 changes: 4 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,10 @@

Mastermind is a programming language designed to compile to the esoteric language _Brainfuck_.

Brainfuck is essentially a modern interpretation of the classical Turing machine. It consists of a tape of 8-bit values, with simple increment/decrement, move left/right, and control flow operations. The full language only uses 8 control characters: `+-><.,[]`.
Brainfuck is essentially a modern interpretation of the classical Turing machine. It consists of an array (or _tape_) of 8-bit values, with simple increment/decrement, move left/right, and control flow operations. The full language only uses 8 control characters: `+-><.,[]`.

Imagine if C was designed for computer architectures that run Brainfuck directly, that is what Mastermind is intended to be.
Imagine an alternate reality where C was designed for computer architectures that run Brainfuck natively, that is what Mastermind is intended to be.

## Development and Setup
Mastermind language/compiler reference can be found here: [https://github.com/Heathcorp/Mastermind/blob/main/reference.md]()

### Quickstart:

- Install Rust/Cargo and Node/NPM.
- Install Yarn: `npm i --global yarn`.
- Run `yarn`.
- Run `yarn build:wasm`.
- Run `yarn build:grammar`.
- Run `yarn dev`, then follow the link to http://localhost:5173.

Commits to _dev_ and _main_ are published to https://staging.mastermind.lostpixels.org and https://mastermind.lostpixels.org respectively.

### Overview:

This repository contains two main components: the compiler and the web IDE. There are GitHub Actions workflows which build, test, and deploy the web IDE (with bundled compiler) to Firebase Web Hosting.

#### Compiler

The `./compiler` subdirectory contains a Cargo (Rust) package, ensure Rust is installed.

The compiler codebase has two main entrypoints: `main.rs` and `lib.rs`, for the command-line and WASM compilation targets respectively. All other Rust source files are common between compilation targets.

Key files to look at:

- `tokeniser.rs`: tokenises the raw text files into Mastermind syntax tokens.
- `parser.rs`: parses strings of tokens into higher-level Mastermind clauses.
- `compiler.rs`: compiles the high-level clauses into a list of basic instructions akin to an intermediate representation (IR).
- `builder.rs`: takes the basic instructions from the compiler and builds the final Brainfuck program.

Some key commands:

(from within the `./compiler` subdirectory)

- `cargo run -- -h`: runs the command-line compiler module and displays command help information
- `cargo test`: runs the automated test suite
- `cargo build`: builds the command-line module
- `wasm-pack build`: builds the WASM module

#### Web IDE

The project root directory `package.json`/`yarn.lock` defines a Node package managed with Yarn. Most important commands or behaviours are defined as `npm run` or `yarn` scripts within `package.json`.

Ensure Node is installed, then ensure Yarn is installed with `npm i --global yarn`.

The web IDE is a SolidJS app using TypeScript/TSX, and Vite as a bundler. The text editing portions of the UI are provided by the _codemirror_ plugin, and syntax highlighting is defined in the included _lezer_ grammar: `./src/lexer/mastermind.grammar`.

Some key commands:

- `yarn`: installs npm packages
- `yarn build:wasm`: builds the compiler WASM module
- `yarn build:grammar`: compiles the lezer grammar to JS for use in codemirror
- `yarn dev`: runs the SolidJS app in a local Vite dev server
- `yarn build`: builds the SolidJS app
Development guide can be found here: [https://github.com/Heathcorp/Mastermind/blob/main/devguide.md]()
248 changes: 248 additions & 0 deletions compiler/src/backend/bf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
use super::common::{
BrainfuckBuilder, BrainfuckBuilderData, BrainfuckProgram, CellAllocator, CellAllocatorData,
OpcodeVariant, TapeCellVariant,
};
use crate::macros::macros::{r_assert, r_panic};

pub type TapeCell = i32;
impl TapeCellVariant for TapeCell {
fn origin_cell() -> TapeCell {
0
}
fn with_offset(&self, offset: i32) -> Self {
self + offset
}
}

#[derive(Clone, Copy, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub enum Opcode {
Add,
Subtract,
Right,
Left,
OpenLoop,
CloseLoop,
Output,
Input,
Clear,
}

impl OpcodeVariant for Opcode {
fn try_from_char(c: char) -> Option<Opcode> {
match c {
'+' => Some(Opcode::Add),
'-' => Some(Opcode::Subtract),
'>' => Some(Opcode::Right),
'<' => Some(Opcode::Left),
'[' => Some(Opcode::OpenLoop),
']' => Some(Opcode::CloseLoop),
'.' => Some(Opcode::Output),
',' => Some(Opcode::Input),
_ => None,
}
}
}

impl CellAllocator<TapeCell> for CellAllocatorData<TapeCell> {
/// Check if the desired number of cells can be allocated to the right of a given location
fn check_allocatable(&mut self, location: &TapeCell, size: usize) -> bool {
for k in 0..size {
if self.cells.contains(&(location + k as i32)) {
return false;
}
}
return true;
}

/// Allocate size number of cells and return the location, optionally specify a location
fn allocate(&mut self, location: Option<TapeCell>, size: usize) -> Result<TapeCell, String> {
if let Some(l) = location {
if !self.check_allocatable(&l, size) {
r_panic!("Location specifier @{l} conflicts with another allocation");
}
}

// find free space
let mut region_start = location.unwrap_or(0);
for i in region_start.. {
if self.cells.contains(&i) {
region_start = i + 1;
} else if i - region_start == (size as i32 - 1) {
break;
}
}

for i in region_start..(region_start + size as i32) {
r_assert!(
self.cells.insert(i),
"Unreachable error detected in cell allocation: allocate({location:?}, {size:?})"
);
}

Ok(region_start)
}

/// Allocate a cell as close as possible to the given cell,
/// used for optimisations which need extra cells for efficiency
fn allocate_temp_cell(&mut self, location: TapeCell) -> TapeCell {
// alternate left then right, getting further and further out
let mut left_iter = (0..=location).rev();
let mut right_iter = (location + 1)..;
loop {
if let Some(i) = left_iter.next() {
// unallocated cell, allocate it and return
if self.cells.insert(i) {
return i;
}
}

if let Some(i) = right_iter.next() {
if self.cells.insert(i) {
return i;
}
}
}
}

fn free(&mut self, cell: TapeCell, size: usize) -> Result<(), String> {
for i in cell..(cell + size as i32) {
r_assert!(
self.cells.remove(&i),
"Cannot free cell @{i} as it is not allocated.",
);
}

Ok(())
}
}

impl BrainfuckProgram for Vec<Opcode> {
fn to_string(self) -> String {
let mut s = String::new();
self.into_iter().for_each(|o| {
s.push_str(match o {
Opcode::Add => "+",
Opcode::Subtract => "-",
Opcode::Right => ">",
Opcode::Left => "<",
Opcode::OpenLoop => "[",
Opcode::CloseLoop => "]",
Opcode::Output => ".",
Opcode::Input => ",",
Opcode::Clear => "[-]",
})
});
s
}

fn from_str(s: &str) -> Vec<Opcode> {
let mut ops = Vec::new();
let mut i = 0;
while i < s.len() {
let substr = &s[i..];
if substr.starts_with("[-]") {
ops.push(Opcode::Clear);
i += 3;
} else {
match substr.chars().next().unwrap() {
'+' => ops.push(Opcode::Add),
'-' => ops.push(Opcode::Subtract),
'>' => ops.push(Opcode::Right),
'<' => ops.push(Opcode::Left),
'[' => ops.push(Opcode::OpenLoop),
']' => ops.push(Opcode::CloseLoop),
'.' => ops.push(Opcode::Output),
',' => ops.push(Opcode::Input),
_ => (), // could put a little special opcode in for other characters
}
i += 1;
}
}

ops
}
}

impl BrainfuckProgram for BrainfuckBuilderData<TapeCell, Opcode> {
fn to_string(self) -> String {
self.opcodes.to_string()
}

fn from_str(s: &str) -> BrainfuckBuilderData<TapeCell, Opcode> {
BrainfuckBuilderData {
opcodes: Vec::from_str(s),
head_pos: 0,
// head_pos: TapeCell(0),
}
}
}

impl BrainfuckBuilder<TapeCell, Opcode> for BrainfuckBuilderData<TapeCell, Opcode> {
fn new() -> BrainfuckBuilderData<TapeCell, Opcode> {
BrainfuckBuilderData {
opcodes: Vec::new(),
head_pos: 0,
}
}
fn len(&self) -> usize {
self.opcodes.len()
}
fn push(&mut self, op: Opcode) {
self.opcodes.push(op);
}
fn extend<T>(&mut self, ops: T)
where
T: IntoIterator<Item = Opcode>,
{
self.opcodes.extend(ops);
}
fn move_to_cell(&mut self, cell: TapeCell) {
let x = cell;
let x_pos = self.head_pos;
//Move x level
if x_pos < x {
for _ in x_pos..x {
self.opcodes.push(Opcode::Right);
}
} else if x < x_pos {
// theoretically equivalent to cell..head_pos?
for _ in ((x + 1)..=x_pos).rev() {
self.opcodes.push(Opcode::Left);
}
}

self.head_pos = cell;
}

fn add_to_current_cell(&mut self, imm: i8) {
if imm > 0 {
for _ in 0..imm {
self.opcodes.push(Opcode::Add);
}
} else if imm < 0 {
// needs to be i32 because -(-128) = -128 in i8-land
for _ in 0..-(imm as i32) {
self.opcodes.push(Opcode::Subtract);
}
}
}

fn clear_current_cell(&mut self) {
self.opcodes.push(Opcode::OpenLoop);
self.opcodes.push(Opcode::Subtract);
self.opcodes.push(Opcode::CloseLoop);
}
fn output_current_cell(&mut self) {
self.opcodes.push(Opcode::Output);
}
fn input_to_current_cell(&mut self) {
self.opcodes.push(Opcode::Input);
}
fn open_loop(&mut self) {
self.opcodes.push(Opcode::OpenLoop);
}
fn close_loop(&mut self) {
self.opcodes.push(Opcode::CloseLoop);
}
}
Loading