Compare commits

...

21 Commits

Author SHA1 Message Date
4bcbc6c650 If statements 2025-11-01 10:30:03 +11:00
5e9c2273a9 Remove extra stuff 2025-10-30 17:15:43 +11:00
976e9dfaa0 Increment variables 2025-10-30 17:11:10 +11:00
89065711ce Print strings 2025-10-28 14:48:11 +11:00
9ad3bdbc92 Create variables 2025-10-24 20:32:51 +11:00
176c07151a Remove test.bf, add test.bf to .gitignore 2025-10-20 10:39:53 +11:00
1ceecbb99e Set variables to inputs 2025-10-20 10:38:15 +11:00
0710369871 Remove __pycache__, add .gitignore 2025-10-18 20:00:09 +11:00
5fa47dc377 Start of redesign 2025-10-18 19:55:02 +11:00
c98f248245 Undo literally everything lol 2025-10-16 13:21:27 +11:00
333a1241e8 Set, add, remove bytes 2025-10-11 18:07:46 +11:00
74494711f6 Edit individual bytes of data 2025-10-11 16:59:05 +11:00
ea8d651171 Equal bugfix 2025-10-11 15:20:12 +11:00
f2336c8463 Input and equal commands 2025-10-11 11:11:14 +11:00
ea55468a10 While loop 2025-10-10 14:19:07 +11:00
11f708a9fd Fix error in syntax.md 2025-10-10 08:44:31 +11:00
937cdfa3ef Remove bf file 2025-10-10 08:41:13 +11:00
91f090dc12 Booleans and if function 2025-10-10 08:30:37 +11:00
3ccf7f845f Non-functional integer type, if function 2025-10-10 08:28:12 +11:00
e180adea8c Reassign variables with set, bugfixes for strings shorter than allocated size 2025-10-06 18:33:53 +11:00
ae37d8deab Print function and readme.md 2025-10-06 12:52:38 +11:00
11 changed files with 445 additions and 59 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
src/__pycache__
test.bf

View File

@@ -1,3 +0,0 @@
## create var = Value
Creates the variable "var" and initialises its value.

View File

@@ -1,3 +1,36 @@
## Welcome to BrainAssembly!
BrainAssembly is a language that compiles to Brainfuck. Similar to Assembly languages, it has simple syntax, and is designed to make Brainfuck easier to write code for. Enjoy!
BrainAssembly is a language that compiles to Brainfuck. Similar to Assembly languages, it has simple syntax, and is designed to make Brainfuck easier to write code for. Heavily inspired by Ground. Enjoy!
## Features
- Simple syntax: The language is designed to be very simple, and a framework for larger projects. This also means that the syntax is quite easy to learn.
**NOTE:** Due to the current lack of optimisation in the compiled code, we recommend an optimising intepreter such as [this one](https://copy.sh/brainfuck).
To run:
- First clone this repository and install python.
```
git clone https://chookspace.com/DiamondNether90/BrainAssembly
```
- Next, run the following command to compile a .basm file to Brainfuck:
```bash
python3 src/main.py path/to/file.basm path/to/file.bf
```
(Replace path/to/file.basm with the actual path to your desired .basm file, and replace path/to/file.bf with where you want the compiled file to be created.)
This will create or replace the contents of path/to/file.bf with the compiled BrainAssembly code.
## Test the compiler with our in-built test programs!
You can access our test programs in the test folder.
Example:
```bash
python src/main.py tests/create.basm test.bf
```
This will create a Brainfuck file named test.bf that contains the compiled code of the 'create' test program.

301
src/codegen.py Normal file
View File

@@ -0,0 +1,301 @@
from error import error, warn
import math as m
class Variable:
def __init__(self, name: str, bytes: int, type: int):
self.name: str = name
self.bytes: int = bytes
self.type: int = type
def __repr__(self) -> str:
return f"[{self.name}, {self.bytes}, {self.type}]"
variables: list[Variable] = []
bytesum: int = 0
bool = {
"true": 1,
"false": 0,
}
def getIndex(name: str) -> int:
varnames = []
for var in variables:
varnames.append(var.name)
try:
return varnames.index(name)
except IndexError:
return -1
def generateCode(line: list[str], offset: int) -> str:
keyword = line[0]
if keyword == 'bool':
if len(line) != 3:
error(f'Bool command requires 2 arguments (got {len(line)-1})')
variables.append(Variable(line[1], 1, 0))
if line[2] == 'true':
returnval = '>' * offset + '[>]'
for var in variables[:-1]:
if var.bytes >= 0:
returnval += '>' * (var.bytes+1)
elif var.bytes == -1:
returnval += '>[>]'
else:
error()
returnval += '>+<'
for var in reversed(variables[:-1]):
if var.bytes >= 0:
returnval += '<' * (var.bytes+1)
elif var.bytes == -1:
returnval += '<[<]'
else:
error()
returnval += '<[<]' + '<' * (offset-1)
return returnval
elif line[2] == 'false':
return ''
else:
error(f"Invalid bool: {line[2]}")
elif keyword == 'string':
if len(line) != 4:
error(f'String command requires 3 arguments (got {len(line)-1})')
try:
variables.append(Variable(line[1], int(line[2]), 1))
except ValueError:
error(f'Unexpected bytes for string: {line[2]}')
returnval = '>' * offset + '[>]'
for var in variables[:-1]:
if var.bytes >= 0:
returnval += '>' * (var.bytes + 1)
elif var.bytes == -1:
returnval += '>[>]'
else:
error('Cannot have negative byte count (excluding -1)')
returnval += '>'
# Normal string
if line[3][0] == line[3][-1] == '"':
line[3] = line[3][1:-1]
if (len(line[3]) > int(line[2])) & (line[2] != '-1'):
line[3] = line[3][:(int(line[2])-len(line[3]))]
for char in line[3]:
returnval += '+' * ord(char) + '>'
for char in range(int(line[2])-len(line[3])):
returnval += '>'
else:
error(f'Invalid string: {line[3]}')
# Return to start
for var in reversed(variables):
if var.bytes >= 0:
returnval += '<' * (var.bytes + 1)
elif var.bytes == -1:
returnval += '<[<]'
else:
error()
returnval += '<[<]' + '<' * (offset-1)
return returnval
elif keyword == 'int':
if len(line) != 3:
error(f'Int requires 2 arguments (got {len(line)-1})')
variables.append(Variable(line[1], 4, 2))
returnval: str = '>' * offset + '[>]'
for var in variables[:-1]:
if var.bytes >= 0:
returnval += '>' * (var.bytes+1)
elif var.bytes == -1:
returnval += '>[>]'
else:
error()
returnval += '>'
try:
input = int(line[2])
if abs(input) > 4294967295:
warn(f'Input {line[2]} is too large for int. Overflowing...')
input %= 4294967296
except ValueError:
error(f'Invalid integer: {line[2]}')
values: list[int] = []
for i in reversed(range(4)):
values.append(m.floor(input/(256**i)))
input -= values[-1] * (256**i)
for num in values:
if num < 128:
returnval += '+' * num + '>'
else:
returnval += '-' * (256-num) + '>'
returnval += '<<<<<'
for var in reversed(variables[:-1]):
if var.bytes >= 0:
returnval += '<' * (var.bytes + 1)
elif var.bytes == -1:
returnval += '<[<]'
else:
error()
returnval += '<[<]' + '<' * (offset-1)
return returnval
elif keyword == 'print':
if len(line) != 2:
error(f'Print command requires 1 argument (got {len(line)-1})')
returnval = '>' * offset + '[>]'
idx = getIndex(line[1])
if idx != -1:
for var in variables[:idx]:
if var.bytes >= 0:
returnval += '>' * (var.bytes+1)
elif var.bytes == -1:
returnval += '>[>]'
else:
error()
var = variables[idx]
if var.type != 1:
error(f'Can\'t print {var.name}: Invalid type')
if var.bytes >= 0:
returnval += '>' + '.>' * var.bytes + '<' * (var.bytes+1)
elif var.bytes == -1:
returnval += '>[.>]' + '<[<]'
else:
error()
for var in reversed(variables[:idx]):
if var.bytes >= 0:
returnval += '<' * (var.bytes+1)
elif var.bytes == -1:
returnval += '<[<]'
else:
error()
returnval += '<[<]' + '<' * (offset-1)
else:
error(f'{line[1]} is not a variable')
return returnval
elif keyword == 'inc':
if len(line) != 2:
error(f'Inc command requires 1 argument (got {len(line)-1})')
returnval = '>' * offset + '[>]'
idx = getIndex(line[1])
if variables[idx].type != 2:
error(f'Cannot increment {line[1]}: Invalid type')
for var in variables[:idx]:
if var.bytes > 0:
returnval += '>' * (var.bytes + 1)
elif var.bytes == -1:
returnval += '>[>]'
# Increment logic
'''
The logic in pseudo-python is like this:
byte[3]++
carry = 1
if byte[3] != 0:
carry = 0
if carry == 1:
byte[2]++
if byte[2] != 0:
carry = 0
if carry == 1:
byte[1]++
if byte[1] != 0:
carry = 0
if carry == 1:
byte[0]++
carry = 0
'''
# Carry will be stored to the immediate right of cells
# byte[3]++; carry = 1
returnval += '>>>>+>+'
# If byte[3] != 0: carry = 0
returnval += '<[>-<[<<<<+>>>>-]]<<<<[>>>>+<<<<-]>>>>'
# If carry == 1
returnval += '>['
# byte[2]++
returnval += '<<+'
# if byte[2] != 0: carry = 0
returnval += '[>>-<<[<<<+>>>-]]'
returnval += '<<<[>>>+<<<-]>>>'
# if carry == 1
returnval += '>>['
# byte[1]++
returnval += '<<<+'
# if byte[1] != 0: carry = 0
returnval += '[>>>-<<<[<<+>>-]]<<[>>+<<-]>>'
# if carry == 1
returnval += '>>>['
# byte[0]++; carry = 0
returnval += '<<<<+>>>>-]]]'
# Return to start
returnval += '<<<<<'
for var in reversed(variables[:idx]):
if var.bytes >= 0:
returnval += '<' * (var.bytes+1)
elif var.bytes == -1:
returnval += '<[<]'
else:
error()
returnval += '<[<]' + '<' * (offset-1)
return returnval
elif keyword == 'if':
if len(line) != 2:
error(f'If command requires 1 argument (got {len(line)-1})')
returnval = '>' * offset + '[>]'
idx = getIndex(line[1])
for var in variables[:idx]:
if var.bytes >= 0:
returnval += '>' * (var.bytes+1)
elif var.bytes == -1:
returnval += '>[>]'
else:
error()
returnval += '>[<+>->+<]<[>+<-]>>[<<'
for var in reversed(variables[:idx]):
if var.bytes >= 0:
returnval += '<' * (var.bytes+1)
elif var.bytes == -1:
returnval += '<[<]'
else:
error()
returnval += '<[<]<+>>[>]'
for var in variables[:idx]:
if var.bytes >= 0:
returnval += '>' * (var.bytes+1)
elif var.bytes == -1:
returnval += '>[>]'
else:
error()
returnval += '>>-]<<'
for var in reversed(variables[:idx]):
if var.bytes >= 0:
returnval += '<' * (var.bytes+1)
elif var.bytes == -1:
returnval += '<[<]'
else:
error()
returnval += '<[<]<[' + '<' * (offset-2) + '+' + '>' * (offset-2) + '-]' + '<' * (offset-2) + '[-'
return returnval
elif keyword == 'end':
if len(line) != 2:
error(f'End command requires 2 arguments (got {len(line)-1})')
if line[1] == 'if':
return ']'
else:
error(f'Invalid argument for end: {line[1]}')
else:
error(f'Invalid keyword: {line[0]}')

5
src/error.py Normal file
View File

@@ -0,0 +1,5 @@
def error(message: str = "This is a bugged error message. Please report this issue."):
exit(f"\033[91mError: \033[0m{message}")
def warn(message: str):
print(f"\033[33mWarning: {message}\033[0m")

View File

@@ -1,55 +1,40 @@
import string
# Hopefully this code is actually decent lol
# Note: cells in bf are | 0 | inputs | 0 | variables |
from sys import argv
file = open(argv[1]).readlines();
bfcode = ""
def removeChar(str, char):
ans = ""
for i in str:
if (i != char):
ans += i
return ans
def removeEnd(string, num):
ans = ""
for i in range(len(string)-1):
ans += string[i]
return ans
def ord2(char):
if (len(char) == 1):
return ord(char)
elif (len(char == 0)):
return(0)
from preprocess import preprocess
from error import error
from codegen import generateCode
for i in file:
if (i.split(" ")[0] == "create"):
if (i[len(i)-1] == "\n"):
i = removeEnd(i, 1)
bfcode += ">>>>>>>>>>>>[[>>]>>]"
bytes = int(removeChar(i.split("*", 1)[1].split("=", 1)[0], " "))
a = i.split("=", 1)[1]
if (a[0] == " "):
a = a.split(" ", 1)[1]
# Get type
if ((a[0] == "\"") & (a[len(a)-1] == "\"")):
type = "string"
else:
type = "undefined"
if (type == "string"):
a = a.split("\"", 1)[1]
a = removeEnd(a, 1)
for j in range(bytes):
bfcode += "+++>"
if (j < len(a)):
bfcode += "+" * ord(a[j]) + ">"
bfcode += "<[[<<]<<]<<<<<<<<<"
print(type)
else:
print("what the sigma")
if (len(argv) < 3):
print(f"Usage: python {argv[0]} file.basm file.bf")
quit()
# Check if correct argument count
if (len(argv) != 3) | (argv[1][-5:] != ".basm") | (argv[2][-3:] != ".bf"):
print(f"Usage: python {argv[0]} file.basm file.bf")
quit()
# Get contents of files
try:
with open(argv[1], 'r') as file:
content : str = file.read()
except FileNotFoundError:
error(f"File '{argv[1]}' not found.")
quit()
except IOError:
error(f"Cannot read file '{argv[1]}'.")
quit()
# Pre-processing of the basm file, from preprocess.py
code = preprocess(content)
offset: int = 5
bfcode: str = '>' * offset + ',[>,]<[<]' + '<' * (offset-1)
open(argv[2], 'w').write('')
for line in code:
bfcode += generateCode(line, offset)
with open(argv[2], "w") as file:
file.write(bfcode)
with open(argv[2], 'w') as f:
print(f"{f.write(bfcode)} characters successfully compiled!")

31
src/preprocess.py Normal file
View File

@@ -0,0 +1,31 @@
def preprocess(code: str) -> list[list[str]]:
token : str = ''
tokenise : list[list[str]] = [[]]
isString : bool = False
isComment : bool = False
for i in code:
if i == '\n':
isComment = False
if token != '':
tokenise[-1].append(token)
if tokenise[-1]:
tokenise.append([])
token = ''
elif isComment:
pass
elif i == '#':
isComment = True
elif (i != ' ') | isString:
token += i
if (i == '\"'):
isString = not(isString)
elif token != '':
tokenise[-1].append(token)
token = ''
if token != '':
tokenise[-1].append(token)
token = ''
if tokenise[-1] == []:
tokenise = tokenise[:-1]
return tokenise

28
syntax.md Normal file
View File

@@ -0,0 +1,28 @@
## Work in Progress
To create a comment, begin a line with a hash (#) symbol, like python.
Create a string: `string name value bytes`
Create a boolean: `bool name value`
Create an integer: `int name value`
**Note**: A string of dynamic length can be created by setting the bytes to -1.
Print a string: `print name`
Increment an int: `inc name`
If statement:
```py
if bool
# Code here
end if
```
Example program (prints "Hello, World"):
```py
string var 13 "Hello, world!" # Or -1 bytes for a dynamic string
print var
```

View File

@@ -1,3 +0,0 @@
create str *5 = "Hello, world!"
create str *5 = "World, hello!"
create str *6 = "Sigma!"

View File

7
tests/test.basm Normal file
View File

@@ -0,0 +1,7 @@
string str1 -1 "Hello, world!"
int num 100
bool cond true
if cond
inc num
end if